text: /* Copyright © Triad National Security, LLC, and others. */
  
  /* Function prefixes:
  
     fuse_     libfuse; docs: https://libfuse.github.io/doxygen/globals.html
     sqfs_ll_  SquashFUSE; no docs but: https://github.com/vasi/squashfuse
     sq_       Charliecloud */
  
  #define _GNU_SOURCE
  #include <dirent.h>
  #include <stdlib.h>
  #include <string.h>
  #include <sys/wait.h>
  #include <unistd.h>
  
  // SquashFUSE has a bug [1] where ll.h includes SquashFUSE's own config.h.
  // This clashes with our own config.h, as well as the system headers because
  // it defines _POSIX_C_SOURCE. By defining SQFS_CONFIG_H, SquashFUSE's
  // config.h skips itself.
  // [1]: https://github.com/vasi/squashfuse/issues/65
  #define SQFS_CONFIG_H
  // But then FUSE_USE_VERSION isn't defined, which makes other parts of ll.h
  // puke. Looking at their code, it seems the only values used are 32 (for
  // libfuse3) and 26 (for libfuse2), so we can just blindly define it.
  #define FUSE_USE_VERSION 32
  // SquashFUSE redefines __le16 unless HAVE_LINUX_TYPES_LE16 is defined. We are
  // assuming it is defined in <linux/types.h> on your machine.
  #define HAVE_LINUX_TYPES_LE16
  // Now we can include ll.h.
  #include <squashfuse/ll.h>
  
  #include "config.h"
  #include "ch_core.h"
  #include "ch_fuse.h"
  #include "ch_misc.h"
  
  
  /** Types **/
  
  /* A SquashFUSE mount. SquashFUSE allocates ll for us but not chan; use
     pointers for both for consistency. */
  struct squash {
     char *mountpt;       // path to mount point
     sqfs_ll_chan *chan;  // FUSE channel associated with SquashFUSE mount
     sqfs_ll *ll;         // SquashFUSE low-level data structure
  };
  
  
  /** Constants **/
  
  /* This mapping tells libfuse what functions implement which FUSE operations.
     It is passed to sqfs_ll_mount(). Why it is not internal to SquashFUSE I
     have no idea. */
  struct fuse_lowlevel_ops OPS = {
      .getattr    = &sqfs_ll_op_getattr,
      .opendir    = &sqfs_ll_op_opendir,
      .releasedir = &sqfs_ll_op_releasedir,
      .readdir    = &sqfs_ll_op_readdir,
      .lookup     = &sqfs_ll_op_lookup,
      .open       = &sqfs_ll_op_open,
      .create     = &sqfs_ll_op_create,
      .release    = &sqfs_ll_op_release,
      .read       = &sqfs_ll_op_read,
      .readlink   = &sqfs_ll_op_readlink,
      .listxattr  = &sqfs_ll_op_listxattr,
      .getxattr   = &sqfs_ll_op_getxattr,
      .forget     = &sqfs_ll_op_forget,
      .statfs     = &stfs_ll_op_statfs
  };
  
  
  /** Global variables **/
  
  /* SquashFUSE mount. Initialized in sq_mount() and then used in most of the
     other functions in this file. It's a global because the signal handler
     needs access to it. */
  struct squash sq;
  
  /* True if exit request signal handler received SIGCHLD. */
  volatile bool sigchld_received;
  
  /* True if any exit request signal has been received. */
  volatile bool loop_terminating = false;
  
  
  /** Function prototypes (private) **/
  
  void sq_done_request(int signum);
  int sq_loop();
  void sq_mount(const char *img_path, char *mountpt);
  
  
  /** Functions **/
  
  /* Signal handler to end the FUSE loop. This simply requests FUSE to end its
     loop, causing fuse_session_loop() to exit. */
  void sq_done_request(int signum)
  {
     if (!loop_terminating) {  // only act on first signal
        loop_terminating = true;
        sigchld_received = (signum == SIGCHLD);
        fuse_session_exit(sq.chan->session);
     }
  }
  
  /* Mount SquashFS archive c->img_path on directory c->newroot. If the latter
     is NULL, then mkdir(2) the default mount point and assign its path to
     c->newroot. After mounting, fork; the child returns immediately while the
     parent runs the FUSE loop until the child exits and then exits itself,
     with the same exit code as the child (unless something else went wrong). */
  void sq_fork(struct container *c)
  {
     pid_t pid_child;
     struct stat st;
  
     // Default mount point?
     if (c->newroot == NULL) {
        char *subdir;
        char *username = getenv("USER");
        Te (username != NULL, "$USER not set");
        T_ (asprintf(&subdir, "/%s.ch/mnt", username) > 0);
        c->newroot = cat("/var/tmp", subdir);
        INFO("using default mount point: %s", c->newroot);
        mkdirs("/var/tmp", subdir, NULL, 0);
     }
  
     // Verify mount point exists and is a directory. (SquashFS file path
     // already checked in img_type_get().)
     Zf (stat(c->newroot, &st), "can't stat mount point: %s", c->newroot);
     Te (S_ISDIR(st.st_mode), "not a directory: %s", c->newroot);
  
     // Mount SquashFS.
     sq_mount(c->img_path, c->newroot);
  
     // Now that the filesystem is mounted, we can fork without race condition.
     // The child returns to caller and runs the user command. When that exits,
     // the parent gets SIGCHLD.
     pid_child = fork();
     Tf (pid_child >= 0, "can't fork");
     if (pid_child > 0)  // parent (child does nothing here)
        exit(sq_loop());
  }
  
  /* Run the squash loop to completion and return the exit code of the user
     command. Warning: This sets up but does not restore signal handlers. */
  int sq_loop(void)
  {
     struct sigaction fin, ign;
     int looped, exit_code, child_status;
     // Set up signal handlers. Avoid fuse_set_signal_handlers() because we need
     // to catch a different set of signals, letting some be handled by the user
     // command [1]. Use sigaction(2) instead of signal(2) because the latter's
     // man page [2] says “avoid its use” and there are reports of bad
     // interactions with libfuse [3].
     //
     // [1]: https://unix.stackexchange.com/questions/176235
     // [2]: https://man7.org/linux/man-pages/man2/signal.2.html
     // [3]: https://stackoverflow.com/a/8918597
     fin.sa_handler = sq_done_request;
     Z_ (sigemptyset(&fin.sa_mask));  // block no other signals during handling
     fin.sa_flags = SA_NOCLDSTOP;     // only SIGCHLD on child exit
     ign.sa_handler = SIG_IGN;
     Z_ (sigaction(SIGCHLD, &fin, NULL));  // user command exits
     Z_ (sigaction(SIGHUP,  &ign, NULL));  // terminal/session terminated
     Z_ (sigaction(SIGINT,  &ign, NULL));  // Control-C
     Z_ (sigaction(SIGPIPE, &ign, NULL));  // broken pipe; we don't use pipes
     Z_ (sigaction(SIGTERM, &fin, NULL));  // somebody asked us to exit
  
     // Run the FUSE loop, which services FUSE requests until sq_done_request()
     // is invoked by a signal and tells it to stop, or someone unmounts the
     // filesystem externally with e.g. fusermount(1). Because we don't use
     // fuse_set_signal_handlers(), the return value doesn't contain the signal
     // number that ended the loop, contrary to the documentation.
     //
     // FIXME: this is single-threaded; see issue #1157.
     looped = fuse_session_loop(sq.chan->session);
     if (looped < 0) {
        errno = -looped;  // restore encoded errno so our logging finds it
        Tf (0, "FUSE session failed");
     }
     INFO("FUSE loop terminated successfully");
  
     // Clean up zombie child if exit signal was SIGCHLD.
     if (!sigchld_received)
        exit_code = 0;
     else {
        Tf (wait(&child_status) >= 0, "can't wait for child");
        Te (WIFEXITED(child_status), "child terminated abnormally");
        exit_code = WEXITSTATUS(child_status);
        INFO("child terminated normally with exit code %d", exit_code);
     }
  
     // Clean up SquashFS mount. These functions have no error reporting.
     INFO("unmounting: %s", sq.mountpt);
     sqfs_ll_destroy(sq.ll);
     sqfs_ll_unmount(sq.chan, sq.mountpt);
  
     INFO("FUSE loop done");
     return exit_code;
  }
  
  /* Mount the SquashFS img_path at mountpt. Exit on any errors. */
  void sq_mount(const char *img_path, char *mountpt)
  {
     // SquashFUSE mount takes basically a command line rather than having a
     // standard library API. It's unclear to me where this command line is
     // documented, but the libfuse docs [1] suggest mount(8).
     // [1]: https://libfuse.github.io/doxygen/fuse-3_810_83_2include_2fuse_8h.html#ad866b0fd4d81bdbf3e737f7273ba4520
     char *mount_argv[] = {"WEIRDAL", "-d"};
     int mount_argc = (verbose > 3) ? 2 : 1;  // include -d if high verbosity
     struct fuse_args mount_args = FUSE_ARGS_INIT(mount_argc, mount_argv);
  
     sq.mountpt = mountpt;
     T_ (sq.chan = malloc(sizeof(sqfs_ll_chan)));
  
     sq.ll = sqfs_ll_open(img_path, 0);
     Te (sq.ll != NULL, "can't open SquashFS: %s; try ch-run -vv?", img_path);
  
     if (sqfs_ll_mount(sq.chan, sq.mountpt, &mount_args,
                       &OPS, sizeof(OPS), sq.ll) != SQFS_OK) {
        // We get back only SQFS_OK or SQFS_ERR, with no further detail. Looking
        // at the source code [1], the latter says either fuse_session_new() or
        // fuse_session_mount() failed, but we can't tell which.
        // [1]: https://github.com/vasi/squashfuse/blob/74f4fe8/ll.c#L399
        FATAL("unknown FUSE error mounting SquashFS; try ch-run -vv?");
     }
  }
  