Fix locale configuration issue on Arch Linux

On a new installed Arch Linux server, I come across the following problem:

# locale -a
locale: Cannot set LC_CTYPE to default locale: No such file or directory
locale: Cannot set LC_MESSAGES to default locale: No such file or directory
locale: Cannot set LC_COLLATE to default locale: No such file or directory
C
POSIX
# locale
locale: Cannot set LC_CTYPE to default locale: No such file or directory
locale: Cannot set LC_MESSAGES to default locale: No such file or directory
locale: Cannot set LC_ALL to default locale: No such file or directory
LANG=en_US.UTF-8
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC="en_US.UTF-8"
LC_TIME="en_US.UTF-8"
LC_COLLATE="en_US.UTF-8"
LC_MONETARY="en_US.UTF-8"
LC_MESSAGES="en_US.UTF-8"
LC_PAPER="en_US.UTF-8"
LC_NAME="en_US.UTF-8"
LC_ADDRESS="en_US.UTF-8"
LC_TELEPHONE="en_US.UTF-8"
LC_MEASUREMENT="en_US.UTF-8"
LC_IDENTIFICATION="en_US.UTF-8"
LC_ALL=

Especially when using man command:

# man wait
man: can't set the locale; make sure $LC_* and $LANG are correct

After referring this post, I fix the issue. My /etc/locale.conf is like this:

# cat /etc/locale.conf
LANG=en_US.UTF-8

But en_US.UTF-8 is commented out in /etc/locale.gen:

#en_US.UTF-8 UTF-8

uncomment it, then run locale-gen command, all become normal.

Out-of-bound access of vector in C++

Today I debugged a crash bug of C++ program, and the core dump is like this:

Program terminated with signal SIGABRT, Aborted.
#0  0x00007f57ba2ed860 in raise () from /usr/lib/libc.so.6
(gdb) bt
#0  0x00007f57ba2ed860 in raise () from /usr/lib/libc.so.6
#1  0x00007f57ba2eeec9 in abort () from /usr/lib/libc.so.6
#2  0x00007f57ba330437 in __libc_message () from /usr/lib/libc.so.6
#3  0x00007f57ba336d34 in malloc_printerr () from /usr/lib/libc.so.6
#4  0x00005593dc2f7b6c in __gnu_cxx::new_allocator<int>::deallocate (this=0x7ffc65848820, __p=0x5593dce6fac0)
    at /usr/include/c++/7.2.1/ext/new_allocator.h:125
#5  0x00005593dc2f7a36 in std::allocator_traits<std::allocator<int> >::deallocate (__a=..., __p=0x5593dce6fac0, __n=12)
    at /usr/include/c++/7.2.1/bits/alloc_traits.h:462
#6  0x00005593dc2f789a in std::_Vector_base<int, std::allocator<int> >::_M_deallocate (this=0x7ffc65848820,
    __p=0x5593dce6fac0, __n=12) at /usr/include/c++/7.2.1/bits/stl_vector.h:180
#7  0x00005593dc2f7543 in std::_Vector_base<int, std::allocator<int> >::~_Vector_base (this=0x7ffc65848820,
    __in_chrg=<optimized out>) at /usr/include/c++/7.2.1/bits/stl_vector.h:162
#8  0x00005593dc2f71cf in std::vector<int, std::allocator<int> >::~vector (this=0x7ffc65848820,
    __in_chrg=<optimized out>) at /usr/include/c++/7.2.1/bits/stl_vector.h:435
......

From the stack trace, we can see the abort() occurred in vector‘s destructor function. After some debugging, I find the root cause is the general “out-of-bound” error, which accessed the memory beyond the vector space. But the caveat is that “out-of-bound” error may be silent and give you no hurt apparently. E.g., I write a simple test program:

#include <vector>

void fun() {
    std::vector<int> v(1);

    auto it = v.begin();
    for (int i = 0; i < 100; i++) {
        *it++ = i;
    }
}
int main() {
    fun();
    return 0;
}

Build and run it:

# g++ -g test.cpp
# ./a.out
# ./a.out

The application goes well. So we really should pay enough attention to vector access.

Install make before using cmake

Today I tried to build a project, but running “cmake” gave me a strange output:

# cmake ..
CMake Error: CMake was unable to find a build program corresponding to "Unix Makefiles".  CMAKE_MAKE_PROGRAM is not set.  You probably need to select a different build tool.
CMake Error: CMAKE_C_COMPILER not set, after EnableLanguage
CMake Error: CMAKE_CXX_COMPILER not set, after EnableLanguage

After referring this discussion: CMAKE_MAKE_PROGRAM not found, I suddenly realized that this a new setup server, so make program may not be installed. As expected:

# make
-bash: make: command not found

After installing make, the cmake flow works normally.

The anatomy of tee program on OpenBSD

The tee command is used to read content from standard input and displays it not only in standard output but also saves to other files simultaneously. The source code of tee in OpenBSD is very simple, and I want to give it an analysis:

(1) tee leverages Singlely-linked List defined in sys/queue.h to manage outputted files (including standard output):

struct list {
    SLIST_ENTRY(list) next;
    int fd;
    char *name;
};
SLIST_HEAD(, list) head;

......

static void
add(int fd, char *name)
{
    struct list *p;
    ......
    SLIST_INSERT_HEAD(&head, p, next);
}

int
main(int argc, char *argv[])
{
    struct list *p;
    ......
    SLIST_INIT(&head);
    ......
    SLIST_FOREACH(p, &head, next) {
        ......
    }
}

To understand it easily, I extract the macros from sys/queue.h and created a file which utilizes the marcos:

#define SLIST_HEAD(name, type)                      \
struct name {                               \
    struct type *slh_first; /* first element */         \
}

#define SLIST_ENTRY(type)                       \
struct {                                \
    struct type *sle_next;  /* next element */          \
}

#define SLIST_FIRST(head)   ((head)->slh_first)
#define SLIST_END(head)     NULL
#define SLIST_EMPTY(head)   (SLIST_FIRST(head) == SLIST_END(head))
#define SLIST_NEXT(elm, field)  ((elm)->field.sle_next)

#define SLIST_FOREACH(var, head, field)                 \
    for((var) = SLIST_FIRST(head);                  \
        (var) != SLIST_END(head);                   \
        (var) = SLIST_NEXT(var, field))

#define SLIST_INIT(head) {                      \
    SLIST_FIRST(head) = SLIST_END(head);                \
}

#define SLIST_INSERT_HEAD(head, elm, field) do {            \
    (elm)->field.sle_next = (head)->slh_first;          \
    (head)->slh_first = (elm);                  \
} while (0)

struct list {
    SLIST_ENTRY(list) next;
    int fd;
    char *name;
};
SLIST_HEAD(, list) head;

int
main(int argc, char *argv[])
{
    struct list *p;
    SLIST_INIT(&head);

    SLIST_INSERT_HEAD(&head, p, next);
    SLIST_FOREACH(p, &head, next) {

    }
}

Then employed gcc‘s pre-processing function:

# gcc -E slist.c
# 1 "slist.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "slist.c"
# 30 "slist.c"
struct list {
 struct { struct list *sle_next; } next;
 int fd;
 char *name;
};
struct { struct list *slh_first; } head;

int
main(int argc, char *argv[])
{
 struct list *p;
 { ((&head)->slh_first) = NULL; };

 do { (p)->next.sle_next = (&head)->slh_first; (&head)->slh_first = (p); } while (0);
 for((p) = ((&head)->slh_first); (p) != NULL; (p) = ((p)->next.sle_next)) {

 }
}

It becomes clear now! The head node in list contains only 1 member: slh_first, which points to the first valid node. For the elements in the list, it is embedded with next struct which uses sle_next to refer to next buddy.

(2) By default, tee will overwrite the output files. If you want to append it, use -a option, and the code is as following:

while (*argv) {
    if ((fd = open(*argv, O_WRONLY | O_CREAT |
        (append ? O_APPEND : O_TRUNC), DEFFILEMODE)) == -1) {
        ......
    } 
    ......
}

(3) The next part is the skeleton of saving content to files:

while ((rval = read(STDIN_FILENO, buf, sizeof(buf))) > 0) {
    SLIST_FOREACH(p, &head, next) {
        n = rval;
        bp = buf;
        do {
            if ((wval = write(p->fd, bp, n)) == -1) {
                ......
            }
            bp += wval;
        } while (n -= wval);
    }
}

We need to iterates every opened file descriptor and write contents into it.

(4) Normally, theinterrupt signal will cause tee exit:

# tee
fdkfkdfjk
fdkfkdfjk
^C
#

To disable this feature, use -i option:

# tee -i
fdhfhd
fdhfhd
^C^C

The corresponding code is like this:

......
case 'i':
    (void)signal(SIGINT, SIG_IGN);
    break;

The gRPC server program will crash when can’t bind successfully

a server program which uses gRPC crashed when started:

$ ./server 60001
......
E1123 15:37:54.133480971   14408 server_chttp2.c:38]         {"created":"@1511422674.133408109","description":"No address added out of total 1 resolved","file":"src/core/ext/transport/chttp2/server/chttp2_server.c","file_line":245,"referenced_errors":[{"created":"@1511422674.133405147","description":"Failed to add any wildcard listeners","file":"src/core/lib/iomgr/tcp_server_posix.c","file_line":338,"referenced_errors":[{"created":"@1511422674.133394827","description":"Unable to configure socket","fd":4,"file":"src/core/lib/iomgr/tcp_server_utils_posix_common.c","file_line":200,"referenced_errors":[{"created":"@1511422674.133385167","description":"OS Error","errno":98,"file":"src/core/lib/iomgr/tcp_server_utils_posix_common.c","file_line":173,"os_error":"Address already in use","syscall":"bind"}]},{"created":"@1511422674.133404647","description":"Unable to configure socket","fd":4,"file":"src/core/lib/iomgr/tcp_server_utils_posix_common.c","file_line":200,"referenced_errors":[{"created":"@1511422674.133401558","description":"OS Error","errno":98,"file":"src/core/lib/iomgr/tcp_server_utils_posix_common.c","file_line":173,"os_error":"Address already in use","syscall":"bind"}]}]}]}
Segmentation fault (core dumped)

It is weird because it runs well yesterday. Check the core dump file:

......
Core was generated by `./server 60001'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x00007f30edbbf9b0 in pthread_mutex_lock () from /usr/lib/libpthread.so.0
(gdb) bt
#0  0x00007f30edbbf9b0 in pthread_mutex_lock () from /usr/lib/libpthread.so.0
#1  0x000055c312c87077 in grpc::Server::Wait() ()
......

No clue. So format the above json log:

{  
   "created":"@1511422674.133408109",
   "description":"No address added out of total 1 resolved",
   "file":"src/core/ext/transport/chttp2/server/chttp2_server.c",
   "file_line":245,
   "referenced_errors":[  
      {  
         "created":"@1511422674.133405147",
         "description":"Failed to add any wildcard listeners",
         "file":"src/core/lib/iomgr/tcp_server_posix.c",
         "file_line":338,
         "referenced_errors":[  
            {  
               "created":"@1511422674.133394827",
               "description":"Unable to configure socket",
               "fd":4,
               "file":"src/core/lib/iomgr/tcp_server_utils_posix_common.c",
               "file_line":200,
               "referenced_errors":[  
                  {  
                     "created":"@1511422674.133385167",
                     "description":"OS Error",
                     "errno":98,
                     "file":"src/core/lib/iomgr/tcp_server_utils_posix_common.c",
                     "file_line":173,
                     "os_error":"Address already in use",
                     "syscall":"bind"
                  }
               ]
            },
            {  
               "created":"@1511422674.133404647",
               "description":"Unable to configure socket",
               "fd":4,
               "file":"src/core/lib/iomgr/tcp_server_utils_posix_common.c",
               "file_line":200,
               "referenced_errors":[  
                  {  
                     "created":"@1511422674.133401558",
                     "description":"OS Error",
                     "errno":98,
                     "file":"src/core/lib/iomgr/tcp_server_utils_posix_common.c",
                     "file_line":173,
                     "os_error":"Address already in use",
                     "syscall":"bind"
                  }
               ]
            }
         ]
      }

Oh, I see. “Address already in use” means the 60001 port is occupied already. I switch to another port, and it works.