Provided by: charliecloud-runtime_0.31-1_amd64 bug

NAME

       ch-run - Run a command in a Charliecloud container

SYNOPSIS

          $ ch-run [OPTION...] IMAGE -- CMD [ARG...]

DESCRIPTION

       Run  command  CMD in a fully unprivileged Charliecloud container using the image specified
       by IMAGE, which can be: (1) a path to a directory, (2) the name of an  image  in  ch-image
       storage  (e.g.   example.com:5050/foo)  or,  if  the proper support is enabled, a SquashFS
       archive. ch-run does not use any setuid or setcap  helpers,  even  for  mounting  SquashFS
       images with FUSE.

          -b, --bind=SRC[:DST]
                 Bind-mount  SRC at guest DST. The default destination if not specified is to use
                 the same path as the host; i.e., the default is --bind=SRC:SRC. Can be repeated.

                 If --write is given and DST does not exist, it  will  be  created  as  an  empty
                 directory.  However,  DST  must  be entirely within the image itself; DST cannot
                 enter a previous bind  mount.   For  example,  --bind  /foo:/tmp/foo  will  fail
                 because  /tmp  is  shared with the host via bind-mount (unless $TMPDIR is set to
                 something else or --private-tmp is given).

                 Most images do have  ten  directories  /mnt/[0-9]  already  available  as  mount
                 points.

                 Symlinks  in  DST are followed, and absolute links can have surprising behavior.
                 Bind-mounting happens  after  namespace  setup  but  before  pivoting  into  the
                 container  image,  so absolute links use the host root. For example, suppose the
                 image has a symlink /foo -> /mnt.  Then, --bind=/bar:/foo will bind-mount on the
                 host’s  /mnt,  which  is inaccessible on the host because namespaces are already
                 set up and also inaccessible in the container because of  the  subsequent  pivot
                 into  the  image.  Currently, this problem is only detected when DST needs to be
                 created: ch-run will refuse to follow absolute symlinks in this case,  to  avoid
                 directory creation surprises.

          -c, --cd=DIR
                 Initial working directory in container.

          --ch-ssh
                 Bind ch-ssh(1) into container at /usr/bin/ch-ssh.

          --env-no-expand
                 don’t expand variables when using --set-env

          -g, --gid=GID
                 Run as group GID within container.

          --home Bind-mount  your host home directory (i.e., $HOME) at guest /home/$USER. This is
                 accomplished by over-mounting a new  tmpfs  at  /home,  which  hides  any  image
                 content  under  that  path.  By default, neither of these things happens and the
                 image’s /home is exposed unaltered.

          -j, --join
                 Use the same container (namespaces) as peer ch-run invocations.

          --join-pid=PID
                 Join the namespaces of an existing process.

          --join-ct=N
                 Number of ch-run peers (implies --join; default: see below).

          --join-tag=TAG
                 Label for ch-run peer group (implies --join; default: see below).

          -m, --mount=DIR
                 Use DIR for  the  SquashFS  mount  point,  which  must  already  exist.  If  not
                 specified,  the  default  is  /var/tmp/$USER.ch/mnt,  which  will  be created if
                 needed.

          --no-passwd
                 By default, temporary /etc/passwd and /etc/group files are created according  to
                 the  UID  and  GID  maps  for the container and bind-mounted into it. If this is
                 specified, no such temporary  files  are  created  and  the  image’s  files  are
                 exposed.

          -s, --storage DIR
                 Set the storage directory. Equivalent to the same option for ch-image(1).

          -t, --private-tmp
                 By  default,  the  host’s  /tmp (or $TMPDIR if set) is bind-mounted at container
                 /tmp. If this is specified, a new tmpfs  is  mounted  on  the  container’s  /tmp
                 instead.

          --set-env, --set-env=FILE, --set-env=VAR=VALUE
                 Set environment variable(s). With:

                     • no  argument: as listed in file /ch/environment within the image. It is an
                       error if the file does not exist or  cannot  be  read.   (Note  that  with
                       SquashFS  images,  it  is not currently possible to use other files within
                       the image.)

                     • FILE (i.e., no equals in argument): as specified  in  file  at  host  path
                       FILE. Again, it is an error if the file cannot be read.

                     • NAME=VALUE (i.e., equals sign in argument): set variable NAME to VALUE.

                 See below for details on how environment variables work in ch-run.

          -u, --uid=UID
                 Run as user UID within container.

          --unsafe
                 Enable various unsafe behavior. For internal use only. Seriously, stay away from
                 this option.

          --unset-env=GLOB
                 Unset environment variables whose names match GLOB.

          -v, --verbose
                 Be more verbose (can be repeated).

          -w, --write
                 Mount image read-write (by default, the image is mounted read-only).

          -?, --help
                 Print help and exit.

          --usage
                 Print a short usage message and exit.

          -V, --version
                 Print version and exit.

       Note: Because ch-run is fully unprivileged, it is not possible to  change  UIDs  and  GIDs
       within  the container (the relevant system calls fail). In particular, setuid, setgid, and
       setcap executables do not work. As a precaution, ch-run  calls  prctl(PR_SET_NO_NEW_PRIVS,
       1)  to  disable these executables within the container. This does not reduce functionality
       but is a “belt and suspenders” precaution to reduce the  attack  surface  should  bugs  in
       these system calls or elsewhere arise.

