From 53ae190ad16849bb052607389f5508f9ede20f84 Mon Sep 17 00:00:00 2001 From: Parke Date: Fri, 29 Oct 2021 19:00:10 -0700 Subject: [PATCH] Minor changes. Mostly code/file reorganization. --- Makefile | 72 ++- aux/static.sh | 181 +++++++ aux/unit.sh | 37 +- changelog.txt | 41 ++ help.cpp | 268 ++++++++++ lxroot.cpp | 1400 +++++++++++++------------------------------------ readme.md | 6 + str.cpp | 532 +++++++++++++++++++ 8 files changed, 1477 insertions(+), 1060 deletions(-) create mode 100644 aux/static.sh create mode 100644 help.cpp create mode 100644 str.cpp diff --git a/Makefile b/Makefile index 59fbb7b..7ef8403 100644 --- a/Makefile +++ b/Makefile @@ -1,29 +1,38 @@ -# version 20210624 +# version 20211020 -bin ?= bin -demo ?= /tmp/lxroot-demo -unit ?= /tmp/lxroot-unit - - -help: - @ echo - @ echo 'usage:' - @ echo ' make build # build bin/lxroot' - @ echo ' make unit # run the unit tests' - @ echo ' make unit-clean # delete the unit test environment' - @ echo ' make demo # run the interactive demo' - @ echo ' make demo-clean # delete the demo environment' - @ echo - +bin ?= bin +demo ?= /tmp/lxroot-demo +static ?= /tmp/lxroot-static +unit ?= /tmp/lxroot-unit Wextra ?= -Wextra -Wno-unused-parameter gpp_opts ?= -fmax-errors=2 -Wall -Werror $(Wextra) -$(bin)/lxroot: Makefile lxroot.cpp $(bin) +help: + @ echo "\ + \n\ +Welcome to the Lxroot Makefile. \n\ + \n\ +Usage: \n\ + \n\ + make build # build bin/lxroot \n\ + make unit # run the unit tests \n\ + make demo # run the interactive Alpine demo \n\ + make demo3 # run the Arch Linux + Chromium demo \n\ + make static # build a statically linked lxroot \n\ + \n\ + make clean # delete bin/lxroot \n\ + make unit-clean # delete the unit test environment \n\ + make demo-clean # delete the demo environment \n\ + make demo3-clean # delete the demo3 environment \n\ + make static-clean # delete the static build environment " + + +$(bin)/lxroot: Makefile lxroot.cpp help.cpp str.cpp $(bin) g++ -g $(gpp_opts) lxroot.cpp -o $@ @@ -41,9 +50,9 @@ clean: unit: $(bin)/lxroot $(unit) cp $(bin)/lxroot $(unit)/lxr @ echo - bash demo.sh demo1_extract $(demo) $(unit)/nr 2>&1 + bash aux/demo.sh demo1_extract $(demo) $(unit)/nr 2>&1 @ echo - bash unit.sh $(unit) 2>&1 + bash aux/unit.sh $(unit) 2>&1 unit-clean: clean @@ -53,7 +62,7 @@ unit-clean: clean demo: $(bin)/lxroot $(demo) cp $(bin)/lxroot $(demo)/lxroot - bash demo.sh demo1 $(demo) + bash aux/demo.sh demo1 $(demo) demo-clean: clean @@ -62,6 +71,13 @@ demo-clean: clean rm -rf /tmp/lxroot-demo/demo1 +static: + bash aux/static.sh $(bin)/lxroot $(static) + + +# demo 3 ----------------------------------------------------------- demo 3 + + demo3_iso=$(demo)/dist/archlinux-2021.06.01-x86_64.iso demo3_url=https://mirror.rackspace.com/archlinux/iso/2021.06.01/archlinux-2021.06.01-x86_64.iso @@ -75,25 +91,25 @@ demo3-base: demo3-iso-soft $(bin)/lxroot @ echo @ echo 'demo3 create userland1' - bash demo.sh demo1_extract $(demo) $(demo)/demo3 + bash aux/demo.sh demo1_extract $(demo) $(demo)/demo3 cp /etc/resolv.conf $(demo)/demo3/etc/ mkdir -p $(demo)/demo3/dist ln -f $(demo3_iso) $(demo)/demo3/dist @ echo - cp demo.sh $(demo)/demo3/root/ + cp aux/demo.sh $(demo)/demo3/root/ $(bin)/lxroot -nw $(demo)/demo3 \ - /bin/ash /root/demo.sh demo3_u1_create_u2 + -- /bin/ash /root/demo.sh demo3_u1_create_u2 @ echo - cp demo.sh $(demo)/demo3/userland2/root/ + cp aux/demo.sh $(demo)/demo3/userland2/root/ $(bin)/lxroot -nr $(demo)/demo3/userland2 \ - /bin/bash /root/demo.sh demo3_u2_create_u3 + -- /bin/bash /root/demo.sh demo3_u2_create_u3 @ echo - cp demo.sh $(demo)/demo3/userland2/userland3/root/ + cp aux/demo.sh $(demo)/demo3/userland2/userland3/root/ $(bin)/lxroot -nr $(demo)/demo3/userland2/userland3 \ - /bin/bash /root/demo.sh demo3_u3_finish + -- /bin/bash /root/demo.sh demo3_u3_finish @ echo mkdir -p $(demo)/demo3/userland2/userland3/$(HOME) @@ -127,3 +143,5 @@ demo3-clean: mkdir -p /tmp/lxroot-demo/demo3 chmod -R u+w /tmp/lxroot-demo/demo3 rm -rf /tmp/lxroot-demo/demo3 + + diff --git a/aux/static.sh b/aux/static.sh new file mode 100644 index 0000000..e4b07f6 --- /dev/null +++ b/aux/static.sh @@ -0,0 +1,181 @@ +#! /bin/bash + + +# static.sh version 20211028 + + +# this script will: +# Download an x86 (32 bit) Alpine userland. +# Build a statically compiled lxroot binary in that userland. + + +tgz_url='https://dl-cdn.alpinelinux.org/alpine/v3.14/releases/x86/alpine-minirootfs-3.14.2-x86.tar.gz' +tgz_sum='874f6e60047535c08ffcc8f430afec21d323b367269391279f74ff8f87420d83' + + +set -o errexit + + +usage () { # ---------------------------------------------------- usage + echo + echo 'usage: sh static.sh /path/to/lxroot /path/to/build_dir' ; } + + +die () { echo "die $*" ; exit 1 ; } # ------------------- die + +found () { printf 'found %-8s %s\n' "$1" "$2" ; } # ----- found + +trace () { echo "+ $@" ; "$@" ; } # ---------------------- trace + + +lxr () { # -------------------------------------------------------- lxr + "$lxroot" -rn "$build"/newroot -- "$@" ; } + + +init () { # ------------------------------------------------------ init + [ "$build" ] || { usage ; exit 1 ; } + if [ -d "$build" ] + then found init "$build" + else trace mkdir -p "$build" ; fi + cd "$build" ; } + + +fetch () ( # ---------------------------------------------------- fetch + [ -d 'dist' ] || trace mkdir dist + [ -f dist/"$tgz" ] && { found fetch dist/"$tgz" ; return ; } + cd dist + trace wget -nc "$tgz_url" ) + + +checksum () { # ---------------------------------------------- checksum + local actual="$( sha256sum dist/"$tgz" )" + actual="${actual%% dist/*}" + [ "$actual" = "$tgz_sum" ] && { found checksum valid ; return ; } + echo + echo 'checksum error' + echo " expect $tgz_sum" + echo " actual $actual" + exit 1 ; } + + +extract () { # ------------------------------------------------ extract + [ -d newroot ] && { found extract newroot ; return ; } + trace mkdir newroot + trace tar xzf dist/"$tgz" -C newroot ; } + + +dns () { # -------------------------------------------------------- dns + local path='newroot/etc/resolv.conf' + [ -f "$path" ] && { found dns "$path" ; return ; } + trace cp /etc/resolv.conf newroot/etc/resolv.conf ; } + + +apk_update () { # ------------------------------------------ apk_update + local path="newroot/root/touch_apk_update" + [ -f "$path" ] && { found update "$path" ; return ; } + trace lxr apk update + trace touch "$path" ; } + + +apk_add () { # ------------------------------------------------ apk_add + local path="newroot/usr/bin/g++" + [ -f "$path" ] && { found add "$path" ; return ; } + trace lxr apk add build-base ; } + + +build () { # ---------------------------------------------------- build + + # see http://ptspts.blogspot.com/2013/12/how-to-make-smaller-c-and-c-binaries.html + + local options=( + + -s + -Os + -fomit-frame-pointer + -fno-stack-protector + -ffunction-sections + -fdata-sections + -Wl,--gc-sections + -fno-unroll-loops + -fmerge-all-constants + -fno-ident + -Wl,-z,norelro + -Wl,--build-id=none + + -fno-exceptions + -fno-rtti + + # 20211028 -fvtable-gc is no longer supported + # -fvtable-gc + + ) + + echo + trace rm -f 'newroot/root/lxroot-plain' + trace rm -f 'newroot/root/lxroot-small' + trace rm -f 'newroot/root/lxroot-static' + trace cp "$src"/lxroot.cpp newroot/root/ + trace cp "$src"/help.cpp newroot/root/ + trace cp "$src"/str.cpp newroot/root/ + + echo + trace lxr time g++ lxroot.cpp -o lxroot-plain + + echo + trace lxr time g++ lxroot.cpp -o lxroot-small \ + "${options[@]}" + + echo + trace lxr time g++ lxroot.cpp -o lxroot-static \ + "${options[@]}" -static + + + + return ; } + + +main () { # ------------------------------------------------------ main + + local src="$PWD" lxroot="$1" build="$2" + local tgz="${tgz_url##*/}" + + lxroot=` realpath "$lxroot" ` + + echo + echo 'static.sh' + echo " src $src" + echo " lxroot $lxroot" + echo " bulid $build" + + echo + init + fetch + checksum + extract + dns + apk_update + apk_add + + build + + trace cd newroot/root + + echo + echo + echo '==== size of elf sections' + size lxroot-small lxroot-plain lxroot-static + + echo + echo + echo '==== file size in bytes' + ls -lSr lxroot-* + + echo + echo + echo '==== file size in kilobytes' + ls -lSrh lxroot-* + + return ; } + + +main "$@" diff --git a/aux/unit.sh b/aux/unit.sh index 4d6cf56..5277045 100644 --- a/aux/unit.sh +++ b/aux/unit.sh @@ -1,7 +1,7 @@ #! /bin/bash -# unit.sh version 20210624 +# unit.sh version 20210803 set -o errexit @@ -20,7 +20,7 @@ run () { # -------------------------------------------------------- run # echo "command ${command[@]}" local stdout status actual pretty set +o errexit - stdout=` "${command[@]}" 2>&1 ` ; status=$? pretty="$status" + stdout=` "${command[@]}" 2>&1 ` ; status="$?" ; pretty="$status" set -o errexit @@ -224,8 +224,7 @@ test_no_home () { # -------------------------------------- test_no_home lxr1=() run1 'err 0-' ./lxr - run1 'err 1 lxroot error execve bad No such file or directory' \ - ./lxr bad -- true + run1 'err 1-' ./lxr bad -- true return ; } @@ -336,7 +335,7 @@ prepare_full_overlay () { # ---------------------- prepare_full_overlay test_full_overlay () { # ---------------------------- test_full_overlay - echo ; echo "- test_full_overlay" + echo ; echo '- test_full_overlay' prepare_full_overlay env1=( env - ) lxr1=() cmd1=() @@ -590,10 +589,33 @@ test_env () { # ---------------------------------------------- test_env echo ; echo "- test_env" - env1=( env - 'FOO=BAR2' ) lxr1=() cmd1=() + TZ='America/Los_Angeles' + env1=( env - 'FOO=BAR2' "TZ=$TZ" ) lxr1=() cmd1=() - run1 'hello' ./lxr -e -- /bin/sh -c 'echo hello' + run1 'hello' ./lxr -- /bin/sh -c 'echo hello' + run1 '' ./lxr -- /bin/sh -c 'echo "$FOO"' run1 'BAR2' ./lxr -e -- /bin/sh -c 'echo "$FOO"' + run1 "$TZ" ./lxr -- /bin/sh -c 'echo "$TZ"' + run1 "$TZ" ./lxr -e -- /bin/sh -c 'echo "$TZ"' + + return ; } + + +test_dev_shm () { # -------------------------------------- test_dev_shm + + # 20211002 On my development system, /dev/shm exists. Moreover, + # /dev/shm is mounted with the nosuid and nodev flags. + # Therefore, I can use /dev/shm to test read-only + # remounting of bind mounts with nosuid and nodev. + + [ ! -d /dev/shm ] && return + + env1=( env - ) lxr1=() cmd1=() + + run1 'foo' ./lxr nr bind rw /mnt /dev/shm -- echo foo + run1 'foo' ./lxr nr bind ra /mnt /dev/shm -- echo foo + run1 'foo' ./lxr nr bind ro /mnt /dev/shm -- echo foo + run1 'foo' ./lxr nr bind /mnt /dev/shm -- echo foo return ; } @@ -612,6 +634,7 @@ main () { # ------------------------------------------------------ main cd "$unit" + test_dev_shm test_no_newroot test_env diff --git a/changelog.txt b/changelog.txt index fbcd80d..2c6c2e8 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,6 +1,47 @@ +20211028 + +Ipmelmented proper fix of GitHub issue #3, via statfs(). +Moved demo.sh and unit.sh to aux/ directory. +Added static.sh to aux/ to aux/ directory. +Improved Makefile help text. +Moved help strings from lxroot.cpp to help.cpp. +Moved string classes from lxroot.cpp to str.cpp. + + +20211019 + +moved string implementation to str.cpp +name changes related to char * and/or mutability vs. immutability + renamed struct mstr to struct mStr + renamed enum opt to enum mopt + renamed struct str to struct Str + introduced typedef mstr + introduced typedef opt + introduced typedef str + + +20210803 + +In anticipation of binding files, '--' is now required before command. + + +20210801 + +Rebuilt Logic :: binds(). It is now simpler and (more?) correct. +Specifically: I believe Lxroot now ignores (i.e. skips) overbound +binds. This is beneficial, for example, when using multiple, +partially-overlapping full-overlays. + + +20210630 + +Added '/usr/local/games:/usr/games' to $PATH. + + 20210627 + Implemented environment passthru via -e or --env. Added 'e' and 'w' indicators to command prompt. Omit the newroot-option to remain in the external mount namespace. diff --git a/help.cpp b/help.cpp new file mode 100644 index 0000000..0b8f81a --- /dev/null +++ b/help.cpp @@ -0,0 +1,268 @@ + + +// help.cpp - Create and use chroot-style virtual software environments. +// Copyright (c) 2021 Parke Bostrom, parke.nexus at gmail.com +// Distributed under GPLv3 (see end of file) WITHOUT ANY WARRANTY. + +// version 20211019 + + + + +const char * help = // xxhe ------------------------------------- help +"\n" +"usage: lxroot [mode] newroot [options] [-- command [arg ...]]\n\n" + +"options\n" +" -short one or more short options\n" +" --long-option a long option\n" +" name=value set an environment variable\n" +" [mode] newroot set and bind the newroot\n" +" [mode] path bind a full or partial overlay\n" +" 'src' [mode] path set the source for partial overlays\n" +" 'bind' [mode] dst src bind src to newroot/dst\n" +" 'cd' path cd to path (inside newroot)\n" +" 'wd' path cd to path and make path writable\n" +" -- end of options, command follows\n" +" command [arg ...] command\n" +""; // end help --------------------------------------------- end help + + +const char * help2 = // xxhe ----------------------------------- help2 +"\n" +" For more help, please run: lxroot --help-more\n" +""; // end help2 ------------------------------------------- end help2 + + +// short options ( for plannig purposes only, possibly inaccurate ) +// . . . . . . +// a - f - hostfs k - p - PID u - UID z +// b - g - l - q - v - verbose +// c - h - hstname m - MOUNT r root w write +// d - dbus i - n NET s - /sys x x11 +// e environ j - o - t y +// +// A F K P U Z +// B G L Q V +// C H M R W +// D I N S X +// E J O T Y +// +// long options ( for plannig purposes only, possibly inaccurate ) +// dbus env help help-more network pid pulseaudio root trace uid +// verbose version write x11 + + +const char * help_more = // xxlo ----------------------------- help_more + +"\nMODES\n\n" + +" ra read-auto (default for newroot, described below)\n" +" ro read-only (bind mount with MS_RDONLY)\n" +" rw read-write (bind mount without MS_RDONLY)\n\n" + +"SHORT OPTIONS\n\n" + +" e import (almost) all external environment variables\n" +" n allow network access (CLONE_NEWNET = 0)\n" +" r simulate root user (map uid and gid to zero)\n" +" w allow full write access to all read-auto binds\n" +" x allow X11 access (bind /tmp/.X11-unix and set $DISPLAY)\n\n" + +"LONG OPTIONS\n\n" + +" --env import (almost) all external environment variables\n" +" --help display help\n" +" --help-more display more help\n" +" --network allow network access (CLONE_NEWNET = 0)\n" +" --pulseaudio allow pulseaudio access (bind $XDG_RUNTIME_DIR/pulse)\n" +" --root simulate root user (map uid and gid to zero)\n" +" --trace log diagnostic info to stderr\n" +" --version print version info and exit\n" +" --write allow full write access to all read-auto binds\n" +" --x11 allow X11 access (bind /tmp/.X11-unix and set $DISPLAY)\n\n" + +"READ-AUTO MODE\n\n" + +"The purpose of read-auto mode is to (a) grant a simulated-root user\n" +"broad or total write access, while (b) granting a non-root user write\n" +"access only to a few select directories, namely: $HOME, /tmp, and\n" +"/var/tmp.\n\n" + +"To be precise and complete:\n\n" + +"Each bind (including newroot) has a specified mode. The specified\n" +"mode is one of: 'ra', 'ro', or 'rw'.\n\n" + +"If no mode is specified for newroot, then newroot's specified mode\n" +"defaults to 'ra' (read-auto).\n\n" + +"If any other bind lacks a specified mode, then that bind simply\n" +"inherits the specified mode of its parent.\n\n" + +"Each bind also has an actual mode. The actual mode is: 'ro' or 'rw'.\n\n" + +"A bind's actual mode may be different from its specified mode. A\n" +"bind's actual mode is determined as follows:\n\n" + +"If the specified mode is 'rw', then the actual mode is 'rw'.\n\n" + +"If the bind is inside a path specified by a wd-option, then the actual\n" +"mode is 'rw' (even if that bind's specified mode is 'ro').\n\n" + +"If the specified mode is 'ra', and furthormore if:\n" +" a) the '-r' or '--root' option is specified, or\n" +" b) the '-w' or '--write' option is specified, or\n" +" c) the bind's destination path is inside $HOME, /tmp, or /var/tmp,\n" +"then the actual mode is 'rw'.\n\n" + +"Otherwise the bind's actual mode is 'ro'.\n\n" + +"NEWROOT\n\n" + +"Note that the newroot, full-overlay, and partial-overlay options all\n" +"have the same form, namely: [mode] path\n\n" + +"The first option of this form is the newroot-option. The newroot-\n" +"option specfies the newroot.\n\n" + +"If no newroot-option is specified, then lxroot will neither bind,\n" +"chroot, nor pivot. This is useful to simulate root or deny network\n" +"access while retaining the current mount namespace.\n\n" + +"FULL OVERLAY\n\n" + +"Zero or more full-overlay options may occur anywhere before the first\n" +"set-source option.\n\n" + +"A full-overlay option has the form: [mode] path\n\n" + +"A full-overlay option will attempt to bind all the subdirectories\n" +"inside path to identically named subdirectories inside newroot.\n\n" + +"For example, if my_overlay contains the subdirectories 'home', 'run',\n" +"and 'tmp', then the full-overlay option 'rw my_overlay' will attempt\n" +"to bind the following:\n\n" + +" my_overlay/home to newroot/home in read-write mode\n" +" my_overlay/run to newroot/run in read-write mode\n" +" my_overlay/tmp to newroot/tmp in read-write mode\n\n" + +"If any newroot/subdir does not exist, then that my_overlay/subdir will\n" +"be silently skipped.\n\n" + +"SET SOURCE\n\n" + +"A set-source option has the form: 'src' [mode] path\n\n" + +"'src' is the literal string 'src'.\n\n" + +"A set-source option sets the overlay-source-path and the default\n" +"overlay-mode. These values will be used by any following\n" +"partial-overlay options.\n\n" + +"Zero or more set-source options may be specified.\n\n" + +"PARTIAL OVERLAY\n\n" + +"Zero or more partial-overlay options may occur anywhere after the\n" +"first set-source option.\n\n" + +"A partial-overlay option has the form: [mode] path\n\n" + +"A partial-overlay option will bind overlay/path to newroot/path, where\n" +"overlay is the overlay-source-path set by the preceding set-source\n" +"option.\n\n" + +"For example, the two options 'src my_overlay home/my_username' will do\n" +"the following:\n\n" + +" 1) first, the overlay-source-path will be set to 'my_overlay'\n" +" 2) then, the following bind will occur:\n\n" + +" my_overlay/home/my_username to newroot/home/my_username\n\n" + +"If either directory does not exist, lxroot will exit with status 1.\n\n" + +"Successive partial-overlay options may be used to bind a selected\n" +"subset of the descendants of an overlay into newroot. (Whereas a\n" +"single full-overlay option attempts to bind all of the full-overlay's\n" +"immediate subdirectories into newroot.)\n\n" + +"BIND\n\n" + +"A bind-option has the form: 'bind' [mode] dst src\n\n" + +"'bind' is the literal string 'bind'.\n\n" + +"A bind-option will bind src to newroot/dst, using the optionally\n" +"specified mode.\n\n" + +"Note that dst precedes src. This hopefully improves readibilty in\n" +"scripts where: (a) many binds may be specified, (b) dst is tyically\n" +"shorter than src, and (c) src may vary greatly in length from bind to\n" +"bind.\n\n" + +"CD\n\n" + +"A cd-option has the form: 'cd' path\n\n" + +"'cd' is the literal string 'cd'. One or zero cd-options may be\n" +"specified.\n\n" + +"A cd-option tells lxroot to cd into path (in the new environment)\n" +"before executing the command.\n\n" + +"path does not include newroot, as a cd-option is processed after the\n" +"pivot.\n\n" + +"WD\n\n" + +"A wd-option has the form: 'wd' path\n\n" + +"'wd' is the literal string 'wd'. Zero or more wd-options may be\n" +"specified.\n\n" + +"Lxroot will bind path (and all of path's descendants) in read-write\n" +"mode. So a wd-option is used to make writeable a specific path (and\n" +"its descendants) inside the new environment.\n\n" + +"path does not include newroot, as wd-options are processed after the\n" +"pivot.\n\n" + +"Additionally, if no cd-option is specified, then lxroot will cd into\n" +"the path of the last wd-option prior to executing the command.\n\n" + +"Note: Any path that is already mounted in read-only mode in the\n" +"outside environment (i.e. before lxroot runs) will still be read-only\n" +"inside the new environment. This is because non-root namespaces can\n" +"only impose new read-only restricitons. Non-root namespaces cannot\n" +"remove preexsiting read-only restrictions.\n\n" + +"COMMAND\n\n" + +"The command-option specifies the command that will be executed inside\n" +"the lxroot environment. The command-option must be preceded by '--'.\n\n" + +"If no command is specified, lxroot will attempt to find and execute an\n" +"interactive shell inside the lxroot environment.\n\n" + +"" ; // end help_more --------------------------------- end help_more + + +// help.cpp - Create and use chroot-style virtual software environments. +// +// Copyright (c) 2021 Parke Bostrom, parke.nexus at gmail.com +// +// This program is free software: you can redistribute it and/or +// modify it under the terms of version 3 of the GNU General Public +// License as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See version +// 3 of the GNU General Public License for more details. +// +// You should have received a copy of version 3 of the GNU General +// Public License along with this program. If not, see +// . diff --git a/lxroot.cpp b/lxroot.cpp index 863c458..66eb8b8 100644 --- a/lxroot.cpp +++ b/lxroot.cpp @@ -5,297 +5,56 @@ // Distributed under GPLv3 (see end of file) WITHOUT ANY WARRANTY. -#define LXROOT_VERSION "0.0.20210628" - - -const char * help = // xxhe ------------------------------------- help -"\n" -"usage: lxroot [[mode] newroot] [options] [--] [command [arg ...] ]\n\n" - -"options\n" -" -short one or more short options\n" -" --long-option a long option\n" -" n=v set an environment variable\n" -" [mode] newroot set and bind the newroot\n" -" [mode] path bind a full or partial overlay\n" -" 'src' [mode] path set the source for partial overlays\n" -" 'bind' [mode] dst src bind src to newroot/dst\n" -" 'cd' path cd to path (inside newroot)\n" -" 'wd' path cd to path and make path writable\n" -" -- end of options, command follows\n" -" command [arg ...] command\n" -""; // end help --------------------------------------------- end help - - -const char * help2 = // xxhe ----------------------------------- help2 -"\n" -" For more help, please run: lxroot --help-more\n" -""; // end help2 ------------------------------------------- end help2 - - -// short options ( for plannig purposes only, possibly inaccurate ) -// . . . . . . -// a - f - hostfs k - p - PID u - UID z -// b - g - l - q - v - verbose -// c - h - hstname m - MOUNT r root w write -// d - dbus i - n NET s - /sys x x11 -// e environ j - o - t y -// -// A F K P U Z -// B G L Q V -// C H M R W -// D I N S X -// E J O T Y -// -// long options ( for plannig purposes only, possibly inaccurate ) -// dbus env help help-more network pid pulseaudio root trace uid -// verbose version write x11 - - -const char * help_more = // xxlo ----------------------------- help_more - -"\nMODES\n\n" - -" ra read-auto (default for newroot, described below)\n" -" ro read-only (bind mount with MS_RDONLY)\n" -" rw read-write (bind mount without MS_RDONLY)\n\n" - -"SHORT OPTIONS\n\n" - -" e import (almost) all external environment variables\n" -" n allow network access (CLONE_NEWNET = 0)\n" -" r simulate root user (map uid and gid to zero)\n" -" w allow full write access to all read-auto binds\n" -" x allow X11 access (bind /tmp/.X11-unix and set $DISPLAY)\n\n" - -"LONG OPTIONS\n\n" - -" --env import (almost) all external environment variables\n" -" --help display help\n" -" --help-more display more help\n" -" --network allow network access (CLONE_NEWNET = 0)\n" -" --pulseaudio allow pulseaudio access (bind $XDG_RUNTIME_DIR/pulse)\n" -" --root simulate root user (map uid and gid to zero)\n" -" --trace log diagnostic info to stderr\n" -" --version print version info and exit\n" -" --write allow full write access to all read-auto binds\n" -" --x11 allow X11 access (bind /tmp/.X11-unix and set $DISPLAY)\n\n" - -"READ-AUTO MODE\n\n" - -"The purpose of read-auto mode is to (a) grant a simulated-root user\n" -"broad or total write access, while (b) granting a non-root user write\n" -"access only to a few select directories, namely: $HOME, /tmp, and\n" -"/var/tmp.\n\n" - -"To be precise and complete:\n\n" - -"Each bind (including newroot) has a specified mode. The specified\n" -"mode is one of: 'ra', 'ro', or 'rw'.\n\n" - -"If no mode is specified for newroot, then newroot's specified mode\n" -"defaults to 'ra' (read-auto).\n\n" - -"If any other bind lacks a specified mode, then that bind simply\n" -"inherits the specified mode of its parent.\n\n" - -"Each bind also has an actual mode. The actual mode is: 'ro' or 'rw'.\n\n" - -"A bind's actual mode may be different from its specified mode. A\n" -"bind's actual mode is determined as follows:\n\n" - -"If the specified mode is 'rw', then the actual mode is 'rw'.\n\n" - -"If the bind is inside a path specified by a wd-option, then the actual\n" -"mode is 'rw' (even if that bind's specified mode is 'ro').\n\n" - -"If the specified mode is 'ra', and furthormore if:\n" -" a) the '-r' or '--root' option is specified, or\n" -" b) the '-w' or '--write' option is specified, or\n" -" c) the bind's destination path is inside $HOME, /tmp, or /var/tmp,\n" -"then the actual mode is 'rw'.\n\n" - -"Otherwise the bind's actual mode is 'ro'.\n\n" - -"NEWROOT\n\n" - -"Note that the newroot, full-overlay, and partial-overlay options all\n" -"have the same form, namely: [mode] path\n\n" - -"The first option of this form is the newroot-option. The newroot-\n" -"option specfies the newroot.\n\n" - -"If no newroot-option is specified, then lxroot will neither bind,\n" -"chroot, nor pivot. This is useful to simulate root or deny network\n" -"access while retaining the current mount namespace.\n\n" - -"FULL OVERLAY\n\n" - -"Zero or more full-overlay options may occur anywhere before the first\n" -"set-source option.\n\n" - -"A full-overlay option has the form: [mode] path\n\n" - -"A full-overlay option will attempt to bind all the subdirectories\n" -"inside path to identically named subdirectories inside newroot.\n\n" - -"For example, if my_overlay contains the subdirectories 'home', 'run',\n" -"and 'tmp', then the full-overlay option 'rw my_overlay' will attempt\n" -"to bind the following:\n\n" - -" my_overlay/home to newroot/home in read-write mode\n" -" my_overlay/run to newroot/run in read-write mode\n" -" my_overlay/tmp to newroot/tmp in read-write mode\n\n" - -"If any newroot/subdir does not exist, then that my_overlay/subdir will\n" -"be silently skipped.\n\n" - -"SET SOURCE\n\n" - -"A set-source option has the form: 'src' [mode] path\n\n" - -"'src' is the literal string 'src'.\n\n" - -"A set-source option sets the overlay-source-path and the default\n" -"overlay-mode. These values will be used by any following\n" -"partial-overlay options.\n\n" - -"Zero or more set-source options may be specified.\n\n" - -"PARTIAL OVERLAY\n\n" - -"Zero or more partial-overlay options may occur anywhere after the\n" -"first set-source option.\n\n" - -"A partial-overlay option has the form: [mode] path\n\n" - -"A partial-overlay option will bind overlay/path to newroot/path, where\n" -"overlay is the overlay-source-path set by the preceding set-source\n" -"option.\n\n" - -"For example, the two options 'src my_overlay home/my_username' will do\n" -"the following:\n\n" - -" 1) first, the overlay-source-path will be set to 'my_overlay'\n" -" 2) then, the following bind will occur:\n\n" - -" my_overlay/home/my_username to newroot/home/my_username\n\n" - -"If either directory does not exist, lxroot will exit with status 1.\n\n" - -"Successive partial-overlay options may be used to bind a selected\n" -"subset of the descendants of an overlay into newroot. (Whereas a\n" -"single full-overlay option attempts to bind all of the full-overlay's\n" -"immediate subdirectories into newroot.)\n\n" - -"BIND\n\n" - -"A bind-option has the form: 'bind' [mode] dst src\n\n" - -"'bind' is the literal string 'bind'.\n\n" - -"A bind-option will bind src to newroot/dst, using the optionally\n" -"specified mode.\n\n" - -"Note that dst precedes src. This hopefully improves readibilty in\n" -"scripts where: (a) many binds may be specified, (b) dst is tyically\n" -"shorter than src, and (c) src may vary greatly in length from bind to\n" -"bind.\n\n" - -"CD\n\n" - -"A cd-option has the form: 'cd' path\n\n" - -"'cd' is the literal string 'cd'. One or zero cd-options may be\n" -"specified.\n\n" - -"A cd-option tells lxroot to cd into path (in the new environment)\n" -"before executing the command.\n\n" - -"path does not include newroot, as a cd-option is processed after the\n" -"pivot.\n\n" - -"WD\n\n" - -"A wd-option has the form: 'wd' path\n\n" - -"'wd' is the literal string 'wd'. Zero or more wd-options may be\n" -"specified.\n\n" - -"Lxroot will bind path (and all of path's descendants) in read-write\n" -"mode. So a wd-option is used to make writeable a specific path (and\n" -"its descendants) inside the new environment.\n\n" - -"path does not include newroot, as wd-options are processed after the\n" -"pivot.\n\n" - -"Additionally, if no cd-option is specified, then lxroot will cd into\n" -"the path of the last wd-option prior to executing the command.\n\n" - -"Note: Any path that is already mounted in read-only mode in the\n" -"outside environment (i.e. before lxroot runs) will still be read-only\n" -"inside the new environment. This is because non-root namespaces can\n" -"only impose new read-only restricitons. Non-root namespaces cannot\n" -"remove preexsiting read-only restrictions.\n\n" - -"COMMAND\n\n" - -"The command-option specifies the command that will be executed inside\n" -"the lxroot environment.\n\n" - -"If no command is specified, lxroot will attempt to find and execute an\n" -"interactive shell inside the lxroot environment.\n\n" - -"Note the following lexical ambiguity: a path-like argument may specify\n" -"either (a) an overlay option or (b) the command option.\n\n" - -"lxroot resolves this ambiguity by looking for a directory at the path.\n" -"If a directory exists, lxroot interprets the path as an overlay option.\n" -"If no such directory exists, lxroot interprets the path as a command.\n\n" - -"Note that lxroot does not verify that the command actually exists\n" -"inside newroot. If the command does not exist, then the call to\n" -"exec() will will fail and lxroot will exit with status 1.\n\n" - -"To force a path to be interpreted as a command, proceed the path with\n" -"the option '--'.\n\n" - -"" ; // end help_more --------------------------------- end help_more +#define LXROOT_VERSION "0.0.0 + 20211019" // Welcome to the source code for lxroot. // +// lxroot's command line interface is documented in help.cpp. +// // The classes and structs in lxroot can be divided into three categories: // // low level data storage classes // convenience wrappers around system APIs // high level classes // -// Low level data storage classes +// -- Classes and typedefs that are defined in str.cpp +// +// String type names that begin with 'm' mean mutable. +// Most string type names lack the 'm' prefix are immutable. +// Lxroot uses the below nicknames (i.e. typedefs) for 'const char *'. +// +// typedef mstr a const char * +// typedef str a const mstr +// +// The following structs provide method access to a wrapped 'const char *'. +// +// struct mFrag a mutable string fragment (mstr and length). +// struct mStr a mutable string (mstr, null terminated). +// struct Concat_2 assists with string concatination. +// struct oStr an appendable string that owns its memory. +// struct Concat possibly deprecated, assists with string concatination +// struct Argv a convenience and saftey wrapper around mstr[]. +// +// typedef Frag a const mFrag. +// typedef Str a const mStr. +// +// -- Low level data storage classes // // enum opt an enumeration that represents various options. -// struct mfrag a mutable string fragment (const char * and length). -// struct mstr a mutable string (const char *, null terminated). -// struct Concat_2 assists with string concatination. -// struct ostr an appendable string that owns its memory. -// struct Concat possibly deprecated, assists with string concatination -// struct Argv a convenience and saftey wrapper around const char * *. // struct Option represents a parsed command line option. // struct Bind represents (and specifies) a potential bind mount. // struct Env a list that specifies the new environment. // struct State contains shared, mutable, global variables. // -// typedef frag a const mfrag. -// typedef str a const mstr. -// -// Convenience wrappers +// -- Convenience wrappers // // class Dirent represents a directory entry (from readdir()). // struct Fgetpwent parses /etc/passwd. // struct Lib contains generally useful functions. // struct Syscall provides error handling and tracing of syscalls. // -// High level classes +// -- High level classes // // struct Option_Reader parses Argv to generate Options and/or Binds. // struct Logic analyzes data and performs loops over data. @@ -309,9 +68,6 @@ const char * help_more = // xxlo ----------------------------- help_more // classes as needed. -const char * bash_command[] = { "/bin/bash", "--norc", nullptr }; - - #include // man 3 opendir #include // man 3 errno #include // man 2 open @@ -325,6 +81,8 @@ const char * bash_command[] = { "/bin/bash", "--norc", nullptr }; #include // man 2 getuid stat #include // man 2 mount #include // man 2 stat +#include // man 2 statfs +#include // man 2 statfs ST_RELATIME #include // man 8 pivot_root #include // man 3 opendir #include // man 2 wait @@ -332,6 +90,15 @@ const char * bash_command[] = { "/bin/bash", "--norc", nullptr }; #include // class Env .vec #include // std :: function +#include "help.cpp" // Lxroot's help strings + + +// 20211019 At present, these typedefs are also duplicated in str.cpp. +typedef const char * mstr; // --------------------------- typedef mstr +typedef const mstr str; // ---------------------------- typedef str + + + template < class C > // --------------------------- template using sink using sink = std :: function < void ( const C & ) >; @@ -364,7 +131,7 @@ using msink = std :: function < void ( C & ) >; template < typename T > // ---------------------------- template assert2 -T assert2 ( T v, const char * file, const int line ) { +T assert2 ( T v, str file, const int line ) { // Discussion of assert2 being a function vs being a macro: @@ -388,7 +155,22 @@ T assert2 ( T v, const char * file, const int line ) { -enum opt { // xxop ----------------------------------------- enum opt +// 20211019 At present, str.cpp depends on the above assert macro. +// Someday, I may remove the dependency on assert. +// Until then, I will include str.cpp here. + +#include "str.cpp" // Parke's string library + + + + +mstr bash_command[] = { // ------------------------------ bash_command + "/bin/bash", "--norc", nullptr }; + + + + +enum mopt { // xxop --------------------------------------- enum mopt o_none, /* literal arg types */ o_bind, o_dashdash, o_cd, o_ra, o_ro, o_rw, o_src, o_wd, @@ -400,7 +182,7 @@ enum opt { // xxop ----------------------------------------- enum opt }; -const char * const opt_name[] = { // ------------------------ opt_name +str opt_name[] = { // --------------------------------------- opt_name "0", /* literal arg types */ "bind", "--", "cd", "ra", "ro", "rw", "src", "wd", @@ -412,539 +194,38 @@ const char * const opt_name[] = { // ------------------------ opt_name nullptr }; -opt operator || ( opt a, opt b ) { // --------------------- opt op || +typedef const mopt opt; + + +mopt operator || ( opt a, opt b ) { // --------------------- opt op || return a ? a : b ; } -opt global_opt_trace = o_none; // -------------- opt global_opt_trace +mopt global_opt_trace = o_none; // -------------- :: global_opt_trace -const char * o2s ( const opt n ) { // ---------------------------- o2s +mstr o2s ( const opt n ) { // -------------------------------- :: o2s if ( 0 <= n && n < ( sizeof opt_name / sizeof(char*) ) ) { return opt_name[n]; } return "INVALID_OPTION"; } -opt s2o ( const char * const s ) { // ---------------------------- s2o +mopt s2o ( str s ) { // xxs2 -------------------------------- :: s2o if ( s ) { for ( int n = o_none; opt_name[n]; n++ ) { - if ( strcmp ( s, opt_name[n] ) == 0 ) { return (opt) n; } } } + if ( strcmp ( s, opt_name[n] ) == 0 ) { return (mopt) n; } } } return o_none; } -// end enum opt ------------------------------------------ end enum opt - - - - -struct mfrag; // ------------------------------ declare struct mfrag -struct mstr; // ------------------------------- declare struct mstr -class ostr; // -------------------------------- declare class ostr -class Concat; // ------------------------------ declare class Concat -typedef const mstr str; // xxst ------------------ typedef str -typedef const mfrag frag; // xxfr ----------------- typedef frag -typedef unsigned long flags_t; // xxfl -------------- typedef flags_t - - -struct mfrag { // xxmf --------------------------------- struct mfrag - - - const char * s = nullptr; - int n = 0; - - - mfrag () {} // ------------------------------------------ mfrag ctor - mfrag ( const char * s ) : s(s), n(s?strlen(s):0) {} - mfrag ( const char * s, int n ) : s(s), n(n) {} - mfrag ( const char * a, - const char * b ) : s(a), n( b - a + 1 ) {} - - - explicit operator bool () const { return s; } // -------- op bool - - - bool operator == ( frag & o ) const { // ------------- mfrag op == - return n == o.n && memcmp ( s, o.s, n ) == 0; } - - - /* 20210623 used next() instead. - frag operator ++ ( int ) { // --------------------------- mfrag op ++ - if ( n > 0 ) { s ++; n --; } return * this; } - */ - - - Concat operator + ( frag & o ) const; // --------------- mfrag op + - - - char c () const { return n > 0 ? * s : '\0' ; } // ------ c - void next () { if ( n > 0 ) { s ++; n --; } } // ------- next - - - mfrag capture_until ( const char accept ) { // ------- capture_until - const char * const a = s; - return frag ( a, find(accept).s - a ); } - - - mfrag & find ( const char accept ) { // ---------------- mfrag find - while ( c() && c() not_eq accept ) { next(); } return * this; } - - - mfrag & find_skip ( const char accept ) { // ------ mfrag find_skip - return find ( accept ) .skip ( accept ); } - - - mfrag & skip ( const char accept ) { // ---------------- mfrag skip - while ( c() && c() == accept ) { next(); } return * this; } - - - void trace ( const char * const message ) const { // -- mfrag trace - if ( global_opt_trace == o_trace ) { - printe ( "%s >", message ); - fwrite ( s, n, 1, stderr ); - printe ( "<\n" ); } } - - -}; // end struct mfrag ---------------------------- end struct mfrag - - - - -const char * leak ( frag o ) { // ------------- global function leak - // lxroot quickly exec()s on success or exit()s on failure. - // therefore, a few convenient and minor memory leaks are acceptable. - char * rv = (char*) malloc ( o.n + 1 ); // intentional leak. - if ( rv ) { - memcpy ( rv, o.s, o.n ); - rv [ o.n ] = '\0'; } - return rv; } - - - - -struct mstr { // xxms ----------------------------------- struct mstr - - // mstr is a convenience wrapper around a const char *. - // s points to either (a) nullptr or (b) a null-terminated string. - // an mstr does not own s. (But see also derived class ostr.) - // an mstr makes no guarantee about the lifetime of *s. - - - const char * s = nullptr; // a pointer to the string. - - - mstr () {} // -------------------------------------------- mstr ctor - mstr ( const char * s ) : s(s) {} // ------------------- mstr ctor - - - operator bool () const { return s; } // -------- mstr cast bool - operator frag () const { return s; } // -------- mstr cast frag - - - bool operator == ( str & o ) const { // --------------- mstr op == - if ( s == nullptr && o.s == nullptr ) { return true; } - if ( s == nullptr || o.s == nullptr ) { return false; } - return strcmp ( s, o.s ) == 0; } - - - mstr operator || ( const mstr & o ) const { // -------- mstr op || - return s ? * this : o ; } - - - char operator * () const { // -------------------------- mstr op * - return s ? *s : '\0'; } - - - char operator [] ( int index ) const { // ------------- mstr op [] - if ( s == nullptr ) { return 0; } - return skip ( index ) .s[0]; } - - - mstr operator ++ ( int ) { // -------------------------- mstr op ++ - return s && *s ? s++ : s ; } - - - Concat operator + ( frag o ) const; // --------- declare mstr op + - Concat operator + ( str o ) const; // --------- declare mstr op + - Concat operator + ( const char * const o ) const; // ---- mstr op + - - - frag basename () const { // ------------------------- mstr basename - if ( s == nullptr ) { return nullptr; } - // /foo/bar - // a bc - // a is the first character of this basename - // b is the first character after this basename ( '/' or '\0' ) - // c is the first non-slash after b - // return the last basename - const char * a = s; - while ( a[0] == '/' && a[1] ) { a++; } - for (;;) { - const char * b = a + ( a[0] ? 1 : 0 ); - while ( b[0] && b[0] != '/' ) { b++; } - if ( b[0] == '\0' ) { return frag ( a, b-1 ); } - const char * c = b + 1; - while ( c[0] == '/' ) { c++; } - if ( c[0] == '\0' ) { return frag ( a, b-1 ); } - a = c; } } - - - static void basename_test ( str s, str expect ); // ------------------ - - - frag capture_until ( char c ) const { // ------ mstr capture_until - mstr p = s; while ( p && * p && * p != c ) { p ++; } - return frag ( s, p.s - s ); } - - - const void * chr ( const int c ) const { // ------------- mstr chr - return s ? :: strchr ( s, c ) : nullptr ; } - - - bool contains ( char c ) const { // ---------------- mstr contains - if ( s == nullptr ) { return false; } - for ( const char * p = s; * p; p ++ ) { - if ( * p == c ) { return true; } } - return false; } - - - frag env_name () const { // ------------------------ str env_name - return head ( "=" ); } - - - frag head ( const char * sep, int start = 0 ) const { // ----- head - if ( s == nullptr ) { return frag(); } - const char * p = s; - while ( * p && start-- > 0 ) { p++; } - const char * found = :: strstr ( p, sep ); - if ( found ) { return frag ( p, found - p ); } - return frag(); } - - - bool is_inside ( str path ) const { // ------------ mstr is_inside - - // return true iff s is equal to or a descendant of path. - - mstr descendant = s; - mstr ancestor = path; - bool is_inside = false; - - auto skip_slash = [&] () { - while ( * ancestor == '/' ) { ancestor ++; } - while ( * descendant == '/' ) { descendant ++; } - if ( * ancestor == '\0' ) { is_inside = true; } }; - - if ( path == nullptr ) { return false; } - skip_slash(); - while ( * ancestor == * descendant ) { - switch ( * ancestor ) { - case '\0': return true; break; - case '/': skip_slash(); break; - default: ancestor ++; descendant ++; break; } } - - return is_inside || ( * ancestor == '\0' && * descendant == '/' ); } - - - bool is_same_path_as ( str path ) const { // ------ is_same_path_as - return is_inside(path) && path.is_inside(*this); } - - - int n () const { // --------------------------------------- mstr n - return s ? strlen ( s ) : 0 ; } - - - str skip ( int n ) const { // -------------------------- mstr skip - const char * p = s; - while ( n-- > 0 ) { - if ( p && * p ) { p++; } - else { return nullptr; } } - return p; } - - - str skip_all ( char c ) const { // ----------------- mstr skip_all - mstr p = s; - while ( p && * p == c ) { p ++; }; return p; } - - - int spn ( str accept ) const { // ----------------------- mstr spn - return s ? :: strspn ( s, accept.s ) : 0 ; } - - - bool startswith ( frag o ) const { // ------------ mstr starstwith - return s && strncmp ( s, o.s, o.n ) == 0; } - - - str tail ( str sep ) const { // ------------------------ mstr tail - str found = :: strstr ( s, sep.s ); - return found ? found.s + sep.n() : nullptr ; } - - - static void unit_test () { // ---------------------- mstr unit_test - - basename_test ( "", "" ); - basename_test ( "/", "/" ); - basename_test ( "//", "/" ); - basename_test ( "///", "/" ); - basename_test ( "a", "a" ); - basename_test ( "/b", "b" ); - basename_test ( "c/", "c" ); - basename_test ( "/d/", "d" ); - basename_test ( "e/f", "f" ); - basename_test ( "/g/h", "h" ); - basename_test ( "abc", "abc" ); - basename_test ( "/def", "def" ); - basename_test ( "ghi/", "ghi" ); - basename_test ( "/jkl/", "jkl" ); - basename_test ( "mno/pqr", "pqr" ); - basename_test ( "/stu/vwx", "vwx" ); - basename_test ( "./xyz", "xyz" ); - - return; } - - -}; // end struct mstr ------------------------------ end struct mstr - - - - -struct Concat_2 { // xxco --------------------------- struct Concat_2 - mfrag s[3]; - Concat_2 ( frag a, frag b = 0, frag c = 0 ) : s{a,b,c} {} -}; // end struct Concat_2 ---------------------- end struct Concat_2 - - - - -Concat_2 s ( frag a, frag b = 0, frag c = 0 ) { // xxs ---- global s - return Concat_2 ( a, b, c ); } - - -Concat_2 s ( str a, str b = 0, str c = 0 ) { // xxs ------- global s - return Concat_2 ( a, b, c ); } - - -opt s2o ( str s ) { // xxs2 -------------------- global function s2o +mopt s2o ( Str s ) { // xxs2 -------------------------------- :: s2o return s2o ( s.s ); } +// end enum opt ------------------------------------------ end enum opt -struct ostr : mstr { // xxos ------------------------- struct ostr - - - int n = 0; - - - ostr () {} // -------------------------------------------- ostr ctor - ostr ( frag o ) : mstr() { * this += o; } - ostr ( const char * o ) : mstr() { * this += o; } - ostr ( const str & o ) : mstr() { * this += o; } - ostr ( const ostr & o ) : mstr() { * this += o; } - ostr ( ostr && o ) : mstr() { s=o.s; n=o.n; o.s=nullptr; o.n=0; } - ostr ( const Concat_2 & o ) : mstr() { * this = o; } - - - ~ostr () { // -------------------------------------------- ostr dtor - free ( (char*) s ); } - - - void operator = ( frag o ) { n=0; * this += o; } // ----- - void operator = ( const ostr & o ) { * this = frag(o); } // ----- - void operator = ( str o ) { * this = frag(o); } // ----- - void operator = ( char * o ) { * this = frag(o); } // ----- - void operator = ( const char * o ) { * this = frag(o); } // ----- - - - void operator = ( const Concat_2 & t ) { - ostr & r = * this; r = t.s[0]; r += t.s[1]; r += t.s[2]; } - - - ostr & operator += ( frag o ) { // --------------------- ostr op += - char * p = (char*) assert ( realloc ( (char*) s, n + o.n + 1 ) ); - memcpy ( p+n, o.s, o.n ); - n += o.n; - p[n] = '\0'; - s = p; - return * this; } - - - static ostr claim ( str s ) { // ----------------------- ostr claim - // return an ostr that owns s. s must be an unowned, malloc()ed string. - ostr rv; rv.s=s.s; rv.n=s.n(); return rv; } - - - static void unit_test (); // ------------------------ ostr unit_test - - -}; // end struct ostr ------------------------------ end struct ostr - - - - -struct Concat { // xxco -------------------------------- class Concat - - ostr s; - - Concat ( frag o ) : s(o) {} // ----------------------- Concat ctor - - operator frag () const { return s; } // Concat cast frag - operator str () const { return s; } // Concat cast str - operator ostr () const { return s; } // Concat cast ostr - bool operator == ( str o ) const { return s == o.s; } // -------- - Concat & operator + ( frag o ) { s += o; return * this; } // - - Concat & operator += ( frag o ) { s += o; return * this; } // - - - -}; // end struct Concat - - -Concat mfrag :: operator + ( frag & o ) const { // ------ mfrag op + - // written as three statements to (1) avoid infinite recursion, and - // (2) allow named return value optimization. - Concat rv (*this); rv += o; return rv; } - - -Concat mstr :: operator + ( frag o ) const { // ---------- mstr op + - return ((frag)*this) + o; } - - -Concat mstr :: operator + ( str o ) const { // ---------- mstr op + - return ((frag)*this) + o; } - - -Concat mstr :: operator + ( const char * const o ) const { // -- op + - return ((frag)*this) + o; } - - -Concat operator + ( const char * a, frag b ) { // ----------- :: op + - return frag(a) + b; } - - -Concat operator + ( const char * a, str b ) { // ------------ :: op + - return frag(a) + b; } - - -void mstr :: basename_test ( str s, str expect ) { // -- basename_test - if ( s.basename() == expect ) { return; } - ostr actual = s.basename(); - printe ( "basename_test failed\n" ); - printe ( " s %s\n", s.s ); - printe ( " expect %s\n", expect.s ); - printe ( " actual %s\n", actual.s ); - abort(); } - - -void ostr :: unit_test () { // ----------------------- ostr unit_test - str a = "a"; - str b = "b"; - ostr ab = a + b; - assert ( ab == "ab" ); - assert ( ab == a+b ); - frag c = "c"; - frag d = "d"; - ostr cd = c + "=" + d; - assert ( cd == "c=d" ); - ostr ef = cd + "ef" + "gh"; - assert ( ef == "c=defgh" ); - assert ( cd == "c=d" ); - ;;; } - - -// end struct Concat -------------------------------- end struct Concat - - - - -struct Argv { // xxar ----------------------------------- struct Argv - - - const char * const * p = nullptr; - - - Argv () {} // -------------------------------------------- Argv ctor - Argv ( const char * const * const p ) : p(p) {} - - - Argv & operator |= ( const Argv & o ) { // ------------- Argv op |= - if ( p == nullptr ) { p = o.p; } return * this; } - - - bool operator == ( const Argv & o ) const { // -------- Argv op == - return p == o.p; } - - - explicit operator bool () const { // ---------------- Argv op bool - return p; } - - - str operator * () const { // --------------------------- Argv op * - return p ? * p : nullptr ; } - - - Argv operator ++ () { // ------------------------------- Argv op ++ - return p && * p ? ++p : nullptr ; } - - - Argv operator ++ (int) { // ---------------------------- Argv op ++ - return p && * p ? p++ : nullptr ; } - - - Argv operator + ( int n ) const { // ------------------- Argv op + - Argv rv ( * this ); - while ( rv.p && rv.p[0] && n-- > 0 ) { rv++; } - return rv; } - - - Argv & operator += ( int n ) { // ---------------------- Argv op += - * this = ( * this ) + n; return * this; } - - - str operator [] ( int n ) const { // ------------------ Argv op [] - return * ( ( * this ) + n ); } - - - ostr concat ( frag sep = " " ) const { // ----------- Argv concat - Argv o ( * this ); - ostr rv; - if ( *(o.p) ) { rv += * o; o++; } - for ( ; *(o.p) ; o++ ) { rv += sep; rv += * o; } - return rv; } - - - str env_get ( str k ) const { // -------------------- Argv env_get - for ( Argv o (*this) ; *(o.p) ; o++ ) { - if ( (*o).env_name() == k ) { return (*o); } } - return nullptr; } - - - void print ( str s ) const { // ----------------------- Argv print - for ( Argv o (*this) ; o && o.p[0] ; o++ ) { - printe ( "%s%s\n", s.s, o[0].s ); } } - - - static void unit_test () { // ---------------------- Argv unit_test - - const char * p[] = { "a=1", "b=2", "c=3", nullptr }; - Argv a(p); - - assert ( a[0] == p[0] ); - assert ( a[1] == p[1] ); - assert ( a[2] == p[2] ); - assert ( a[3] == p[3] ); - - assert ( a[0] == a.p[0] ); - assert ( a[1] == a.p[1] ); - assert ( a[2] == a.p[2] ); - assert ( a[3] == a.p[3] ); - assert ( a.concat() == "a=1 b=2 c=3" ); - - assert ( a.env_get("a") == p[0] ); - assert ( a.env_get("b") == p[1] ); - assert ( a.env_get("c") == p[2] ); - - ;;; } - +typedef unsigned long flags_t; // xxfl -------------- typedef flags_t -}; // end struct Argv ------------------------------ end struct Argv @@ -952,14 +233,14 @@ struct Argv { // xxar ----------------------------------- struct Argv struct Option { // xxop ------------------------------- struct Option - opt type = o_none; // see the below input/type chart for details. - opt mode = o_none; // one of: o_none o_ra o_ro o_rw - mstr arg0, arg1; + mopt type = o_none; // see the below input/type chart for details. + mopt mode = o_none; // one of: o_none o_ra o_ro o_rw + mStr arg0, arg1; Argv command; // nullptr, then set to command when found + Argv p; // used by subclass Option_Reader - Argv p; // points to the next option - mstr newroot; // the newroot - mstr overlay; // the current overlay + mStr newroot; // the newroot + mStr overlay; // the current overlay Option ( Argv p ) : p(p) {} // ----------------------- Option ctor @@ -968,10 +249,16 @@ struct Option { // xxop ------------------------------- struct Option operator bool () const { return type; } // --- Option cast bool - void print ( str m ) const { // --------------------- Option print + void print ( Str m ) const { // --------------------- Option print printe ( "%-8s %-7s %s\n", m.s, o2s(type), arg0.s ); } +protected: + + + Option () {} + + }; // end struct Option -------------------------- end struct Option @@ -980,40 +267,43 @@ struct Option { // xxop ------------------------------- struct Option struct Bind { // xxbi ----------------------------------- struct Bind - opt type = o_none; - opt mode = o_none; // the specified mode ro, rw, ra, none - opt actual = o_none; // the actual mode ro, rw - mstr full; - mstr dst; // 20210619 at present, dst never begins with '/' - ostr src; - ostr newroot_dst; // newroot + dst - Argv p; // points to the next option after this Bind. + mopt type = o_none; + mopt mode = o_none; // the specified mode ro, rw, ra, none + mopt actual = o_none; // the actual mode ro, rw + + mStr full; // the path of the full overlay + mStr dst; // 20210619 at present, dst never begins with '/' + oStr src; + oStr newroot_dst; // newroot + dst + + const Option * option = nullptr; // the source option for this bind void clear () { // ------------------------------------- Bind clear * this = Bind(); } - void print ( str s ) const { // ----------------------- Bind print + void print ( Str s ) const { // ----------------------- Bind print printe ( "%s bind %-7s %-2s %-2s '%s' '%s' '%s'\n", s.s, o2s(type), o2s(mode), o2s(actual), dst.s, src.s, newroot_dst.s ); } Bind & set // ---------------------------------------------- Bind set - ( const Option & o, str childname = 0 ) { + ( const Option & o, Str childname = 0 ) { // 20210620 note: a Bind my outlive the Option o. - str ov = o.overlay; - str a0 = o.arg0; - str a1 = o.arg1; - str cn = childname; + Str ov = o.overlay; + Str a0 = o.arg0; + Str a1 = o.arg1; + Str cn = childname; + + type = o.type; + mode = o.mode; + full = nullptr; + option = & o; - type = o.type; - mode = o.mode; - full = nullptr; - p = o.p; switch ( o.type ) { case o_newroot: dst=""; src=a0; mode=mode||o_ra; break; case o_bind: dst=a0; src=s(a1); break; @@ -1038,7 +328,7 @@ struct Bind { // xxbi ----------------------------------- struct Bind return * this; } - const Bind & trace ( str s ) const { // --------------- Bind trace + const Bind & trace ( Str s ) const { // --------------- Bind trace if ( global_opt_trace ) { print ( s ); } return * this; } @@ -1051,47 +341,47 @@ struct Bind { // xxbi ----------------------------------- struct Bind class Env { // xxen --------------------------------------- class Env - std :: vector < const char * > vec; + std :: vector < mstr > vec; public: - const char * const * data () const { // ----------------- Env data + str * data () const { // -------------------------------- Env data return vec .data(); } - str get ( frag name ) const { // ------------------------- Env get + Str get ( Frag name ) const { // ------------------------- Env get for ( auto & o : vec ) { - if ( str(o) .env_name() == name ) { - return str(o) .tail ( "=" ); } } + if ( Str(o) .env_name() == name ) { + return Str(o) .tail ( "=" ); } } return nullptr; } - void set ( str pair ) { // -------------------------------- Env set - frag name = pair .env_name(); + void set ( Str pair ) { // -------------------------------- Env set + Frag name = pair .env_name(); if ( name.n == 0 ) { return; } for ( auto & o : vec ) { - if ( str(o) .env_name() == name ) { o = pair.s; return; } } + if ( Str(o) .env_name() == name ) { o = pair.s; return; } } if ( vec .size() == 0 ) { vec .push_back ( nullptr ); } vec .back() = pair.s; vec .push_back ( nullptr ); } - void set ( frag name, frag value ) { // ------------------- Env set + void set ( Frag name, Frag value ) { // ------------------- Env set set ( leak ( name + "=" + value ) ); } - void soft ( str pair ) { // ------------------------------ Env soft + void soft ( Str pair ) { // ------------------------------ Env soft if ( not get ( pair .env_name() ) ) { set ( pair ); } } - void soft ( frag name, frag value ) { // ----------------- Env soft + void soft ( Frag name, Frag value ) { // ----------------- Env soft if ( not get ( name ) ) { set ( leak ( name + "=" + value ) ); } } - void soft_copy ( str name ) { // -------------------- Env soft_copy - str pair = Argv ( environ ) .env_get ( name ); + void soft_copy ( Str name ) { // -------------------- Env soft_copy + Str pair = Argv ( environ ) .env_get ( name ); if ( pair ) { soft ( pair ); } } @@ -1103,10 +393,10 @@ class Env { // xxen --------------------------------------- class Env struct Fgetpwent { // xxfg ------------------------- struct Fgetpwent - ostr dir, name, shell; // see man 3 fgetpwent + oStr dir, name, shell; // see man 3 fgetpwent - void fgetpwent ( str path, uid_t uid ) { // -------------- fgetpwent + void fgetpwent ( Str path, uid_t uid ) { // -------------- fgetpwent FILE * f = fopen ( path.s, "r" ); @@ -1138,17 +428,18 @@ struct State { // xxst --------------------------------- struct State const gid_t gid = getgid(); Fgetpwent outside ; // from /etc/passwd outside the lxroot Fgetpwent inside ; // from /etc/passwd inside the lxroot - opt opt_env = o_none; // pass in environment - opt opt_network = o_none; - opt opt_pulse = o_none; - opt opt_root = o_none; - opt opt_write = o_none; - opt opt_x11 = o_none; - opt newroot_mode = o_none; - mstr newroot; - mstr guestname; - mstr chdir; // from the first and only cd option - mstr workdir; // from the last wd option + mopt opt_env = o_none; // pass in environment + mopt opt_network = o_none; + mopt opt_pulse = o_none; + mopt opt_root = o_none; + mopt opt_write = o_none; + mopt opt_x11 = o_none; + mopt newroot_mode = o_none; + bool before_pivot = true; + mStr newroot; + mStr guestname; + mStr chdir; // from the first and only cd option + mStr workdir; // from the last wd option Argv command; @@ -1171,10 +462,10 @@ class Dirent { // xxdi --------------------------------- class Dirent Dirent & operator = ( dirent * pp ) { p = pp; return * this; } - bool operator == ( str s ) const { return name() == s; } + bool operator == ( Str s ) const { return name() == s; } operator bool () const { return p; } ino_t inode () const { return p -> d_ino; } - str name () const { return p -> d_name; } + Str name () const { return p -> d_name; } bool is_dir () const { // -------------------------- Dirent is_dir @@ -1193,29 +484,28 @@ struct Lib { // xxli ------------------------------------- struct Lib // 20210530 apparent redundancy: assert_is_dir vs directory_require - static void assert_is_dir // ---------------------- Lib assert_is_dir - ( const char * const path, const char * const m ) { + static void assert_is_dir ( str path, str m ) { // --- assert_is_dir if ( is_dir ( path ) ) { return; } printe ( "lxroot %s directory not found '%s'\n", m, path ); exit ( 1 ); } static void directory_require // ------------- Lib directory_require - ( str path, str m ) { + ( Str path, Str m ) { if ( Lib :: is_dir ( path ) ) { return; } die1 ( "%s directory not found\n '%s'", m.s, path.s ); } - static bool eq ( const char * a, const char * b ) { // ----- Lib eq + static bool eq ( str a, str b ) { // ----------------------- Lib eq if ( a == NULL || b == NULL ) return false; return strcmp ( a, b ) == 0; } - static ostr getcwd () { // ----------------------------- Lib getcwd - return ostr :: claim ( get_current_dir_name() ); } + static oStr getcwd () { // ----------------------------- Lib getcwd + return oStr :: claim ( get_current_dir_name() ); } - static str getenv ( str name ) { // -------------------- Lib getenv + static Str getenv ( Str name ) { // -------------------- Lib getenv return :: getenv ( name.s ); } @@ -1227,11 +517,11 @@ struct Lib { // xxli ------------------------------------- struct Lib printe ( "%s%s", help, help_more ); exit ( 0 ); } - static str home () { // ---------------------------------- Lib home + static Str home () { // ---------------------------------- Lib home return getenv ( "HOME" ); } - static bool is_dir ( str path ) { // ------------------- Lib is_dir + static bool is_dir ( Str path ) { // ------------------- Lib is_dir struct stat st; if ( path.s && path.n() && stat ( path.s, & st ) == 0 && st .st_mode & S_IFDIR ) { @@ -1240,17 +530,17 @@ struct Lib { // xxli ------------------------------------- struct Lib return false; } - static bool is_empty_dir ( const char * path ) { // --- is_empty_dir + static bool is_empty_dir ( str path ) { // ------------ is_empty_dir if ( not is_dir ( path ) ) { return false; } DIR * dirp = assert ( opendir ( path ) ); for ( struct dirent * p; ( p = readdir ( dirp ) ); ) { - const char * s = p -> d_name; + str s = p -> d_name; if ( eq(s,".") || eq(s,"..") ) { continue; } closedir ( dirp ); return false; } closedir ( dirp ); return true; } - static bool is_file ( str path ) { // ----------------- Lib is_file + static bool is_file ( Str path ) { // ----------------- Lib is_file struct stat st; if ( path && :: stat ( path.s, & st ) == 0 && st .st_mode & S_IFREG ) { @@ -1259,7 +549,7 @@ struct Lib { // xxli ------------------------------------- struct Lib return false; } - static bool is_link ( str path ) { // ----------------- Lib is_link + static bool is_link ( Str path ) { // ----------------- Lib is_link struct stat st; if ( path.s && lstat ( path.s, & st ) == 0 @@ -1268,7 +558,7 @@ struct Lib { // xxli ------------------------------------- struct Lib return false; } - static str readlink ( str path ) { // --------------- Lib readlink + static Str readlink ( Str path ) { // --------------- Lib readlink struct stat st; if ( lstat ( path.s, & st ) == 0 && st .st_mode & S_IFLNK ) { ssize_t lim = st .st_size + 2; @@ -1282,12 +572,6 @@ struct Lib { // xxli ------------------------------------- struct Lib return nullptr; } - /* 20201217 realpath() is not used at present? - static str realpath ( const char * const path ) { // ------ realpath - return ostr :: claim ( :: realpath ( path, nullptr ) ); } - */ - - }; // end struct Lib -------------------------------- end struct Lib @@ -1323,7 +607,7 @@ struct Syscall { // xxsy ----------------------------- struct Syscall pid_t wstatus = 0; - void bind ( str target, str source ) { // ------------ Syscall bind + void bind ( Str target, Str source ) { // ------------ Syscall bind // from /usr/include/linux/mount.h // MS_REC 0x 4000 // MS_BIND 0x 1000 @@ -1350,11 +634,11 @@ struct Syscall { // xxsy ----------------------------- struct Syscall die_pe ( "bind %s %s\n", source.s, target.s ); } - void chdir ( str path ) { // ------------------------ Syscall chdir + void chdir ( Str path ) { // ------------------------ Syscall chdir try1( :: chdir, " chdir %s", path.s ); } - void chroot ( str new_root ) { // ------------------ Syscall chroot + void chroot ( Str new_root ) { // ------------------ Syscall chroot try1( :: chroot, " chroot %s", new_root.s ); } @@ -1362,7 +646,7 @@ struct Syscall { // xxsy ----------------------------- struct Syscall try_quiet ( :: close, " close %d", fd ); } - void execve ( const str pathname, // -------------- Syscall execve + void execve ( const Str pathname, // -------------- Syscall execve const Argv argv, const Argv envp ) { @@ -1390,7 +674,7 @@ struct Syscall { // xxsy ----------------------------- struct Syscall die_pe ( "fork" ); } - void mount ( str source, str target, str filesystemtype ) { // mount + void mount ( Str source, Str target, Str filesystemtype ) { // mount Lib :: directory_require ( target, "target" ); trace1 ( " mount %s %s %s", source.s, target.s, filesystemtype.s ); if ( :: mount ( source.s, target.s, filesystemtype.s, 0, 0 ) == 0 ) { @@ -1398,44 +682,97 @@ struct Syscall { // xxsy ----------------------------- struct Syscall die_pe ( "mount %s %s %s", source.s, target.s, filesystemtype.s ); } - void open ( int * fd, str pathname, const int flags ) { // ---- open + void open ( int * fd, Str pathname, const int flags ) { // ---- open if ( ( * fd = :: open ( pathname.s, flags ) ) >= 0 ) { return; } die_pe ( "open %s %d", pathname.s, flags ); } - void pivot ( str new_root, str put_old ) { // -------- Syscall pivot + void pivot ( Str new_root, Str put_old ) { // -------- Syscall pivot trace1 ( " pivot '%s' '%s'", new_root.s, put_old.s ); - if ( syscall ( SYS_pivot_root, new_root.s, put_old.s ) == 0 ) { return; } + if ( syscall ( SYS_pivot_root, new_root.s, put_old.s ) == 0 ) { + mut .before_pivot = false; return; } die_pe ( "pivot %s %s", new_root.s, put_old.s ); } - void rdonly_20210624 ( str target ) { // --- Syscall rdonly_20210624 - trace1 ( " rdonly '%s'", target.s ); - const flags_t flags = MS_BIND | MS_REMOUNT | MS_RDONLY; - const int rv = :: mount ( NULL, target.s, NULL, flags, NULL ); - if ( rv == 0 ) { return; } - die_pe ( "rdonly %lx %s\n", flags, target.s ); } - - - void rdonly ( str target ) { // -------------------- Syscall rdonly - // 20210624 I do not know a terse way to determine which flags - // are already set. So we iterate through the relevant - // possibilities until one works. Maybe someday I will - // implement a more refined solution. See the file - // fs/namespace.c in the Linux kernel, specifaclly the - // do_reconfigure_mnt() and can_change_locked_flags() - // functions. - const char d = MS_NODEV, e = MS_NOEXEC, s = MS_NOSUID; - const unsigned char masks[] = { 0, d, s, d|s, e, d|e, e|s, d|e|s }; - for ( const auto guess : masks ) { - const flags_t flags = guess | MS_BIND | MS_REMOUNT | MS_RDONLY; - const int rv = :: mount ( NULL, target.s, NULL, flags, NULL ); - if ( rv == 0 ) { - trace1 ( " rdonly %lx '%s'", flags, target.s ); return; } } - die_pe ( "rdonly '%s'\n", target.s ); } - - - void umount2 ( str target, int flags ) { // -------- Syscall umount2 + void rdonly ( Str target ) { // -------------------- Syscall rdonly + struct statfs st; // we will store the current statfs() flags in st. + if ( statfs ( target.s, & st ) not_eq 0 ) { + die_pe ( "rdonly statfs err '%s'\n", target.s ); } + const flags_t flags = ( st_to_ms ( st .f_flags ) + | MS_BIND | MS_REMOUNT | MS_RDONLY ); + if ( :: mount ( NULL, target.s, NULL, flags, NULL ) == 0 ) { + trace1 ( " rdonly %lx '%s'", flags, target.s ); return; } + die_pe ( "rdonly %lx '%s'\n", flags, target.s ); } + + + static flags_t st_to_ms ( flags_t n ) { // ------- Syscall st_to_ms + + // convert a statfs() flag to a mount() flag. (ST_ to MS_ conversion.) + + // see / usr / include / x86_64-linux-gnu / bits / statvfs.h + // see / usr / include / linux / mount.h + + // flags from man 2 statfs flags from man 2 mount + // ST_RDONLY 1 MS_RDONLY 1 + // ST_NOSUID 2 MS_NOSUID 2 + // ST_NODEV 4 MS_NODEV 4 + // ST_NOEXEC 8 MS_NOEXEC 8 + // ST_SYNCHRONOUS 16 MS_SYNCHRONOUS 16 + // ST_MANDLOCK 64 MS_MANDLOCK 64 + // ST_NOATIME 1024 MS_NOATIME 1024 + // ST_NODIRATIME 2048 MS_NODIRATIME 2048 + // ST_RELATIME 4096 MS_RELATIME 1<<21 + + // note on x86_64 ST_RELATIME != MS_RELATIME + // note on x86_64 ST_RELATIME == MS_BIND == 4096 + + // the below verbose yet simple implementation should optimize well. + + #define if_equal( a, b ) ( a == b ? b : 0 ) + #define if_not_equal( a, b ) ( ( a != b ) && ( n & a ) ? b : 0 ) + + constexpr flags_t copy_these_bits = + if_equal ( ST_RDONLY, MS_RDONLY ) + | if_equal ( ST_NOSUID, MS_NOSUID ) + | if_equal ( ST_NODEV, MS_NODEV ) + | if_equal ( ST_NOEXEC, MS_NOEXEC ) + | if_equal ( ST_SYNCHRONOUS, MS_SYNCHRONOUS ) + | if_equal ( ST_MANDLOCK, MS_MANDLOCK ) + | if_equal ( ST_NOATIME, MS_NOATIME ) + | if_equal ( ST_NODIRATIME, MS_NODIRATIME ) + | if_equal ( ST_RELATIME, MS_RELATIME ); + + const flags_t shifted_bits = + if_not_equal ( ST_RDONLY, MS_RDONLY ) + | if_not_equal ( ST_NOSUID, MS_NOSUID ) + | if_not_equal ( ST_NODEV, MS_NODEV ) + | if_not_equal ( ST_NOEXEC, MS_NOEXEC ) + | if_not_equal ( ST_SYNCHRONOUS, MS_SYNCHRONOUS ) + | if_not_equal ( ST_MANDLOCK, MS_MANDLOCK ) + | if_not_equal ( ST_NOATIME, MS_NOATIME ) + | if_not_equal ( ST_NODIRATIME, MS_NODIRATIME ) + | if_not_equal ( ST_RELATIME, MS_RELATIME ); + + #undef if_equal + #undef if_not_equal + + return ( n & copy_these_bits ) | shifted_bits; } + + + static void st_to_ms_unit_test () { // ---------- st_to_ms_unit_test + assert ( st_to_ms ( ST_RDONLY ) == MS_RDONLY ); + assert ( st_to_ms ( ST_NOSUID ) == MS_NOSUID ); + assert ( st_to_ms ( ST_NODEV ) == MS_NODEV ); + assert ( st_to_ms ( ST_NOEXEC ) == MS_NOEXEC ); + assert ( st_to_ms ( ST_SYNCHRONOUS ) == MS_SYNCHRONOUS ); + assert ( st_to_ms ( ST_MANDLOCK ) == MS_MANDLOCK ); + assert ( st_to_ms ( ST_NOATIME ) == MS_NOATIME ); + assert ( st_to_ms ( ST_NODIRATIME ) == MS_NODIRATIME ); + assert ( st_to_ms ( ST_RELATIME ) == MS_RELATIME ); + return; } + + + void umount2 ( Str target, int flags ) { // -------- Syscall umount2 try1( :: umount2, " umount2 %s 0x%x", target.s, flags ); } @@ -1456,7 +793,11 @@ struct Syscall { // xxsy ----------------------------- struct Syscall void write ( int fd, const void * buf, ssize_t count ) { // -- write assert ( count >= 0 ); if ( :: write ( fd, buf, count ) == count ) { return; } - die_pe ( "write %d %ld", fd, count ); } + die_pe ( "write %d %ld", fd, (long int) count ); } + + + static void unit_test () { // ------------------- Syscall unit_test + st_to_ms_unit_test(); } }; // end struct Syscall ------------------------ end struct Syscall @@ -1470,11 +811,14 @@ Syscall sys; // xxsy -------------------------------------- global sys struct Option_Reader // xxop -------------------- struct Option_Reader : private Option { + const Option & o; // const access to the Option base class + - const Option & o; // const access to Option + Option_Reader ( Argv p ) : Option(p), o(*this) {} // -------- ctor - Option_Reader ( Argv p ) : Option(p), o(*this) {} // ---------- ctor + Option_Reader ( const Option * o ) : o(*this) { // ----------- ctor + * (Option*) this = * o; } const Option & next () { // -------------------- Option_Reader next @@ -1490,7 +834,7 @@ struct Option_Reader // xxop -------------------- struct Option_Reader // // -- o_ - -- - // -short o_shortopt - -short - - // n=v o_setenv - n=v - + // name=value o_setenv - name=value - // [mode] path o_newroot mode path - // [mode] path o_full mode path - // [mode] path o_partial mode path - @@ -1527,7 +871,7 @@ struct Option_Reader // xxop -------------------- struct Option_Reader if ( arg0.startswith("-") ) { type=o_shortopt; p++; return; } if ( is_setenv() ) { type=o_setenv; p++; return; } - path_or_command(); } + path(); } void do_command () { // ------------------ Option_Reader do_command @@ -1536,7 +880,7 @@ struct Option_Reader // xxop -------------------- struct Option_Reader bool is_setenv () { // -------------------- Option_Reader is_setenv - const char * var_name_allowed = + str var_name_allowed = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"; const int n = arg0.spn ( var_name_allowed ); return n > 0 && n < arg0.n() && arg0[n] == '='; } @@ -1561,12 +905,6 @@ struct Option_Reader // xxop -------------------- struct Option_Reader else { type=o_newroot; newroot=arg0; } } - void path_or_command () { // -------- Option_Reader path_or_command - // path_or_command - bool found = Lib::is_dir( overlay ? overlay + "/" + arg0 : arg0 ); - found ? path() : do_command(); } - - }; // end class Option_Reader -------------- end class Option_Reader @@ -1575,7 +913,7 @@ struct Option_Reader // xxop -------------------- struct Option_Reader struct Logic : Lib { // xxlo ------------------------- struct Logic - static opt actual ( str path, const opt mode ) { // -- Logic actual + static mopt actual ( Str path, opt mode ) { // ------- Logic actual return ( mode == o_rw || is_inside_workdir ( path ) || ( mode == o_ra @@ -1586,27 +924,44 @@ struct Logic : Lib { // xxlo ------------------------- struct Logic static void binds ( sink fn ) { // -------------- Logic binds - auto fn2 = [&] ( Bind & raw ) { - if ( raw .type == o_full ) { binds_full ( fn, raw ); return; } - if ( is_shadowed ( raw.dst, raw.p ) ) { return; } - mfrag parent; - if ( raw .mode == o_none ) { - // raw .trace ( "binds 1 " ); - calculate_parent ( raw .dst, parent, raw .mode ); - // raw .trace ( "binds 2 " ); - ;;; } - raw .actual = actual ( raw.dst, raw.mode ); - fn ( raw ); }; - binds_raw ( fn2 ); } + + // Transform each command line Option into zero or more Binds. + // Pass each Bind to fn. + + Bind b; + + auto single = [&] () { + if ( is_overbound ( b ) ) { return; } + if ( b.mode == o_none ) { + mFrag parent; calculate_parent ( b.dst, parent, b.mode ); } + b.actual = actual ( b.dst, b.mode ); + fn ( b ); }; + + auto full = [&] ( const Option & o ) { + scandir ( o.arg0, [&](auto e) { + if ( e.is_dir() ) { + b.set ( o, e.name() ); + if ( is_dir ( b.newroot_dst ) ) { single(); } } } ); }; + + options ( [&]( const Option & o ) { + switch ( o.type ) { + case o_newroot: // fallthrough to o_bind + case o_partial: // fallthrough to o_bind + case o_bind: b.set(o); single(); break; + case o_full: full(o); break; + default: break; } } ); } static void calculate_parent // -------------- Logic calculate_parent - ( str child, mfrag & parent, opt & mode ) { + ( Str child, mFrag & parent, mopt & mode ) { // Find/calculate the path and specified mode of the Bind that // contains path child. Since each Bind may inherit from its parent, // we must descend to child, one step at a time. + // child is the full dst of the bind. + // parent will be set to the full dst of the Bind that contains child. + auto is_descendant = [&] ( const Bind & raw ) { return child .is_inside ( raw .dst ) && ( parent == nullptr || raw.dst.n() > parent.n ); }; @@ -1615,29 +970,42 @@ struct Logic : Lib { // xxlo ------------------------- struct Logic parent = raw .dst; // .dst is eternal becaus .type != o_full mode = raw .mode || mode; }; - auto full = [&] ( const Bind & full ) { - if ( parent .n > 0 ) { return; } - frag basedir = child .skip_all('/') .capture_until('/'); - if ( not is_dir ( ostr(s( full.full, "/", basedir )) ) ) { return; } - if ( is_shadowed ( basedir, full.p ) ) { return; } - parent = basedir; - mode = full.mode || mode ; }; + Bind b; - auto consider = [&] ( const Bind & raw ) { - if ( raw .type == o_full ) { return full ( raw ); } - if ( is_shadowed ( raw.dst, raw.p ) ) { return; } - if ( is_descendant ( raw ) ) { descend_to ( raw ); } }; + auto single = [&] () { + if ( is_overbound ( b ) ) { return; } + if ( is_descendant ( b ) ) { descend_to ( b ); } }; - binds_raw ( consider ); } + oStr trunk; + auto full = [&] ( const Option & o ) { + ;;; + trunk = child .trunk(); // trunk is a subdir of root + b.set ( o, trunk ); + if ( not is_dir ( b.newroot_dst ) ) { return; } + if ( is_overbound ( b ) ) { return; } + parent = trunk; + mode = b.mode || mode ; }; - static void options ( sink