Here is the control flow of unprivileged hasher-priv client (euid=caller_uid):
==============================================================================
+ sanitize file descriptors
  + set safe umask
  + ensure 0,1,2 are valid file descriptors
  + close all the rest
+ parse command line arguments
+ try to connect to the session server by SOCKETDIR/caller_uid:caller_num socket
+ if failed,
  + connect to the common server by SOCKETDIR/daemon socket
  + wait for the creation of a session server
  + close socket
  + connect to the session server by SOCKETDIR/caller_uid:caller_num socket
+ send a command to start a new job
  + receive a result code from the server
+ send current stdin, stdout, and stderr to the server
  + receive a result code from the server
+ send the job arguments if any
  + receive a result code from the server
+ if the job is a chrootuid,
  + send environment variables
    + receive a result code from the server
  + send chroot fd to the server
    + receive a result code from the server
  + send the current personality to the server
    + receive a result code from the server
+ send a command to run the job
  + receive a job result code from the server

Here is the control flow of the privileged hasher-privd server (euid=root):
===========================================================================
+ sanitize file descriptors
  + set safe umask
  + ensure 0,1,2 are valid file descriptors
  + close all the rest
+ set PR_SET_NO_NEW_PRIVS flag
+ parse command line arguments
  + handle -h and --help options
+ load the server configuration
  + read configuration from /etc/hasher-priv/daemon.conf
  + change the current working directory to "/"
+ check the pidfile, abort if a server is already running
+ if requested to become a daemon, daemonize
  + change the current working directory to "/" again
  + redirect stdin, stdout, and stderr to /dev/null
+ initialize the logger
+ write the pidfile
+ setup a listening socket at SOCKETDIR/daemon
+ create a file descriptor for accepting certain signals
  + block these signals
+ create a file descriptor for polling
  + prepare for polling descriptors
+ ignore SIGPIPE
+ enter the polling loop
  + handle all received signals if any
    + certain signals terminate the polling loop
  + handle a new connection if any
    + accept a new connection
    + set the receiving timeout on the accepted socket
    + obtain a request to open a session
    + get connection credentials
      + check that the uid and the gid are valid
    + notify the client if the session server is already running
    + otherwise fork off a session server for the caller
      + the new session server initializes itself
        and notifies the caller when it's ready to handle connections
    + close the caller connection
+ remove pidfile
+ exit process

Here is the control flow of the privileged session server (euid=root):
=======================================================================
+ initialize the data related to the caller
  + caller_uid and caller_gid are initialized by the data obtained
    from the socket credentials
    + caller_uid and caller_gid must be valid
  + caller_user is initialized from getpwuid(caller_uid)->pw_name
    + must be true: caller_uid == pw->pw_uid
    + must be true: caller_gid == pw->pw_gid
  + caller_home is initialized from getpwuid(caller_uid)->pw_dir
    + caller_user's home directory must exist
+ load configuration
  + safe chdir to /etc/hasher-priv
  + safe load "system" file
    + config format is sequence of name=value lines
    + valid names are:
      user1
      user2
      prefix
      umask
      nice
      nproc
      allow_ttydev
      allowed_devices
      allowed_mountpoints
      rlimit_(hard|soft)_*
      wlimit_(time_elapsed|time_idle|bytes_written)
  + safe chdir to "user.d"
  + safe load caller_uid file, fallback to caller_user file
    + change_user1 and change_user2 should be initialized here
  + if caller_num is defined
    + discard change_user1 and change_user2
    + safe load caller_uid:caller_num file, fallback to caller_user:caller_num file
      + change_user1 and change_user2 should be initialized here
  + change_uid1 and change_gid1 initialized from change_user1
  + change_uid2 and change_gid2 initialized from change_user2
+ create a listening socket at SOCKETDIR/caller_uid:caller_num
+ create a file descriptor for accepting certain signals
  + block these signals
+ create a file descriptor for polling
  + prepare for polling descriptors
+ notify the client that the session server is ready
+ enter the polling loop
  + terminate the polling loop in case of timeout
  + terminate the polling loop in case of an event in the parent pipe
  + handle all received signals if any
    + certain signals terminate the polling loop
  + handle a new connection if any
    + accept a new connection
    + set the receiving timeout on the accepted socket
    + check connection credentials
    + handle the job request
      + reset the timeout counter if the job request is valid
+ exit process

Here is the control flow of the privileged job handler (euid=root):
====================================================================
+ fork off a process to handle the job request
+ in the parent,
  + wait for the child process termination
  + return the child process exit code
+ in the child,
  + enter the command loop
    + receive the job command header
      + reject repeated commands
      + supported commands:
        type, fds, arguments, environ, chroot_fd, personality, run
    + receive the data according to the job command header
      + validate the number of received arguments according to the job type
    + if the command is to run,
      + check that arguments were received if the job type requires arguments
      + check that a chroot descriptor was received if the job type requires it
      + run the job and terminate the job command loop
  + exit process

Here is the control flow of the privileged job runner (euid=root):
==================================================================
+ fork off a process to run the job
  + in the parent,
    + if the job is not a chrootuid,
      + wait for the child process termination
      + otherwise wait until the executor sanitized its descriptors
    + terminate the job command loop and exit