IMAGE FORMAT

       ch-run supports two different image formats.

       The  first  is  a  simple  directory  that  contains  a Linux filesystem tree. This can be
       accomplished by:

       • ch-convert directly from ch-image or another builder to a directory.

       • Charliecloud’s tarball workflow: build or pull the image, ch-convert it  to  a  tarball,
         transfer the tarball to the target system, then ch-convert the tarball to a directory.

       • Manually  mount a SquashFS image, e.g. with squashfuse(1) and then un-mount it after run
         with fusermount -u.

       • Any other workflow that produces an appropriate directory tree.

       The second is a SquashFS image archive mounted internally by  ch-run,  available  if  it’s
       linked  with  the  optional  libsquashfuse_ll  shared  library.  ch-run  mounts  the image
       filesystem, services all FUSE requests, and unmounts it, all within  ch-run.  See  --mount
       above to set the mount point location.

       Like  other  FUSE  implementations, Charliecloud calls the fusermount3(1) utility to mount
       the SquashFS filesystem. However, this executable does not need  to  be  installed  setuid
       root, and in fact ch-run actively suppresses its setuid bit if set (using prctl(2)).

       Prior  versions  of  Charliecloud  provided  wrappers for the squashfuse and squashfuse_ll
       SquashFS mount commands and fusermount -u unmount command. We  removed  these  because  we
       concluded they had minimal value-add over the standard, unwrapped commands.

       WARNING:
          Currently,  Charliecloud  unmounts  the  SquashFS  filesystem  when  user command CMD’s
          process exits. It does not monitor any of its child processes. Therefore, if  the  user
          command  spawns  child processes and then exits before them (e.g., some daemons), those
          children will have the  image  unmounted  from  underneath  them.  In  this  case,  the
          workaround  is  to  mount/unmount  using  external  tools.  We  expect  to  remove this
          limitation in a future version.

HOST FILES AND DIRECTORIES AVAILABLE IN CONTAINER VIA BIND MOUNTS

       In addition to any directories specified by the user with --bind, ch-run has standard host
       files and directories that are bind-mounted in as well.

       The  following  host  files  and  directories are bind-mounted at the same location in the
       container. These give access to the host’s devices and various kernel facilities.  (Recall
       that Charliecloud provides minimal isolation and containerized processes are mostly normal
       unprivileged processes.) They cannot be disabled and are required; i.e., they  must  exist
       both on host and within the image.

          • /dev/proc/sys

       Optional;  bind-mounted  only  if  path  exists on both host and within the image, without
       error or warning if not.

          • /etc/hosts and /etc/resolv.conf.  Because  Charliecloud  containers  share  the  host
            network namespace, they need the same hostname resolution configuration.

          • /etc/machine-id.  Provides  a  unique  ID  for the OS installation; matching the host
            works  for  most  situations.  Needed  to  support  D-Bus,  some  software  licensing
            situations, and likely other use cases. See also issue #1050.

          • /var/lib/hugetlbfs  at  guest  /var/opt/cray/hugetlbfs, and /var/opt/cray/alps/spool.
            These support Cray MPI.

          • $PREFIX/bin/ch-ssh  at  guest  /usr/bin/ch-ssh.  SSH   wrapper   that   automatically
            containerizes after connecting.

       Additional bind mounts done by default but can be disabled; see the options above.

          • $HOME  at  /home/$USER  (and  image /home is hidden).  Makes user data and init files
            available.

          • /tmp (or $TMPDIR if set) at guest /tmp. Provides a temporary directory that  persists
            between container runs and is shared with non-containerized application components.

          • temporary  files  at /etc/passwd and /etc/group. Usernames and group names need to be
            customized for each container run.

