sfakeroot

manipulate files faking root privileges
git clone git://git.vx21.xyz/sfakeroot
Log | Files | Refs | README | LICENSE

sfakeroot.c (21214B)


      1 /* sfakeroot: manipulate files faking root privileges
      2  *
      3  * Copyright © 2020 - 2025 Richard Ipsum
      4  *
      5  * This program is free software: you can redistribute it and/or modify
      6  * it under the terms of the GNU General Public License as published by
      7  * the Free Software Foundation, version 3 of the License.
      8  *
      9  * This program is distributed in the hope that it will be useful,
     10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12  * GNU General Public License for more details.
     13  *
     14  * You should have received a copy of the GNU General Public License
     15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
     16  *
     17  */
     18 
     19 #include <syslog.h>
     20 #include <stdarg.h>
     21 
     22 #include <stdio.h>
     23 #include <stdlib.h>
     24 #include <stdbool.h>
     25 #include <limits.h>
     26 #include <dirent.h>
     27 #include <unistd.h>
     28 #include <string.h>
     29 #include <errno.h>
     30 #include <arpa/inet.h>
     31 #include <sys/types.h>
     32 #include <sys/socket.h>
     33 #include <sys/wait.h>
     34 #include <sys/un.h>
     35 #include <signal.h>
     36 
     37 #include "sfakeroot_inline.h"
     38 #include "sfakeroot.h"
     39 
     40 #include <sys/syscall.h>
     41 
     42 #define FALLBACK_SHELL "/bin/sh"
     43 #define SONAME "libsfakeroot.so"
     44 
     45 #ifdef __linux__
     46 #define SESSION_FILE_LINE_FMT "%lu,%u,%u,%u\n"
     47 #else
     48 #define SESSION_FILE_LINE_FMT "%llu,%u,%u,%u\n"
     49 #endif
     50 
     51 static char *argv0;
     52 
     53 struct sfakeroot_list {
     54     struct sfakeroot_ent *first;
     55     struct sfakeroot_ent *last;
     56 };
     57 
     58 struct sfakeroot_list ents;
     59 
     60 struct cmdline_options {
     61     bool require_session_file;
     62     char *session_file;
     63 };
     64 
     65 struct cmdline_arguments {
     66     struct cmdline_options options;
     67     char **args;
     68     size_t args_len;
     69 };
     70 
     71 static struct sfakeroot_ent *lookupent(struct sfakeroot_list *list, ino_t st_ino)
     72 {
     73     for (struct sfakeroot_ent *e = list->first; e != NULL; e = e->next) {
     74         debug("checking st_ino %llu\n", e->st.st_ino);
     75         if (e->st.st_ino == st_ino) {
     76             return e;
     77         }
     78     }
     79 
     80     debug("lookupent returning NULL\n");
     81     return NULL;
     82 }
     83 
     84 static struct sfakeroot_ent *addent(struct sfakeroot_list *list,
     85                                     const struct stat *s, bool stale)
     86 {
     87     struct sfakeroot_ent *ent;
     88 
     89     ent = malloc(sizeof (*ent));
     90     if (ent == NULL) {
     91         return NULL;
     92     }
     93 
     94     debug("add ent: %llu has mode %o\n", s->st_ino, s->st_mode);
     95 
     96     ent->stale = stale;
     97     ent->next = NULL;
     98     ent->st = *s;
     99 
    100     if (list->first == NULL) {
    101         list->first = ent;
    102         goto out;
    103     }
    104 
    105     if (list->last != NULL) {
    106         list->last->next = ent;
    107     }
    108 
    109 out:
    110     list->last = ent;
    111     return ent;
    112 }
    113 
    114 static void handle_stat(struct sfakeroot_msg *m)
    115 {
    116     struct sfakeroot_ent *ent;
    117     int errno_sv = 0;
    118     int cwdfd;
    119     ino_t inode;
    120     DIR *cwd = opendir(".");
    121 
    122     if (cwd == NULL) {
    123         m->retcode = -1;
    124         return;
    125     }
    126     cwdfd = dirfd(cwd);
    127     if (chdir(m->working_dir) == -1) {
    128         closedir(cwd);
    129         m->retcode = -1;
    130         return;
    131     }
    132 
    133     switch (m->type) {
    134         case SFAKEROOT_MSG_FSTAT:
    135             syslog(LOG_WARNING, "handle_fstat fd: %d working dir: %s\n", m->fd, m->working_dir);
    136             m->retcode = fstat(m->fd, &m->st);
    137             inode = m->st.st_ino;
    138             errno_sv = errno;
    139             break;
    140         case SFAKEROOT_MSG_STAT:
    141             syslog(LOG_WARNING, "handle_stat path: %s, working dir: %s\n", m->path, m->working_dir);
    142             m->retcode = stat(m->path, &m->st);
    143             inode = m->st.st_ino;
    144             errno_sv = errno;
    145             break;
    146         case SFAKEROOT_MSG_LSTAT:
    147             syslog(LOG_WARNING, "handle_lstat path: %s, working dir: %s\n", m->path, m->working_dir);
    148             m->retcode = lstat(m->path, &m->st);
    149             inode = m->st.st_ino;
    150             errno_sv = errno;
    151             break;
    152         case SFAKEROOT_MSG_FSTATAT:
    153             m->retcode = fstatat(m->fd, m->path, &m->st, m->flag);
    154             inode = m->st.st_ino;
    155             errno_sv = errno;
    156             break;
    157 #if USE_STATX
    158         case SFAKEROOT_MSG_STATX:
    159             syslog(LOG_WARNING, "handle_statx path: %s, working dir: %s\n", m->path, m->working_dir);
    160             m->retcode = statx(m->fd, m->path, m->flag, m->mask, &m->statxbuf);
    161             inode = m->statxbuf.stx_ino;
    162             errno_sv = errno;
    163             break;
    164 #endif
    165         default:
    166             debug("non stat message in handle_stat\n");
    167             goto cleanup;
    168     }
    169 
    170     if (m->retcode != 0) {
    171         m->reterrno = errno_sv;
    172         goto cleanup;
    173     }
    174 
    175     if ((ent = lookupent(&ents, inode)) != NULL) {
    176         debug("lookupent returned ent for inode %d, has st_mode: %o\n", inode, ent->st.st_mode);
    177         if (ent->stale) {
    178             /* update the entry with the new stat data */
    179 #if USE_STATX
    180             m->statxbuf.stx_uid = ent->st.st_uid;
    181             m->statxbuf.stx_gid = ent->st.st_gid;
    182             m->statxbuf.stx_mode = ent->st.st_mode;
    183 #endif
    184             m->st.st_uid = ent->st.st_uid;
    185             m->st.st_gid = ent->st.st_gid;
    186             m->st.st_mode = ent->st.st_mode;
    187             ent->st = m->st;
    188             ent->stale = false;
    189         }
    190         else {
    191 #if USE_STATX
    192             m->statxbuf.stx_uid = ent->st.st_uid;
    193             m->statxbuf.stx_gid = ent->st.st_gid;
    194             m->statxbuf.stx_mode = ent->st.st_mode;
    195 #endif
    196             m->st = ent->st;
    197         }
    198         if ((int) m->st.st_uid == -1) {
    199             m->st.st_uid = 0;
    200 #if USE_STATX
    201             m->statxbuf.stx_uid = 0;
    202 #endif
    203         }
    204         if ((int) m->st.st_gid == -1) {
    205             m->st.st_gid = 0;
    206 #if USE_STATX
    207             m->statxbuf.stx_gid = 0;
    208 #endif
    209         }
    210 
    211         goto cleanup;
    212     }
    213 
    214     m->st.st_uid = 0;
    215     m->st.st_gid = 0;
    216 #if USE_STATX
    217     m->statxbuf.stx_uid = 0;
    218     m->statxbuf.stx_gid = 0;
    219 #endif
    220 
    221 cleanup:
    222     fchdir(cwdfd);
    223     closedir(cwd);
    224 }
    225 
    226 static void handle_perms_change(struct sfakeroot_msg *m)
    227 {
    228     struct stat s = {0};
    229     int errno_sv;
    230     int cwdfd;
    231     struct sfakeroot_ent *ent;
    232     DIR *cwd = opendir(".");
    233 
    234     if (cwd == NULL) {
    235         m->retcode = -1;
    236         return;
    237     }
    238     cwdfd = dirfd(cwd);
    239     if (chdir(m->working_dir) == -1) {
    240         closedir(cwd);
    241         m->retcode = -1;
    242         return;
    243     }
    244 
    245     debug("handle_perms_change: m->path: %s, m->working_dir: %s\n",
    246           m->path, m->working_dir);
    247 
    248     switch (m->type) {
    249         case SFAKEROOT_MSG_LCHOWN:
    250             m->retcode = lstat(m->path, &s);
    251             errno_sv = errno;
    252             break;
    253         case SFAKEROOT_MSG_CHOWN:
    254         case SFAKEROOT_MSG_CHMOD:
    255             m->retcode = stat(m->path, &s);
    256             errno_sv = errno;
    257             break;
    258         case SFAKEROOT_MSG_FCHOWNAT:
    259             m->retcode = fstatat(m->fd, m->path, &s, m->flag);
    260             errno_sv = errno;
    261             break;
    262         case SFAKEROOT_MSG_FCHOWN:
    263             m->retcode = fstat(m->fd, &s);
    264             errno_sv = errno;
    265             break;
    266         default:
    267             debug("non chown/chmod message in handle_perms_change\n");
    268             goto cleanup;
    269     }
    270 
    271     if (m->retcode == -1) {
    272         debug("stat returned error\n");
    273         m->reterrno = errno_sv;
    274         goto cleanup;
    275     }
    276 
    277     /* path exists */
    278     switch (m->type) {
    279         case SFAKEROOT_MSG_LCHOWN:
    280         case SFAKEROOT_MSG_CHOWN:
    281         case SFAKEROOT_MSG_FCHOWN:
    282         case SFAKEROOT_MSG_FCHOWNAT:
    283             s.st_uid = m->uid;
    284             s.st_gid = m->gid;
    285             break;
    286         case SFAKEROOT_MSG_CHMOD:
    287             debug("setting mode %o\n", m->mode);
    288             s.st_mode = (s.st_mode & S_IFMT) | m->mode;
    289             break;
    290         default:
    291             debug("non chown/chmod message in handle_perms_change\n");
    292             goto cleanup;
    293     }
    294 
    295     if ((ent = lookupent(&ents, s.st_ino)) != NULL) {
    296         uid_t u = ent->st.st_uid;
    297         gid_t g = ent->st.st_gid;
    298         mode_t md = ent->st.st_mode;
    299         ent->st = s;
    300         ent->st.st_uid = ((int) m->uid == -1) ? u : m->uid;
    301         ent->st.st_gid = ((int) m->gid == -1) ? g : m->gid;
    302         ent->st.st_mode = m->mode ? m->mode : md;
    303         m->retcode = 0;
    304         goto cleanup;
    305     }
    306 
    307     s.st_uid = m->uid;
    308     s.st_gid = m->gid;
    309 
    310     if (addent(&ents, &s, false) == NULL) {
    311         m->reterrno = ENOMEM;
    312         m->retcode = -1;
    313         goto cleanup;
    314     }
    315 
    316     m->retcode = 0;
    317 
    318 cleanup:
    319     fchdir(cwdfd);
    320     closedir(cwd);
    321 }
    322 
    323 /* listen for new connections */
    324 static int sfakeroot_create_listener(const char *session_socket_path)
    325 {
    326     static struct sockaddr_un sa = {.sun_family = AF_UNIX};
    327     socklen_t sock_namelen;
    328     int sock;
    329 
    330     sock = socket(AF_UNIX, SOCK_STREAM, 0);
    331     if (sock == -1) {
    332         fprintf(stderr, "%s: socket: %s\n", argv0, strerror(errno));
    333         exit(1);
    334     }
    335 
    336     strlcpy(sa.sun_path, session_socket_path, sizeof (sa.sun_path));
    337     if (unlink(session_socket_path) == -1 && errno != ENOENT) {
    338         fprintf(stderr, "%s: unlink \"%s\" failed: %s\n",
    339                 argv0, sa.sun_path, strerror(errno));
    340         exit(1);
    341     }
    342 
    343     sock_namelen = strlen(sa.sun_path) + 1 + sizeof (sa.sun_family);
    344     if (bind(sock, (struct sockaddr *) &sa, sock_namelen) == -1) {
    345         fprintf(stderr, "%s: bind: %s\n", argv0, strerror(errno));
    346         exit(1);
    347     }
    348 
    349     if (listen(sock, 21) == -1) {
    350         fprintf(stderr, "%s: listen: %s\n", argv0, strerror(errno));
    351         exit(1);
    352     }
    353 
    354     return sock;
    355 }
    356 
    357 static int write_session_to_file(const char *save_path)
    358 {
    359     FILE *f = fopen(save_path, "w");
    360 
    361     debug("write session to `%s'\n", save_path);
    362 
    363     if (f == NULL) {
    364         goto error;
    365     }
    366 
    367     for (struct sfakeroot_ent *p = ents.first; p != NULL; p = p->next) {
    368         struct stat *s = &p->st;
    369         if (fprintf(f, SESSION_FILE_LINE_FMT, s->st_ino, s->st_uid, s->st_gid, s->st_mode) < 0) {
    370             goto error;
    371         }
    372         debug(SESSION_FILE_LINE_FMT, s->st_ino, s->st_uid, s->st_gid, s->st_mode);
    373     }
    374 
    375     if (fclose(f) != 0) {
    376         goto error;
    377     }
    378 
    379     return 0;
    380 
    381 error:
    382     fprintf(stderr, "%s: error saving session to `%s': %s\n",
    383             argv0, save_path, strerror(errno));
    384     return -1;
    385 }
    386 
    387 static int sfakeroot_finish_session(const struct cmdline_arguments *args)
    388 {
    389     if (args->options.session_file != NULL) {
    390         if (write_session_to_file(args->options.session_file) == -1) {
    391             return -1;
    392         }
    393     }
    394 
    395     return 0;
    396 }
    397 
    398 int estrtol(const char *s, long *out)
    399 {
    400     char *e;
    401 
    402     long n = strtol(s, &e, 10);
    403 
    404     if (*s == '\0' || *e != '\0') {
    405         fprintf(stderr, "%s: estrtol: `%s' not a number\n", argv0, s);
    406         return -1;
    407     }
    408 
    409     if ((errno == ERANGE && (n == LONG_MIN || n == LONG_MAX))) {
    410         fprintf(stderr, "%s: estrtol: `%s' out of range\n", argv0, s);
    411         return -1;
    412     }
    413 
    414     *out = n;
    415     return 0;
    416 }
    417 
    418 static int sfakeroot_load_session_from_file(const struct cmdline_arguments *args)
    419 {
    420     char *line = NULL, *session_filepath;
    421     size_t n = 0;
    422     ssize_t count;
    423     FILE *f;
    424 
    425     session_filepath = args->options.session_file;
    426     f = fopen(session_filepath, "r");
    427     if (f == NULL) {
    428         char *errfmt = "%s: couldn't open session file `%s': %s\n";
    429         debug(errfmt, argv0, session_filepath, strerror(errno));
    430         if (args->options.require_session_file) {
    431             fprintf(stderr, errfmt, argv0, session_filepath, strerror(errno));
    432             return -1;
    433         }
    434         return 0;
    435     }
    436 
    437     debug("loading session from `%s'\n", session_filepath);
    438 
    439     while ((count = getline(&line, &n, f)) != -1) {
    440         struct stat s = {0};
    441         long values[4];
    442         int i = 0;
    443         line[count - 1] = '\0'; /* strip '\n' */
    444         for (char *s = strtok(line, ","); s != NULL && i < 4; s = strtok(NULL, ","), i++) {
    445             if (estrtol(s, values + i) == -1) {
    446                 free(line);
    447                 return -1;
    448             }
    449         }
    450         s.st_ino = (ino_t) values[0];
    451         s.st_uid = (uid_t) values[1];
    452         s.st_gid = (gid_t) values[2];
    453         s.st_mode = (mode_t) values[3];
    454         addent(&ents, &s, true);
    455     }
    456 
    457     if (ferror(f)) {
    458         fprintf(stderr, "%s: error reading from `%s': %s\n",
    459                 argv0, session_filepath, strerror(errno));
    460         free(line);
    461         return -1;
    462     }
    463 
    464     free(line);
    465     return 0;
    466 }
    467 
    468 bool sfakeroot_daemon_running(void)
    469 {
    470     int sockfd = sfakeroot__session_open_internal(false);
    471     close(sockfd);
    472     return sockfd != -1;
    473 }
    474 
    475 static void sfakeroot_server(int pipewfd, const char *session_socket_path,
    476                              const struct cmdline_arguments *args)
    477 {
    478     static struct sockaddr_un sa;
    479     int listen_sock, sock;
    480     socklen_t namelen;
    481 
    482     if (args->options.session_file != NULL) {
    483         if (sfakeroot_load_session_from_file(args) == -1) {
    484             exit(1);
    485         }
    486     }
    487 
    488     listen_sock = sfakeroot_create_listener(session_socket_path);
    489 
    490     /* close write end of pipe to indicate to parent process that we
    491      * are now ready to accept incoming connections
    492      */
    493     close(pipewfd);
    494 
    495     for (;;) {
    496         struct sfakeroot_msg m;
    497         debug("waiting for connection...\n");
    498         debug("%d\n", getpid());
    499         debug("getenv(LD_PRELOAD): ");
    500 
    501         sock = accept(listen_sock, (struct sockaddr *) &sa, &namelen);
    502         if (sock == -1) {
    503             fprintf(stderr, "%s: accept: %s\n", argv0, strerror(errno));
    504             exit(1);
    505         }
    506         debug("connection accepted\n");
    507         debug("waiting for message\n");
    508         if (sfakeroot_recvmsg(sock, &m) == -1) {
    509             fprintf(stderr, "eof?\n");
    510             continue;
    511         }
    512 
    513         syslog(LOG_WARNING, "message type: %u\n", m.type);
    514         switch (m.type) {
    515             case SFAKEROOT_MSG_FSTAT:
    516             case SFAKEROOT_MSG_LSTAT:
    517             case SFAKEROOT_MSG_STAT:
    518             case SFAKEROOT_MSG_FSTATAT:
    519             case SFAKEROOT_MSG_STATX:
    520                 syslog(LOG_WARNING, "doing the fake stat\n");
    521                 handle_stat(&m);
    522                 break;
    523             case SFAKEROOT_MSG_CHOWN:
    524             case SFAKEROOT_MSG_LCHOWN:
    525             case SFAKEROOT_MSG_CHMOD:
    526             // TODO: fchmod
    527             // TODO: fchmodat
    528             case SFAKEROOT_MSG_FCHOWN:
    529             case SFAKEROOT_MSG_FCHOWNAT:
    530                 handle_perms_change(&m);
    531                 break;
    532             case SFAKEROOT_MSG_FINISH:
    533                 if (sfakeroot_finish_session(args) == -1) {
    534                     exit(1);
    535                 }
    536                 exit(0);
    537         }
    538         if (sfakeroot_sendmsg(sock, &m, NULL, 0) == -1) {
    539             exit(1);
    540         }
    541         close(sock);
    542     }
    543 }
    544 
    545 static int sfakeroot_daemon(const char *session_socket_path,
    546                             const struct cmdline_arguments *args)
    547 {
    548     int pipefds[2];
    549     char buf[1];
    550     pid_t pid;
    551     int status;
    552 
    553     if (pipe(pipefds) == -1) {
    554         fprintf(stderr, "%s: pipe: %s\n", argv0, strerror(errno));
    555         return -1;
    556     }
    557 
    558     switch ((pid = fork())) {
    559         case 0:
    560             /* child */
    561             close(pipefds[0]); /* close read end */
    562             debug("starting daemon...\n");
    563             if (daemon(1, 1) == -1) {
    564                 fprintf(stderr, "%s: failed to daemonise: %s\n",
    565                         argv0, strerror(errno));
    566                 exit(1);
    567             }
    568             sfakeroot_server(pipefds[1], session_socket_path, args);
    569             break;
    570         case -1:
    571             fprintf(stderr, "%s: fork: %s\n", argv0, strerror(errno));
    572             return -1;
    573         default:
    574             /* parent */
    575             close(pipefds[1]); /* close write end */
    576             /* This read will block until the child process signals it
    577              * is ready, i.e. it's ready to accept connections from us.
    578              */
    579             if (read(pipefds[0], buf, sizeof (buf)) == -1) {
    580                 fprintf(stderr, "%s: error reading from pipe: %s\n",
    581                         argv0, strerror(errno));
    582                 return -1;
    583             }
    584             if (waitpid(pid, &status, 0) == -1) {
    585                 fprintf(stderr, "%s: waitpid: %s\n", argv0, strerror(errno));
    586                 return -1;
    587             }
    588             if (!(WIFEXITED(status) && WEXITSTATUS(status) == 0)) {
    589                 fprintf(stderr, "%s: sfakeroot daemon unexpected condition\n",
    590                         argv0);
    591                 return -1;
    592             }
    593     }
    594 
    595     return 0;
    596 }
    597 
    598 int sfakeroot_session_open(void);
    599 
    600 static int setpreload(void)
    601 {
    602     char *wd = SFAKEROOT_LIBDIR, *path = NULL;
    603     size_t len;
    604 
    605     len = snprintf(NULL, 0, "%s/%s", wd, SONAME);
    606     path = malloc(len + 1);
    607     if (path == NULL) {
    608         fprintf(stderr, "%s: malloc: %s\n", argv0, strerror(errno));
    609         return -1;
    610     }
    611     snprintf(path, len + 1, "%s/%s", wd, SONAME);
    612 
    613     if (setenv("LD_PRELOAD", path, 1) == -1) {
    614         fprintf(stderr, "%s: setenv: %s\n", argv0, strerror(errno));
    615         free(path);
    616         return -1;
    617     }
    618     free(path);
    619     return 0;
    620 }
    621 
    622 static void usage(void)
    623 {
    624     fprintf(stderr, "usage: %s [-Ff file]\n", argv0);
    625 }
    626 
    627 static void parse_options(int argc, char *argv[], struct cmdline_arguments *args)
    628 {
    629     int ch;
    630     extern int optind;
    631     extern char *optarg;
    632 
    633     while ((ch = getopt(argc, argv, "F:f:")) != -1) {
    634         switch (ch) {
    635             case 'F':
    636                 args->options.require_session_file = true;
    637                 /* fallthrough */
    638             case 'f':
    639                 args->options.session_file = optarg;
    640                 break;
    641             default:
    642                 usage();
    643                 exit(1);
    644         }
    645     }
    646 
    647     args->args = argv + optind;
    648     args->args_len = argc - optind;
    649 }
    650 
    651 int main(int argc, char *argv[])
    652 {
    653     int sockfd, status, exit_status, len;
    654     pid_t pid;
    655     struct sfakeroot_msg m;
    656     char *sargv[] = {FALLBACK_SHELL, NULL}, *shell, **exec_argvp;
    657     char tempdir_path[8192] = "/tmp/sfakeroot.XXXXXXXXXX";
    658     char session_socket_path[8192];
    659     static struct cmdline_arguments args;
    660 
    661     argv0 = argv[0];
    662     parse_options(argc, argv, &args);
    663 
    664     if ((shell = getenv("SHELL")) != NULL) {
    665         sargv[0] = shell;
    666     }
    667 
    668     if (mkdtemp(tempdir_path) == NULL) {
    669         fprintf(stderr, "%s: mkdtemp: %s\n", argv0, strerror(errno));
    670         exit_status = 1;
    671         goto cleanup;
    672     }
    673 
    674     len = snprintf(session_socket_path, sizeof (session_socket_path), "%s/%s",
    675                    tempdir_path, "uds.sock");
    676     if (len == -1) {
    677         fprintf(stderr, "%s: snprintf\n", argv0);
    678         exit_status = 1;
    679         goto cleanup;
    680     }
    681     else if ((size_t) len > sizeof (session_socket_path)) {
    682         fprintf(stderr, "%s: path too long\n", argv0);
    683         exit_status = 1;
    684         goto cleanup;
    685     }
    686     if (setenv("SFAKEROOT_SOCKET_PATH", session_socket_path, 1) == -1) {
    687         fprintf(stderr, "%s: setenv: %s\n", argv0, strerror(errno));
    688         exit_status = 1;
    689         goto cleanup;
    690     }
    691     if (!sfakeroot_daemon_running()) {
    692         if (sfakeroot_daemon(session_socket_path, &args) == -1) {
    693             exit_status = 1;
    694             goto cleanup;
    695         }
    696     }
    697     exec_argvp = args.args_len > 0 ? args.args : sargv;
    698     switch (pid = fork()) {
    699         case 0:
    700             // This child process is going to be our session
    701             // i.e. shell or whatever program we were asked to run
    702             // we need to set LD_PRELOAD for this process so that
    703             // we can intercept the 'stat' calls and redirect them
    704             // to the sfakeroot daemon.
    705             if (setpreload() == -1) {
    706                 exit_status = 1;
    707                 goto cleanup;
    708             }
    709             execvp(exec_argvp[0], exec_argvp);
    710             fprintf(stderr, "%s: exec `%s': %s\n",
    711                     argv0, exec_argvp[0], strerror(errno));
    712             switch (errno) {
    713                 case EACCES:
    714                     exit_status = 126;
    715                     goto cleanup;
    716                 case ENOENT:
    717                     exit_status = 127;
    718                     goto cleanup;
    719                 default:
    720                     exit_status = 1;
    721                     goto cleanup;
    722             }
    723         case -1:
    724             fprintf(stderr, "%s: fork: %s\n", argv0, strerror(errno));
    725             exit_status = 1;
    726             goto cleanup;
    727         default:
    728             waitpid(pid, &status, 0);
    729             debug("instructing daemon to finish...\n");
    730             m.type = SFAKEROOT_MSG_FINISH;
    731             sockfd = sfakeroot_session_open();
    732             if (sockfd == -1) {
    733                 fprintf(stderr, "%s: failed to open session\n", argv0);
    734                 exit_status = 1;
    735                 goto cleanup;
    736             }
    737             if (sfakeroot_sendmsg(sockfd, &m, NULL, 0) == -1) {
    738                 exit_status = 1;
    739                 goto cleanup;
    740             }
    741             close(sockfd);
    742             if (WIFEXITED(status) && WEXITSTATUS(status) != 0) {
    743                 exit_status = WEXITSTATUS(status);
    744                 goto cleanup;
    745             }
    746             if (WIFSIGNALED(status)) {
    747                 exit_status = 127 + WTERMSIG(status);
    748                 goto cleanup;
    749             }
    750             else if (!(WIFEXITED(status) && WEXITSTATUS(status) == 0)) {
    751                 exit_status = 1;
    752                 goto cleanup;
    753             }
    754             break;
    755     }
    756 
    757     exit_status = 0;
    758 
    759 cleanup:
    760     unlink(session_socket_path);
    761     rmdir(tempdir_path);
    762     return exit_status;
    763 }