+ if the job is a chrootuid, join the cgroup of the client
+ fork off a child process
+ in the parent,
  + clear the dumpable flag explicitly
  + clear the supplementary group access list
  + setgid/setuid to the caller user
  + create a file descriptor for accepting certain signals
    + block these signals
  + create a file descriptor for polling
    + prepare for polling the client connection
  + enter the polling loop
    + if client has disconnected
      + terminate the executor
      + wait for the completion of the child process
      + terminate the polling loop
    + handle all received signals if any
      + if certain signals has been received
        + terminate the executor
        + wait for the completion of the child process
        + notify the client
        + terminate the polling loop
      + if SIGCHLD has been received
        + wait for the completion of the child process
        + report its exit status to the client
        + terminate the polling loop
  + exit process
+ in the child,
  + unblock all signals
  + replace stdin, stdout and stderr with those that were received
  + re-initialize the logger
  + initialize chroot_fd
  + sanitize file descriptors
    + set safe umask
    + ensure 0,1,2 are valid file descriptors
    + close all the rest
  + drop all environment variables
  + read work limit hints from the client's environment variables
  + execute the chosen job type
    + getconf: print the name of the last loaded config file
    + getugid1: print change_uid1:change_gid1 pair
    + getugid2: print change_uid2:change_gid2 pair
    + killuid
      + check for valid uids specified
      + set RLIMIT_NPROC to RLIM_INFINITY
      + clear the dumpable flag explicitly
      + setuid to the specified uid pair
      + kill (-1, SIGKILL)
      + purge all SYSV IPC objects belonging to the specified uid pair
    + chrootuid1/chrootuid2
      + fork to kill the processes that were left from the previous chrootuid call
        + in the parent:
          + wait for the exit status
        + in the child:
          + killuid
      + obtain the supplementary group access list for the target user
        + initialize the supplementary group access list for the target user
        + save the supplementary group access list
        + clear the supplementary group access list
      + check and setup namespaces
        + check that the server and the client belong to the same namespaces
        + enter the client's mount, ipc, uts, and network namespaces
          if they differ from the server's namespaces
      + setup mount namespace
        + safe fchdir to chroot_fd
        + unshare mount namespace
        + reopen chroot_fd directory in the new mount namespace
        + safe fchdir to chroot_fd
      + setup mounts and devices
        + safe fchdir to chroot_fd
        + safe chdir to dev
        + mount dev
        + create devices available to all users: null, zero, full, random, urandom
        + if makedev_console environment variable is true,
          create devices available to root only: console, tty0, fb0
        + if /dev/pts is going to be mounted, create devices: tty, ptmx
        + mount /dev/shm
        + mount all mountpoints specified by requested_mountpoints environment variable
      + safe fchdir to chroot_fd
      + sanitize file descriptors again
      + if use_pty is disabled, create a pipe to handle child's stdout and stderr
      + if X11 forwarding is requested, create a socketpair and
        open /tmp/.X11-unix directory readonly for later use with fchdir()
      + unless share_ipc is enabled, isolate System V IPC namespace
      + unless share_uts is enabled, unshare UTS namespace
      + unless share_network is enabled,
        if X11 forwarding to a tcp address was not requested, unshare the network
      + create a pty:
        + temporarily switch to called_uid:caller_gid
        + open /dev/ptmx
        + fetch the pts number
        + unlock the pts pair
        + open the pts slave
        + switch uid:gid back
      + chroot to "."
      + create another pty if possible:
        + temporarily switch to called_uid:caller_gid
        + safely chdir to "dev"
        + if "pts/ptmx" is available with the right permissions:
          + replace "ptmx" with a symlink to "pts/ptmx"
          + open "ptmx"
          + fetch the pts number
          + unlock the pts pair
          + open the pts slave
        + chdir back to "/"
        + switch uid:gid back
        + if another pty was created, close the pts pair that was opened earlier
      + set rlimits
      + set close-on-exec flag on all non-standard descriptors
      + fork
        + in the parent:
          + clear the dumpable flag explicitly
          + setgid/setuid to the caller user
          + install the CHLD signal handler
          + unblock the master pty and pipe descriptors
          + if use_pty is enabled, initialize the tty and install WINCH signal handler
          + listen to "/dev/log"
          + while the work limits are not exceeded, handle the child's input/output
          + close the master pty descriptor, thus sending HUP to the child session
          + wait for the child process termination
          + remove the CHLD signal handler
          + return the child process exit code
        + in the child:
          + unless share_network is enabled,
            if X11 forwarding to a tcp address was requested, unshare the network
          + clear the dumpable flag explicitly
          + set the list of supplementary access groups to the saved one
          + setgid/setuid to the specified user
          + set the personality received from the client
          + setsid
          + change the controlling terminal to the pty
          + redirect stdin if required, either to an empty pipe or to the pty
          + redirect stdout and stderr either to the pipe or to the pty
          + set nice
          + reduce CPU affinity to nproc randomly shuffled bits
          + if X11 forwarding is requested,
            + add an X11 auth entry using xauth utility
            + create and bind a unix socket for X11 forwarding
            + send the listening descriptor and the fake auth data to the parent
          + set umask
          + execute the specified program