MULTIPLE PROCESSES IN THE SAME CONTAINER WITH --JOIN

       By default, different ch-run invocations use different user and  mount  namespaces  (i.e.,
       different  containers).  While  this  has  no  impact  on  sharing  most resources between
       invocations, there are a few important exceptions.  These include:

       1. ptrace(2), used by debuggers and related tools. One can attach a debugger to  processes
          in  descendant namespaces, but not sibling namespaces.  The practical effect of this is
          that (without --join), you can’t run a command with ch-run and then attach to it with a
          debugger also run with ch-run.

       2. Cross-memory  attach  (CMA)  is  used by cooperating processes to communicate by simply
          reading and writing one another’s memory. This is also not  permitted  between  sibling
          namespaces.  This  affects  various  MPI  implementations that use CMA to pass messages
          between ranks on the same node, because it’s faster than traditional shared memory.

       --join is designed to address this by placing related ch-run commands (the  “peer  group”)
       in  the  same  container.  This  is  done by one of the peers creating the namespaces with
       unshare(2) and the others joining with setns(2).

       To do so, we need to know the number of  peers  and  a  name  for  the  group.  These  are
       specified  by  additional arguments that can (hopefully) be left at default values in most
       cases:

       • --join-ct sets the number of peers. The default  is  the  value  of  the  first  of  the
         following   environment   variables   that   is   defined:   OMPI_COMM_WORLD_LOCAL_SIZE,
         SLURM_STEP_TASKS_PER_NODE, SLURM_CPUS_ON_NODE.

       • --join-tag sets the tag that names the peer group. The default is  environment  variable
         SLURM_STEP_ID,  if  defined;  otherwise, the PID of ch-run’s parent. Tags can be re-used
         for peer groups that start at different times, i.e., once all peer ch-run have  replaced
         themselves with the user command, the tag can be re-used.

       Caveats:

       • One  cannot  currently  add peers after the fact, for example, if one decides to start a
         debugger after the fact. (This is only required for  code  with  bugs  and  is  thus  an
         unusual use case.)

       • ch-run  instances  race.  The  winner of this race sets up the namespaces, and the other
         peers use the winner to find the namespaces to join. Therefore, if the user  command  of
         the  winner  exits, any remaining peers will not be able to join the namespaces, even if
         they are still active. There is currently no general way to specify which ch-run  should
         be the winner.

       • If --join-ct is too high, the winning ch-run’s user command exits before all peers join,
         or ch-run itself crashes, IPC resources such as semaphores and  shared  memory  segments
         will be leaked. These appear as files in /dev/shm/ and can be removed with rm(1).

       • Many  of the arguments given to the race losers, such as the image path and --bind, will
         be ignored in favor of what was given to the winner.

ENVIRONMENT VARIABLES

       ch-run leaves environment variables unchanged, i.e. the host environment is passed through
       unaltered, except:

       • limited tweaks to avoid significant guest breakage;

       • user-set variables via --set-env;

       • user-unset variables via --unset-env; and

       • set CH_RUNNING.

       This section describes these features.

       The  default tweaks happen first, then --set-env and --unset-env in the order specified on
       the command line, and then CH_RUNNING. The two options can be  repeated  arbitrarily  many
       times, e.g. to add/remove multiple variable sets or add only some variables in a file.

   Default behavior
       By default, ch-run makes the following environment variable changes:

       $CH_RUNNING
              Set  to  Weird  Al  Yankovic.  While  a  process  can  figure  out  that it’s in an
              unprivileged container and what namespaces are active without this hint,  that  can
              be  messy,  and  there  is  no  way  to  tell  that  it’s  a Charliecloud container
              specifically. This variable makes such a test simple and well-defined. (Note:  This
              variable is unaffected by --unset-env.)

       $HOME  If  --home is specified, then your home directory is bind-mounted into the guest at
              /home/$USER. If you also have a different home  directory  path  on  the  host,  an
              inherited  $HOME  will  be  incorrect  inside  the  guest,  which  confuses lots of
              software, notably Spack. Thus,  with  --home,  $HOME  is  set  to  /home/$USER  (by
              default, it is unchanged.)

       $PATH  Newer  Linux  distributions replace some root-level directories, such as /bin, with
              symlinks to their counterparts in /usr.

              Some of these distributions (e.g., Fedora 24)  have  also  dropped  /bin  from  the
              default  $PATH.  This  is  a  problem when the guest OS does not have a merged /usr
              (e.g., Debian 8 “Jessie”). Thus, we add /bin to $PATH if it’s not already present.

              Further reading:

                 • The case for the /usr MergeFedoraDebian

       $TMPDIR
              Unset, because this is almost certainly a host path, and that  host  path  is  made
              available in the guest at /tmp unless --private-tmp is given.

   Setting variables with --set-env
       The  purpose  of  --set-env  is  to set environment variables within the container. Values
       given replace any already in the environment (i.e., inherited from the host shell) or  set
       by earlier --set-env. This flag takes an optional argument with two possible forms:

       1. If  the  argument  contains  an  equals  sign  (=,  ASCII 61), that sets an environment
          variable directly. For example, to set FOO to the string value bar:

             $ ch-run --set-env=FOO=bar ...

          Single straight quotes around the value (', ASCII 39) are  stripped,  though  be  aware
          that both single and double quotes are also interpreted by the shell. For example, this
          example is similar to the prior one; the double quotes are removed by the shell and the
          single quotes are removed by ch-run:

             $ ch-run --set-env="'BAZ=qux'" ...

       2. If the argument does not contain an equals sign, it is a host path to a file containing
          zero or more variables using the same syntax as  above  (except  with  no  prior  shell
          processing).  This file contains a sequence of assignments separated by newlines. Empty
          lines are ignored, and no comments are interpreted. (This syntax is designed to  accept
          the output of printenv and be easily produced by other simple mechanisms.) For example:

             $ cat /tmp/env.txt
             FOO=bar
             BAZ='qux'
             $ ch-run --set-env=/tmp/env.txt ...

          For directory images only (because the file is read before containerizing), guest paths
          can be given by prepending the image path.

       3. If there is no argument, the file /ch/environment within the image is used.  This  file
          is  commonly populated by ENV instructions in the Dockerfile. For example, equivalently
          to form 2:

             $ cat Dockerfile
             [...]
             ENV FOO=bar
             ENV BAZ=qux
             [...]
             $ ch-image build -t foo .
             $ ch-convert foo /var/tmp/foo.sqfs
             $ ch-run --set-env /var/tmp/foo.sqfs -- ...

          (Note the image path is interpreted correctly, not as the --set-env argument.)

          At present, there is no way to use files other  than  /ch/environment  within  SquashFS
          images.

       Environment  variables  are  expanded  for  values  that  look  like  search paths, unless
       --env-no-expand is given prior to --set-env. In this case, the value is a sequence of zero
       or  more  possibly-empty  items  separated  by colon (:, ASCII 58). If an item begins with
       dollar sign ($, ASCII 36), then the rest of  the  item  is  the  name  of  an  environment
       variable.  If this variable is set to a non-empty value, that value is substituted for the
       item; otherwise (i.e., the variable is unset or the empty string), the  item  is  deleted,
       including  a  delimiter  colon.  The  purpose  of  omitting  empty  expansions is to avoid
       surprising behavior such as an empty element in $PATH meaning the current directory.

       For example, to set HOSTPATH to the search path in the current shell (this is expanded  by
       ch-run, though letting the shell do it happens to be equivalent):

          $ ch-run --set-env='HOSTPATH=$PATH' ...

       To prepend /opt/bin to this current search path:

          $ ch-run --set-env='PATH=/opt/bin:$PATH' ...

       To prepend /opt/bin to the search path set by the Dockerfile, as retrieved from guest file
       /ch/environment (here we really cannot let the shell expand $PATH):

          $ ch-run --set-env --set-env='PATH=/opt/bin:$PATH' ...

       Examples of valid assignment, assuming that environment variable BAR is  set  to  bar  and
       UNSET is unset or set to the empty string:

                         ┌───────────────────┬───────┬────────────────────────┐
                         │Assignment         │ Name  │ Value                  │
                         └───────────────────┴───────┴────────────────────────┘

                         │FOO=barFOObar                    │
                         ├───────────────────┼───────┼────────────────────────┤
                         │FOO=bar=bazFOObar=baz                │
                         ├───────────────────┼───────┼────────────────────────┤
                         │FLAGS=-march=fooFLAGS-march=foo -mtune=bar  │
                         │-mtune=bar         │       │                        │
                         ├───────────────────┼───────┼────────────────────────┤
                         │FLAGS='-march=fooFLAGS-march=foo -mtune=bar  │
                         │-mtune=bar'        │       │                        │
                         ├───────────────────┼───────┼────────────────────────┤
                         │FOO=$BARFOObar                    │
                         ├───────────────────┼───────┼────────────────────────┤
                         │FOO=$BAR:bazFOObar:baz                │
                         ├───────────────────┼───────┼────────────────────────┤
                         │FOO=FOO   │ empty string           │
                         ├───────────────────┼───────┼────────────────────────┤
                         │FOO=$UNSETFOO   │ empty string           │
                         ├───────────────────┼───────┼────────────────────────┤
                         │FOO=baz:$UNSET:quxFOObaz:qux (not baz::qux) │
                         ├───────────────────┼───────┼────────────────────────┤
                         │FOO=:bar:baz::FOO:bar:baz::             │
                         ├───────────────────┼───────┼────────────────────────┤
                         │FOO=''FOO   │ empty string           │
                         ├───────────────────┼───────┼────────────────────────┤
                         │FOO=''''FOO'' (two single quotes) │
                         └───────────────────┴───────┴────────────────────────┘

       Example invalid assignments:

                                  ┌───────────┬──────────────────────┐
                                  │Assignment │ Problem              │
                                  ├───────────┼──────────────────────┤
                                  │FOO bar    │ no equals separator  │
                                  ├───────────┼──────────────────────┤
                                  │=bar       │ name cannot be empty │
                                  └───────────┴──────────────────────┘

       Example valid assignments that are probably not what you want:

                      ┌─────────────────┬───────┬───────────┬─────────────────────┐
                      │Assignment       │ Name  │ Value     │ Problem             │
                      ├─────────────────┼───────┼───────────┼─────────────────────┤
                      │FOO="bar"FOO"bar"     │ double       quotes │
                      │                 │       │           │ aren’t stripped     │
                      ├─────────────────┼───────┼───────────┼─────────────────────┤
                      │FOO=bar # bazFOObar # baz │ comments        not │
                      │                 │       │           │ supported           │
                      ├─────────────────┼───────┼───────────┼─────────────────────┤
                      │FOO=bartbazFOObartbaz   │ backslashes are not │
                      │                 │       │           │ special             │
                      ├─────────────────┼───────┼───────────┼─────────────────────┤
                      │ FOO=bar FOObar       │ leading  space   in │
                      │                 │       │           │ key                 │
                      ├─────────────────┼───────┼───────────┼─────────────────────┤
                      │FOO= barFOO bar     │ leading   space  in │
                      │                 │       │           │ value               │
                      ├─────────────────┼───────┼───────────┼─────────────────────┤
                      │$FOO=bar$FOObar       │ variables       not │
                      │                 │       │           │ expanded in key     │
                      ├─────────────────┼───────┼───────────┼─────────────────────┤
                      │FOO=$BAR baz:quxFOOqux       │ variable   BAR  baz │
                      │                 │       │           │ not set             │
                      └─────────────────┴───────┴───────────┴─────────────────────┘

   Removing variables with --unset-env
       The purpose of --unset-env=GLOB is to remove unwanted environment variables. The  argument
       GLOB  is  a  glob pattern (dialect fnmatch(3) with the FNM_EXTMATCH flag where supported);
       all variables with matching names are removed from the environment.

       WARNING:
          Because the shell also interprets glob patterns, if  any  wildcard  characters  are  in
          GLOB, it is important to put it in single quotes to avoid surprises.

       GLOB must be a non-empty string.

       Example 1: Remove the single environment variable FOO:

          $ export FOO=bar
          $ env | fgrep FOO
          FOO=bar
          $ ch-run --unset-env=FOO $CH_TEST_IMGDIR/chtest -- env | fgrep FOO
          $

       Example  2:  Hide  from  a  container the fact that it’s running in a Slurm allocation, by
       removing all variables beginning with SLURM. You might want to do  this  to  test  an  MPI
       program with one rank and no launcher:

          $ salloc -N1
          $ env | egrep '^SLURM' | wc
             44      44    1092
          $ ch-run $CH_TEST_IMGDIR/mpihello-openmpi -- /hello/hello
          [... long error message ...]
          $ ch-run --unset-env='SLURM*' $CH_TEST_IMGDIR/mpihello-openmpi -- /hello/hello
          0: MPI version:
          Open MPI v3.1.3, package: Open MPI root@c897a83f6f92 Distribution, ident: 3.1.3, repo rev: v3.1.3, Oct 29, 2018
          0: init ok cn001.localdomain, 1 ranks, userns 4026532530
          0: send/receive ok
          0: finalize ok

       Example 3: Clear the environment completely (remove all variables):

          $ ch-run --unset-env='*' $CH_TEST_IMGDIR/chtest -- env
          $

       Example  4: Remove all environment variables except for those prefixed with either WANTED_
       or ALSO_WANTED_:

          $ export WANTED_1=yes
          $ export ALSO_WANTED_2=yes
          $ export NOT_WANTED_1=no
          $ ch-run --unset-env='!(WANTED_*|ALSO_WANTED_*)' $CH_TEST_IMGDIR/chtest -- env
          WANTED_1=yes
          ALSO_WANTED_2=yes
          $

       Note that some programs, such as shells, set some environment variables  even  if  started
       with no init files:

          $ ch-run --unset-env='*' $CH_TEST_IMGDIR/debian_9ch -- bash --noprofile --norc -c env
          SHLVL=1
          PWD=/
          _=/usr/bin/env
          $

EXAMPLES

       Run  the  command  echo  hello inside a Charliecloud container using the unpacked image at
       /data/foo:

          $ ch-run /data/foo -- echo hello
          hello

       Run an MPI job that can use CMA to communicate:

          $ srun ch-run --join /data/foo -- bar

SYSLOG

       By default, ch-run logs its command line to syslog. (This can be disabled  by  configuring
       with  --disable-syslog.)  This  includes:  (1)  the  invoking  real UID, (2) the number of
       command line arguments, and (3) the arguments, separated by spaces. For example:

          Dec 10 18:19:08 mybox ch-run: uid=1000 args=7: ch-run -v /var/tmp/00_tiny -- echo hello "wor l}\$d"

       Logging is one of the first things done during program initialization, even before command
       line  parsing.  That is, almost all command lines are logged, even if erroneous, and there
       is no logging of program success or failure.

       Arguments are serialized with the  following  procedure.  The  purpose  is  to  provide  a
       human-readable  reconstruction of the command line while also allowing each argument to be
       recovered byte-for-byte.

          • If an argument contains only printable ASCII bytes that  are  not  whitespace,  shell
            metacharacters,  double quote (", ASCII 34 decimal), or backslash (, ASCII 92), then
            log it unchanged.

          • Otherwise, (a) enclose the argument in double quotes and (b) backslash-escape  double
            quotes,  backslashes,  and  characters  interpreted  by Bash (including POSIX shells)
            within double quotes.

       The verbatim command line typed in the shell  cannot  be  recovered,  because  not  enough
       information is provided to UNIX programs. For example, echo  'foo' is given to programs as
       a sequence of two arguments, echo and foo; the two spaces and single quotes are removed by
       the shell. The zero byte, ASCII NUL, cannot appear in arguments because it would terminate
       the string.

EXIT STATUS

       If there is an error during containerization, ch-run exits with status  non-zero.  If  the
       user  command  is  started successfully, the exit status is that of the user command, with
       one exception: if the image is an internally mounted  SquashFS  filesystem  and  the  user
       command is killed by a signal, the exit status is 1 regardless of the signal value.

REPORTING BUGS

       If  Charliecloud  was  obtained  from your Linux distribution, use your distribution’s bug
       reporting procedures.

       Otherwise, report bugs to: https://github.com/hpc/charliecloud/issues

SEE ALSO

       charliecloud(7)

       Full documentation at: <https://hpc.github.io/charliecloud>

COPYRIGHT

       2014–2022, Triad National Security, LLC and others