├── .github
└── workflows
│ ├── test.yaml
│ └── vagrant.yaml
├── .gitignore
├── LICENSE
├── Makefile.in
├── README.md
├── STYLE.md
├── Vagrantfile
├── completions
├── README.md
└── try.bash
├── config.guess
├── config.sub
├── configure.ac
├── docs
├── try.1.md
├── try_logo.png
└── try_pip_install_example.gif
├── install-sh
├── man
└── .gitignore
├── package.nix
├── scripts
├── README.md
├── check_trycase.sh
├── check_version.sh
├── lint.sh
└── run_tests.sh
├── shell.nix
├── test
├── README.md
├── all-commit-cases.sh
├── command_substitution.sh
├── dev_urandom.sh
├── empty_summary.sh
├── exit_status.sh
├── explore.sh
├── fail.sh.skip
├── hidden_variables.sh
├── ignore_flag.sh
├── merge_multiple_dirs.sh
├── missing_unionfs_mergerfs.sh
├── mkdir_on_file.sh
├── network.sh
├── newline-filename.sh
├── nonexistent_sandbox.sh
├── pipeline.sh
├── resources
│ └── file.txt.gz
├── reuse_problematic_sandbox.sh
├── reuse_sandbox.sh
├── stdstream.sh
├── summary.sh
├── symlinks.sh
├── tempfiles.sh
├── toplevel-perms.sh
├── touch_and_rm_D_flag_commit.sh
├── touch_and_rm_no_flag.sh
├── touch_and_rm_with_cleanup.sh.skip
├── try-commit-c.sh
├── unzip_D_flag_commit.sh
├── unzip_D_flag_commit_without_cleanup.sh
└── unzip_no_flag.sh
├── try
└── utils
├── .gitignore
├── README.md
├── ignores.c
├── ignores.h
├── try-commit.c
├── try-summary.c
└── version.h
/.github/workflows/test.yaml:
--------------------------------------------------------------------------------
1 | name: Main workflow
2 |
3 | on:
4 | pull_request:
5 | push:
6 | schedule:
7 | - cron: '17 14 * * *'
8 |
9 | jobs:
10 | dist:
11 | strategy:
12 | fail-fast: true
13 |
14 | runs-on: ubuntu-latest
15 | if: github.event.pull_request.draft == false
16 |
17 | steps:
18 | - name: Allow unprivileged user namespaces (for Ubuntu 24.04)
19 | run: |
20 | sudo sysctl kernel.apparmor_restrict_unprivileged_userns=0
21 |
22 | - name: Install dependencies
23 | run: |
24 | sudo apt-get install expect mergerfs attr pandoc
25 |
26 | - name: Checkout
27 | uses: actions/checkout@v4
28 | with:
29 | ref: ${{ github.event.pull_request.head.sha }}
30 |
31 | - name: Check autoscan
32 | run: |
33 | autoscan | tee autoscan.out
34 | ! [ -s autoscan.out ]
35 |
36 | - name: Configure and build manpage
37 | run: |
38 | autoconf
39 | ./configure
40 | make dist
41 |
42 | - name: Upload dist tarball
43 | uses: actions/upload-artifact@v4
44 | with:
45 | name: try-dist.tgz
46 | path: try-*.tgz
47 |
48 | test-dist:
49 | needs: dist
50 | strategy:
51 | fail-fast: false
52 |
53 | runs-on: ubuntu-latest
54 | if: github.event.pull_request.draft == false
55 |
56 | steps:
57 | - name: Allow unprivileged user namespaces (for Ubuntu 24.04)
58 | run: |
59 | sudo sysctl kernel.apparmor_restrict_unprivileged_userns=0
60 |
61 | - name: Install dependencies
62 | run: |
63 | sudo apt-get install expect mergerfs attr pandoc
64 |
65 | - name: Download dist tarball
66 | uses: actions/download-artifact@v4
67 |
68 | - name: Unpack tarball; configure and build utilities
69 | run: |
70 | tar xzf try-dist.tgz/try-*.tgz --strip-components=1
71 | rm -r try-dist.tgz
72 | ./configure
73 | make all
74 |
75 | - name: Run tests with shell fallbacks
76 | run: |
77 | ! which try-summary
78 | ! which try-commit
79 | scripts/run_tests.sh
80 |
81 | - name: Install utilities
82 | run: |
83 | sudo make install
84 |
85 | - name: Run tests with utilities
86 | run: |
87 | which try-summary
88 | which try-commit
89 | scripts/run_tests.sh
90 |
91 | - name: Check manpage
92 | run: |
93 | man -w try
94 |
95 | test-checkout:
96 | strategy:
97 | fail-fast: false
98 |
99 | runs-on: ubuntu-latest
100 | if: github.event.pull_request.draft == false
101 |
102 | steps:
103 | - name: Allow unprivileged user namespaces (for Ubuntu 24.04)
104 | run: |
105 | sudo sysctl kernel.apparmor_restrict_unprivileged_userns=0
106 |
107 | - name: Install dependencies
108 | run: |
109 | sudo apt-get install expect mergerfs attr pandoc
110 |
111 | - name: Checkout
112 | uses: actions/checkout@v4
113 | with:
114 | ref: ${{ github.event.pull_request.head.sha }}
115 |
116 | - name: Configure and build utilities
117 | run: |
118 | autoconf
119 | ./configure
120 | make all
121 |
122 | - name: Run tests with shell fallbacks
123 | run: |
124 | ! which try-summary
125 | ! which try-commit
126 | scripts/run_tests.sh
127 |
128 | - name: Install utilities
129 | run: |
130 | sudo make install
131 |
132 | - name: Run tests with utilities
133 | run: |
134 | which try-summary
135 | which try-commit
136 | scripts/run_tests.sh
137 |
138 | - name: Check manpage
139 | run: |
140 | man -w try
141 |
142 | lint:
143 | runs-on: ubuntu-latest
144 |
145 | steps:
146 | - name: Checkout
147 | uses: actions/checkout@v4
148 | with:
149 | ref: ${{ github.event.pull_request.head.sha }}
150 |
151 | - name: Run scripts/lint.sh
152 | run: scripts/lint.sh
153 |
154 | trycase-check:
155 | runs-on: ubuntu-latest
156 | steps:
157 | - uses: actions/checkout@v4
158 | with:
159 | ref: ${{ github.event.pull_request.head.sha }}
160 |
161 | - name: Run check_trycase.sh
162 | run: scripts/check_trycase.sh
163 |
164 | version-check:
165 | runs-on: ubuntu-latest
166 | if: github.event.pull_request.draft == false
167 |
168 | steps:
169 | - name: Checkout
170 | uses: actions/checkout@v4
171 | with:
172 | ref: ${{ github.event.pull_request.head.sha }}
173 |
174 | - name: Check version consistency (script, manpage, include)
175 | run: scripts/check_version.sh
176 |
177 | shellcheck:
178 | runs-on: ubuntu-latest
179 | steps:
180 | - name: Checkout
181 | uses: actions/checkout@v4
182 | with:
183 | ref: ${{ github.event.pull_request.head.sha }}
184 |
185 | - name: Run ShellCheck
186 | uses: ludeeus/action-shellcheck@master
187 | with:
188 | ignore_paths: >-
189 | completions
190 | ./configure
191 | ./install-sh
192 |
193 | nix:
194 | runs-on: ubuntu-latest
195 | steps:
196 | - name: Checkout
197 | uses: actions/checkout@v4
198 | with:
199 | ref: ${{ github.event.pull_request.head.sha }}
200 |
201 | - name: Allow unprivileged user namespaces (for Ubuntu 24.04)
202 | run: |
203 | sudo sysctl kernel.apparmor_restrict_unprivileged_userns=0
204 |
205 | - name: Install Nix
206 | uses: DeterminateSystems/nix-installer-action@main
207 |
208 | - name: Run tests
209 | run: |
210 | nix-shell --run "sh scripts/run_tests.sh"
211 |
212 |
213 | prerelease:
214 | needs:
215 | - test-checkout
216 | - test-dist
217 | - version-check
218 | - lint
219 | - shellcheck
220 | - trycase-check
221 | runs-on: ubuntu-latest
222 | if: ${{ github.ref == 'refs/heads/main' }}
223 |
224 | steps:
225 | - name: Download binaries
226 | uses: actions/download-artifact@v4
227 |
228 | - name: Rename tarball
229 | run: |
230 | mv try-dist.tgz/try-*.tgz try-latest.tgz
231 | rmdir try-dist.tgz
232 |
233 | - name: Deploy 'latest' release
234 | uses: "marvinpinto/action-automatic-releases@latest"
235 | with:
236 | repo_token: "${{ secrets.GITHUB_TOKEN }}"
237 | automatic_release_tag: "latest"
238 | prerelease: true
239 | title: "Latest distribution tarball"
240 | files: |
241 | try-latest.tgz
242 |
--------------------------------------------------------------------------------
/.github/workflows/vagrant.yaml:
--------------------------------------------------------------------------------
1 | name: Distro and LVM tests
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 | schedule:
9 | - cron: '17 20 * * *'
10 |
11 | jobs:
12 | vagrant:
13 | strategy:
14 | fail-fast: false
15 | matrix:
16 | vagrant_target:
17 | - debian
18 | - debianrustup
19 | - debianlvm
20 | - rocky9
21 | - fedora39
22 | runs-on: self-hosted
23 | steps:
24 | - name: Checkout
25 | uses: actions/checkout@v4
26 |
27 | - name: Cleanup
28 | run: bash /home/runner/cleanup.sh
29 |
30 | - name: Start vagrant box
31 | run: vagrant up ${{ matrix.vagrant_target }}
32 |
33 | - name: Stop vagrant box
34 | run: vagrant destroy -f ${{ matrix.vagrant_target }}
35 | if: ${{ success() || failure() }} # we want to run this step even if the last step failed
36 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | test/bash_workspace/
2 | test/results/
3 | test/try_workspace/
4 | *~
5 | .vagrant/
6 | .vagrant
7 | autom4te.cache/
8 | config.log
9 | config.status
10 | Makefile
11 | configure.scan
12 | autoscan.log
13 | configure
14 | try-0.2.0.tgz
15 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 The PaSh Authors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile.in:
--------------------------------------------------------------------------------
1 | prefix=@prefix@
2 | exec_prefix=@exec_prefix@
3 | bindir=@bindir@
4 | datarootdir=@datarootdir@
5 | mandir=@mandir@
6 |
7 | CC=@CC@
8 | CFLAGS=@CFLAGS@
9 | CPPFLAGS=@CPPFLAGS@
10 | INSTALL=@INSTALL@
11 |
12 | .PHONY: all install test dist lint clean
13 |
14 | DISTDIR=@PACKAGE_TARNAME@-@PACKAGE_VERSION@
15 | DISTTGZ=$(DISTDIR).tgz
16 |
17 | TARGETS=$(if $(findstring yes,@enable_utils@),utils/try-summary utils/try-commit) man/try.1.gz
18 |
19 | all: $(TARGETS)
20 |
21 | install: $(TARGETS)
22 | $(INSTALL) -d $(bindir)
23 | $(INSTALL) -m 755 try $(bindir)
24 | ifeq (@enable_utils@, yes)
25 | $(INSTALL) -m 755 utils/try-summary $(bindir)
26 | $(INSTALL) -m 755 utils/try-commit $(bindir)
27 | endif
28 | $(INSTALL) -d $(mandir)/man1
29 | $(INSTALL) -m 644 man/try.1.gz $(mandir)/man1
30 |
31 | dist: $(DISTTGZ)
32 |
33 | $(DISTTGZ): $(DISTDIR)
34 | tar czf $@ $<
35 | rm -rf $(DISTDIR)
36 |
37 | $(DISTDIR): man/try.1 man/try.1.gz
38 | mkdir $@
39 | cp configure configure.ac config.guess config.sub install-sh LICENSE Makefile.in README.md STYLE.md try Vagrantfile $@
40 | cp -R completions $@
41 | cp -R docs $@
42 | cp -R man $@
43 | cp -R scripts $@
44 | cp -R test $@
45 | cp -R utils $@
46 |
47 | man/try.1.gz: man/try.1
48 | cat $< | gzip >$@
49 |
50 | man/try.1: docs/try.1.md
51 | pandoc --standalone --from markdown-smart --to man $< -o $@
52 |
53 | utils/try-summary: utils/ignores.o utils/try-summary.o
54 | $(CC) $(CPPFLAGS) $(CFLAGS) -o $@ -g $^
55 |
56 | utils/try-commit: utils/ignores.o utils/try-commit.o
57 | $(CC) $(CPPFLAGS) $(CFLAGS) -o $@ -g $^
58 |
59 | test: try utils/try-summary utils/try-commit
60 | scripts/run_tests.sh
61 |
62 | lint:
63 | scripts/lint.sh
64 |
65 | clean:
66 | ifeq (@enable_manpage@, yes)
67 | -rm man/try.1 man/try.1.gz
68 | endif
69 | -rm utils/*.o
70 | -rm utils/try-summary utils/try-commit
71 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # try
2 |
3 |
4 |
5 | "Do, or do not. There is no try."
6 |
7 | We're setting out to change that: `try cmd` and commit---or not.
8 |
9 | ## Description
10 | [](https://github.com/binpash/try/actions/workflows/test.yaml)
11 | [](#license)
12 | [](https://github.com/binpash/try/issues)
13 |
14 | `try` lets you run a command and inspect its effects before changing your live system. `try` uses Linux's [namespaces (via `unshare`)](https://docs.kernel.org/userspace-api/unshare.html) and the [overlayfs](https://docs.kernel.org/filesystems/overlayfs.html) union filesystem.
15 |
16 | Please note that `try` is a prototype and not a full sandbox, and should not be used to execute
17 | commands that you don't already trust on your system, (i.e. network calls are all allowed)
18 |
19 |
20 |
21 | ## Getting Started
22 |
23 | ### Dependencies
24 |
25 | `try` relies on the following Debian packages
26 |
27 | * `attr` (for `getfattr`)
28 | * `pandoc` and `autoconf` (if working from a GitHub clone)
29 |
30 | In cases where overlayfs doesn't work on nested mounts, you will need either
31 | [mergerfs](https://github.com/trapexit/mergerfs) or [unionfs](https://github.com/rpodgorny/unionfs-fuse). `try` should be able to autodetect them, but you can specify the path to mergerfs or unionfs with -U (e.g. `try -U ~/.local/bin/unionfs`)
32 |
33 | To run `try`'s test suite (`scripts/run_tests.sh`), you will need:
34 |
35 | * `bash`
36 | * `expect`
37 | * `curl`
38 |
39 | `try` has been tested on the following distributions:
40 |
41 | * `Ubuntu 20.04 LTS` or later
42 | * `Debian 12`
43 | * `Fedora 38`
44 | * `Centos 9 Stream 5.14.0-325.el9`
45 | * `Arch 6.1.33-1-lts`
46 | * `Alpine 6.1.34-1-lts`
47 | * `Rocky 9 5.14.0-284.11.1.el9_2`
48 | * `SteamOS 3.4.8 5.13.0-valve36-1-neptune`
49 |
50 | *Note that try will only work on [Linux
51 | 5.11](https://github.com/torvalds/linux/commit/92dbc9dedccb9759c7f9f2f0ae6242396376988f)
52 | or higher for overlayfs to work in a user namespace.*
53 |
54 | ### Installing
55 |
56 | There are three ways to install try:
57 |
58 | 1. **The quick and janky way: grab the script.** You only need the [`try` script](https://raw.githubusercontent.com/binpash/try/main/try). Put it in your `PATH` and you're ready to go. You won't have documentation or utility support, but it should work as is.
59 |
60 | 2. **By cloning the repository.** Run the following:
61 | ```ShellSession
62 | $ git clone https://github.com/binpash/try.git
63 | $ autoconf && ./configure && make && sudo make install
64 | ```
65 | You should now have a fully featured `try`, including the support utilities (which should help `try` run faster) and manpage. Run `make test` to confirm that everything works.
66 |
67 | 3. **By using a source distribution.** Download `try-XXX.tgz` from [the release page](https://github.com/binpash/try/releases). You can get the latest prerelease by downloading [`try-latest.tgz`](https://github.com/binpash/try/releases/download/latest/try-latest.tgz). You can then install similarly to the above:
68 | ```ShellSession
69 | $ git clone https://github.com/binpash/try.git
70 | $ ./configure && make && sudo make install
71 | ```
72 |
73 | The repository and source distribution are slightly different: the repository does not include the `configure` script (generated by `autoconf`) or the manpage (generated by `pandoc`), but the source distribution does.
74 |
75 | #### Arch Linux
76 |
77 | `try` is present in [AUR](https://aur.archlinux.org/packages/try), you can install it with your preferred AUR helper:
78 |
79 | ```shellsession
80 | yay -S try
81 | ```
82 |
83 | or manually:
84 |
85 | ```shellsession
86 | git clone https://aur.archlinux.org/try.git
87 | cd try
88 | makepkg -sic
89 | ```
90 |
91 | #### Nix/NixOS
92 |
93 | `try` is present in [nixpkgs](https://search.nixos.org/packages?channel=unstable&show=try&from=0&size=50&sort=relevance&type=packages&query=try) and maintained by us.
94 |
95 | ```shellsession
96 | nix-shell -p try
97 | ```
98 |
99 | ## Example Usage
100 |
101 | `try` is a higher-order command, like `xargs`, `exec`, `nohup`, or `find`. For example, to install a package via `pip3`, you can invoke `try` as follows:
102 |
103 | ```ShellSession
104 | $ try pip3 install libdash
105 | ... # output continued below
106 | ```
107 |
108 | By default, `try` will ask you to commit the changes made at the end of its execution.
109 |
110 | ```ShellSession
111 | ...
112 | Defaulting to user installation because normal site-packages is not writeable
113 | Collecting libdash
114 | Downloading libdash-0.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (254 kB)
115 | ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 254.6/254.6 KB 2.1 MB/s eta 0:00:00
116 | Installing collected packages: libdash
117 | Successfully installed libdash-0.3.1
118 | WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
119 |
120 | Changes detected in the following files:
121 |
122 | /tmp/tmp.zHCkY9jtIT/upperdir/home/gliargovas/.local/lib/python3.10/site-packages/libdash/ast.py (modified/added)
123 | /tmp/tmp.zHCkY9jtIT/upperdir/home/gliargovas/.local/lib/python3.10/site-packages/libdash/_dash.py (modified/added)
124 | /tmp/tmp.zHCkY9jtIT/upperdir/home/gliargovas/.local/lib/python3.10/site-packages/libdash/__init__.py (modified/added)
125 | /tmp/tmp.zHCkY9jtIT/upperdir/home/gliargovas/.local/lib/python3.10/site-packages/libdash/__pycache__/printer.cpython-310.pyc (modified/added)
126 | /tmp/tmp.zHCkY9jtIT/upperdir/home/gliargovas/.local/lib/python3.10/site-packages/libdash/__pycache__/ast.cpython-310.pyc (modified/added)
127 |
128 |
129 | Commit these changes? [y/N] y
130 | ```
131 |
132 | Sometimes, you might want to pre-execute a command and commit its result at a later time. Running `try -n` will print the overlay directory on STDOUT without committing the result.
133 |
134 | ```ShellSession
135 | $ try -n "curl https://sh.rustup.rs | sh"
136 | /tmp/tmp.uCThKq7LBK
137 | ```
138 |
139 | Alternatively, you can specify your own existing overlay directory using the `-D [dir]` flag:
140 |
141 | ```ShellSession
142 | $ mkdir rustup-sandbox
143 | $ try -D rustup-sandbox "curl https://sh.rustup.rs | sh"
144 | $ ls rustup-sandbox
145 | temproot upperdir workdir
146 | ```
147 |
148 | As you can see from the output above, `try` has created an overlay environment in the `rustup-sandbox` directory.
149 |
150 | Manually inspecting upperdir reveals the changes to the files made inside the overlay during the execution of the previous command with *try*:
151 |
152 | ```ShellSession
153 | ~/try/rustup-sandbox/upperdir$ du -hs .
154 | 1.2G .
155 | ```
156 |
157 | You can inspect the changes made inside a given overlay directory using `try`:
158 |
159 | ```ShellSession
160 | $ try summary rustup-sandbox/ | head
161 |
162 | Changes detected in the following files:
163 |
164 | rustup-sandbox//upperdir/home/ubuntu/.profile (modified/added)
165 | rustup-sandbox//upperdir/home/ubuntu/.bashrc (modified/added)
166 | rustup-sandbox//upperdir/home/ubuntu/.rustup/update-hashes/stable-x86_64-unknown-linux-gnu (modified/added)
167 | rustup-sandbox//upperdir/home/ubuntu/.rustup/settings.toml (modified/added)
168 | rustup-sandbox//upperdir/home/ubuntu/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/libstd-8389830094602f5a.so (modified/added)
169 | rustup-sandbox//upperdir/home/ubuntu/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/etc/lldb_commands (modified/added)
170 | rustup-sandbox//upperdir/home/ubuntu/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/etc/gdb_lookup.py (modified/added)
171 | ```
172 |
173 | You can also choose to commit the overlay directory contents:
174 |
175 | ```ShellSession
176 | $ try commit rustup-sandbox
177 | ```
178 |
179 | You can also run `try explore` to open your current shell in try, or `/try
180 | explore /tmp/tmp.X6OQb5tJwr` to explore an existing sandbox.
181 |
182 | To specify multiple lower directories for overlay (by merging them together), you can use the `-L` (implies `-n`) flag followed by a colon-separated list of directories. The directories on the left have higher precedence and can overwrite the directories on the right:
183 |
184 | ```ShellSession
185 | $ try -D /tmp/sandbox1 "echo 'File 1 Contents - sandbox1' > file1.txt"
186 | $ try -D /tmp/sandbox2 "echo 'File 2 Contents - sandbox2' > file2.txt"
187 | $ try -D /tmp/sandbox3 "echo 'File 2 Contents - sandbox3' > file2.txt"
188 |
189 | # Now use the -L flag to merge both sandbox directories together, with sandbox3 having precedence over sandbox2
190 | $ try -L "/tmp/sandbox3:/tmp/sandbox2:/tmp/sandbox1" "cat file1.txt file2.txt"
191 | File 1 Contents - sandbox1
192 | File 2 Contents - sandbox3
193 | ```
194 |
195 | In this example, `try` will merge `/sandbox1`, `/sandbox2` and `/sandbox3` together before mounting the overlay. This way, you can combine the contents of multiple `try` sandboxes.
196 |
197 |
198 | ## Known Issues
199 | Any command that interacts with other users/groups will fail since only the
200 | current user's UID/GID are mapped. However, the [future
201 | branch](https://github.com/binpash/try/tree/future) has support for uid/mapping;
202 | please refer to the that branch's readme for installation instructions for the
203 | uid/gidmapper (root access is required for installation).
204 |
205 | Shell quoting may be unintuitive, you may expect `try bash -c "echo a"` to work,
206 | however, try will actually execute `bash -c echo a`, which will not result in
207 | `a` being printed. [We are currently not planning on resolving this
208 | behavior.](https://github.com/binpash/try/issues/155#issuecomment-2078556237)
209 |
210 | Please also report any issue you run into while using the future branch!
211 |
212 | ## Version History
213 |
214 | * v0.2.0 - 2023-07-24
215 | - Refactor tests.
216 | - Improved linting.
217 | - Hide `try`-internal variables from scripts.
218 | - Style guide.
219 | - Testing in Vagrant.
220 | - Support nested mounts.
221 | - Resolve issues with `userxattr`.
222 | - Better support for `unionfs`.
223 | - Use `/bin/sh`, not `/bin/bash`.
224 | - `-i` flag to ignore paths.
225 | - Interactive improvements.
226 |
227 | * v0.1.0 - 2023-06-25
228 | - Initial release.
229 |
230 | ## See Also
231 |
232 | [checkinstall](http://checkinstall.izto.org/) (unmaintained)
233 |
234 | checkinstall keeps track of all the files created or modified by your
235 | installation script, builds a standard binary package and installs it in your
236 | system. This package can then be easily installed, managed, and removed using
237 | the package manager of your Linux distribution. It helps in maintaining a clean
238 | and organized system by keeping track of installed software and its
239 | dependencies.
240 |
241 | ## License
242 |
243 | This project is licensed under the MIT License - see LICENSE for details.
244 |
245 | Copyright (c) 2023 The PaSh Authors.
246 |
--------------------------------------------------------------------------------
/STYLE.md:
--------------------------------------------------------------------------------
1 | The [POSIX
2 | shell](https://pubs.opengroup.org/onlinepubs/9699919799/idx/shell.html)
3 | is a tricky programming language. Here are the style guidelines for
4 | `try`.
5 |
6 | # Indentation and whitespace
7 |
8 | Indent four spaces. Do not use tabs. Blank lines should have no
9 | whitespace; there should be no trailing whitespace on lines. Try to
10 | avoid line continuations.
11 |
12 | Align the commands of a case:
13 |
14 | ```sh
15 | case "$1" in
16 | (summary) : ${SANDBOX_DIR=$2}
17 | summary;;
18 | (commit) : ${SANDBOX_DIR=$2}
19 | commit;;
20 | (explore) : ${SANDBOX_DIR=$2}
21 | try "$SHELL";;
22 | (--) shift
23 | try "$@";;
24 | (*) try "$@";;
25 | esac
26 | ```
27 |
28 | # Write POSIX code
29 |
30 | Do not use:
31 |
32 | - `<(command substitutions)`
33 | - `(( static arithmetic expression ))`
34 | - `[[ static test expressions ]]`
35 | - `array=(assignment)` or `${array[access]}`
36 | - the `function` keyword
37 | - `brace{expansion,}`
38 | - `${param:offset}`, `${!matching}`, `${pattern/substi/tution}`
39 | - `source` (it's called `.`)
40 |
41 | Do not use `LOCAL_ASSIGNMENT=foo f` when `f` could be a function.
42 |
43 | Write:
44 |
45 | ```sh
46 | VAR_NAME=value
47 | export VAR_NAME
48 | ```
49 |
50 | # Quoting
51 |
52 | It is better to be safe than sorry.
53 |
54 | # Variables
55 |
56 | Use alphabetical names with underscores for word separations.
57 |
58 | Variables which live past their scope should be `CAPITALIZED_LIKE_THIS`. Local
59 | variables should be `lowercase_like_this`. The `local` keyword is unspecified in
60 | POSIX and should not be used.
61 |
62 | Do not use braces for variable references: every POSIX shell will
63 | correctly parse `"$SANDBOX_DIR/workdir"` with `SANDBOX_DIR` as the
64 | variable name. You only need braces when sticking on valid variable
65 | name characters, like in `${SANDBOX_DIR}2`.
66 |
67 | # Command substitutions
68 |
69 | Always use `$(...)`; never use backticks.
70 |
71 | # Function definitions
72 |
73 | Function names are `in_lowercase_please`. Put a function header above
74 | function definitions. (Exception: generated scripts can be terser, but
75 | should still include important comments.) Always use braces in a
76 | function definition, starting on the same line after a space. Unless
77 | you are using `$@`, rename positional parameters. Avoid obscure
78 | calling conventions: either use `$@` or name your parameters.
79 |
80 | ```sh
81 | ################################################################################
82 | # Summarize an overlay
83 | ################################################################################
84 |
85 | summary() {
86 | ...
87 | }
88 | ```
89 |
90 | # Commands
91 |
92 | Redirections go at the end of commands. There should be no spaces
93 | anywhere in a redirect: write `2>/dev/null`, not `2> /dev/null`.
94 |
95 | Prefer `echo` when possible (but only use POSIX flags).
96 |
97 | Call it `[`, not `test`. Prefer `&&` and `||` over `-a` and `-o` when
98 | using `[`, but definitely use them for `find`.
99 |
100 | Use `[ "..." ]` to test whether `...` is empty or not; there is no
101 | reason to use `-n`. Do not forget quotes, though!
102 |
103 | When using `case`, put the `;;` on the last command (not its own
104 | line). Do not omit the `;;` on the last case. Use both parentheses for
105 | each case, even though the left-hand one is optional.
106 |
107 | ## Semicolons
108 |
109 | Avoid semicolons in control flow. Write:
110 |
111 | ```sh
112 | if [ ... ]
113 | then
114 | ...
115 | fi
116 | ```
117 |
118 | The shell is terse enough without any effort on your part. Don't write
119 | `c1; c2`; write:
120 |
121 | ```sh
122 | c1
123 | c2
124 | ```
125 |
--------------------------------------------------------------------------------
/Vagrantfile:
--------------------------------------------------------------------------------
1 | Vagrant.configure("2") do |config|
2 |
3 | config.vm.provider "virtualbox" do |vb|
4 | vb.memory = 8192
5 | vb.cpus = 2
6 | end
7 |
8 | # Regular debian testing box
9 | config.vm.define "debian" do |debian|
10 | debian.vm.box = "generic/debian12"
11 | debian.vm.provision "file", source: "./", destination: "/home/vagrant/try"
12 | debian.vm.provision "shell", privileged: false, inline: "
13 | sudo apt-get update
14 | sudo apt-get install -y git expect curl attr pandoc gcc make autoconf mergerfs
15 | sudo chown -R vagrant:vagrant try
16 | cd try
17 | scripts/run_tests.sh
18 |
19 | autoconf && ./configure && make
20 | sudo make install
21 | which try-commit || exit 2
22 |
23 | scripts/run_tests.sh
24 | "
25 | end
26 |
27 | # Regular debian testing box but we try the rustup oneliner
28 | config.vm.define "debianrustup" do |debianrustup|
29 | debianrustup.vm.box = "generic/debian12"
30 | debianrustup.vm.provision "file", source: "./", destination: "/home/vagrant/try"
31 | debianrustup.vm.provision "shell", privileged: false, inline: "
32 | sudo apt-get update
33 | sudo apt-get install -y curl attr pandoc gcc make autoconf mergerfs
34 | sudo chown -R vagrant:vagrant try
35 | cd try
36 | mkdir rustup
37 | ./try -D rustup \"curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y\"
38 | ls -lah rustup/upperdir/home/vagrant/.cargo/bin
39 |
40 | rm -rf rustup
41 | autoconf && ./configure && make
42 | sudo make install
43 | which try-commit || exit 2
44 |
45 | mkdir rustup
46 | ./try -D rustup \"curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y\"
47 | ls -lah rustup/upperdir/home/vagrant/.cargo/bin
48 | "
49 | end
50 |
51 | # Regular debian testing box with LVM
52 | config.vm.define "debianlvm" do |debianlvm|
53 | debianlvm.vm.box = "generic/debian12"
54 | debianlvm.vm.provision "file", source: "./", destination: "/home/vagrant/try"
55 | debianlvm.vm.provision "shell", privileged: false, inline: "
56 | sudo apt-get update
57 | sudo apt-get install -y git expect lvm2 mergerfs curl attr pandoc gcc make autoconf mergerfs
58 |
59 | # Create an image for the lvm disk
60 | sudo fallocate -l 2G /root/lvm_disk.img
61 |
62 | # Setup a loopback device
63 | sudo losetup /dev/loop0 /root/lvm_disk.img
64 |
65 | # Create the lv physicalvolume, volumegroup, and logicalvolumes
66 | sudo pvcreate /dev/loop0
67 | sudo vgcreate vg0 /dev/loop0
68 | sudo lvcreate -n lv0 -l 50%FREE vg0
69 | sudo lvcreate -n lv1 -l 100%FREE vg0
70 |
71 | sudo mkfs.ext4 /dev/vg0/lv0
72 | sudo mkfs.ext4 /dev/vg0/lv1
73 | sudo mkdir /mnt/lv0
74 | sudo mount /dev/vg0/lv0 /mnt/lv0
75 | sudo mkdir /mnt/lv0/lv1
76 | sudo mount /dev/vg0/lv1 /mnt/lv0/lv1
77 |
78 | # This is intentional, if we moved try to lv1 it'd work since itself does not contain a nested mount
79 | sudo mv /home/vagrant/try /mnt/lv0
80 | sudo chown -R vagrant:vagrant /mnt/lv0/try
81 |
82 | cd /mnt/lv0/try
83 | scripts/run_tests.sh
84 |
85 | autoconf && ./configure && make
86 | sudo make install
87 | which try-commit || exit 2
88 |
89 | scripts/run_tests.sh
90 | "
91 | end
92 |
93 | # Regular rocky testing box
94 | config.vm.define "rocky9" do |rocky|
95 | rocky.vm.box = "generic/rocky9"
96 | rocky.vm.provision "file", source: "./", destination: "/home/vagrant/try"
97 | rocky.vm.provision "shell", privileged: false, inline: "
98 | sudo yum install -y git expect curl attr pandoc fuse
99 | wget https://github.com/trapexit/mergerfs/releases/download/2.40.2/mergerfs-2.40.2-1.el9.x86_64.rpm
100 | sudo rpm -i mergerfs-2.40.2-1.el9.x86_64.rpm
101 | sudo chown -R vagrant:vagrant try
102 | cd try
103 | TRY_TOP=$(pwd) scripts/run_tests.sh
104 | autoconf && ./configure && make
105 | sudo make install
106 | which try-commit || exit 2
107 | TRY_TOP=$(pwd) scripts/run_tests.sh
108 | "
109 | end
110 | #
111 | # Regular rocky testing box
112 | config.vm.define "fedora39" do |fedora|
113 | fedora.vm.box = "generic/fedora39"
114 | fedora.vm.provision "file", source: "./", destination: "/home/vagrant/try"
115 | fedora.vm.provision "shell", privileged: false, inline: "
116 | sudo yum install -y git expect curl attr pandoc fuse
117 | wget https://github.com/trapexit/mergerfs/releases/download/2.40.2/mergerfs-2.40.2-1.fc39.x86_64.rpm
118 | sudo rpm -i mergerfs-2.40.2-1.fc39.x86_64.rpm
119 | sudo chown -R vagrant:vagrant try
120 | cd try
121 | TRY_TOP=$(pwd) scripts/run_tests.sh
122 | autoconf && ./configure && make
123 | sudo make install
124 | which try-commit || exit 2
125 | TRY_TOP=$(pwd) scripts/run_tests.sh
126 | "
127 | end
128 | end
129 |
--------------------------------------------------------------------------------
/completions/README.md:
--------------------------------------------------------------------------------
1 | # Shell completions
2 |
3 | This directory holds completions for the `bash` shell.
4 |
5 | You can apply completions locally by running:
6 |
7 | ```Bash
8 | source try.bash
9 | ```
10 |
11 | Alternatively, to enable *try* shell completion by default, consider adding the following line to your `.bashrc`:
12 | ```Bash
13 | source /completions/try.bash
14 | ```
15 |
16 | To enable *try* shell completion for all users,
17 | consider copying the bash completion script to the `/etc/bash_completion.d/` directory:
18 | ```Bash
19 | sudo cp try.bash /etc/bash_completion.d/
20 | ```
21 |
--------------------------------------------------------------------------------
/completions/try.bash:
--------------------------------------------------------------------------------
1 | _try() {
2 | local i cur prev opts cmds
3 | COMPREPLY=()
4 | cur="${COMP_WORDS[COMP_CWORD]}"
5 | prev="${COMP_WORDS[COMP_CWORD-1]}"
6 | cmd=""
7 | opts=""
8 |
9 | for i in ${COMP_WORDS[@]}
10 | do
11 | case "${i}" in
12 | (try) cmd="try";;
13 |
14 | (*) ;;
15 | esac
16 | done
17 |
18 | case "${cmd}" in
19 | (try)
20 | opts="-n -y -v -h -x -i -D -U -L summary commit explore"
21 | if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]]
22 | then
23 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
24 | return 0
25 | fi
26 | case "${prev}" in
27 | (-L)
28 | COMPREPLY=($(compgen -d "${cur}"))
29 | return 0;;
30 | (-D)
31 | COMPREPLY=($(compgen -d "${cur}"))
32 | return 0;;
33 | (-U)
34 | COMPREPLY=($(compgen -c "${cur}"))
35 | return 0;;
36 | (commit)
37 | COMPREPLY=($(compgen -d "${cur}"))
38 | return 0;;
39 | (summary)
40 | COMPREPLY=($(compgen -d "${cur}"))
41 | return 0;;
42 | (*)
43 | COMPREPLY=();;
44 | esac
45 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
46 | return 0;;
47 | esac
48 | }
49 |
50 | complete -F _try -o bashdefault -o default try
51 |
--------------------------------------------------------------------------------
/config.sub:
--------------------------------------------------------------------------------
1 | #! /bin/sh
2 | # Configuration validation subroutine script.
3 | # Copyright 1992-2022 Free Software Foundation, Inc.
4 |
5 | # shellcheck disable=SC2006,SC2268 # see below for rationale
6 |
7 | timestamp='2022-01-03'
8 |
9 | # This file is free software; you can redistribute it and/or modify it
10 | # under the terms of the GNU General Public License as published by
11 | # the Free Software Foundation, either version 3 of the License, or
12 | # (at your option) any later version.
13 | #
14 | # This program is distributed in the hope that it will be useful, but
15 | # WITHOUT ANY WARRANTY; without even the implied warranty of
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 | # General Public License for more details.
18 | #
19 | # You should have received a copy of the GNU General Public License
20 | # along with this program; if not, see .
21 | #
22 | # As a special exception to the GNU General Public License, if you
23 | # distribute this file as part of a program that contains a
24 | # configuration script generated by Autoconf, you may include it under
25 | # the same distribution terms that you use for the rest of that
26 | # program. This Exception is an additional permission under section 7
27 | # of the GNU General Public License, version 3 ("GPLv3").
28 |
29 |
30 | # Please send patches to .
31 | #
32 | # Configuration subroutine to validate and canonicalize a configuration type.
33 | # Supply the specified configuration type as an argument.
34 | # If it is invalid, we print an error message on stderr and exit with code 1.
35 | # Otherwise, we print the canonical config type on stdout and succeed.
36 |
37 | # You can get the latest version of this script from:
38 | # https://git.savannah.gnu.org/cgit/config.git/plain/config.sub
39 |
40 | # This file is supposed to be the same for all GNU packages
41 | # and recognize all the CPU types, system types and aliases
42 | # that are meaningful with *any* GNU software.
43 | # Each package is responsible for reporting which valid configurations
44 | # it does not support. The user should be able to distinguish
45 | # a failure to support a valid configuration from a meaningless
46 | # configuration.
47 |
48 | # The goal of this file is to map all the various variations of a given
49 | # machine specification into a single specification in the form:
50 | # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM
51 | # or in some cases, the newer four-part form:
52 | # CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM
53 | # It is wrong to echo any other type of specification.
54 |
55 | # The "shellcheck disable" line above the timestamp inhibits complaints
56 | # about features and limitations of the classic Bourne shell that were
57 | # superseded or lifted in POSIX. However, this script identifies a wide
58 | # variety of pre-POSIX systems that do not have POSIX shells at all, and
59 | # even some reasonably current systems (Solaris 10 as case-in-point) still
60 | # have a pre-POSIX /bin/sh.
61 |
62 | me=`echo "$0" | sed -e 's,.*/,,'`
63 |
64 | usage="\
65 | Usage: $0 [OPTION] CPU-MFR-OPSYS or ALIAS
66 |
67 | Canonicalize a configuration name.
68 |
69 | Options:
70 | -h, --help print this help, then exit
71 | -t, --time-stamp print date of last modification, then exit
72 | -v, --version print version number, then exit
73 |
74 | Report bugs and patches to ."
75 |
76 | version="\
77 | GNU config.sub ($timestamp)
78 |
79 | Copyright 1992-2022 Free Software Foundation, Inc.
80 |
81 | This is free software; see the source for copying conditions. There is NO
82 | warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
83 |
84 | help="
85 | Try \`$me --help' for more information."
86 |
87 | # Parse command line
88 | while test $# -gt 0 ; do
89 | case $1 in
90 | --time-stamp | --time* | -t )
91 | echo "$timestamp" ; exit ;;
92 | --version | -v )
93 | echo "$version" ; exit ;;
94 | --help | --h* | -h )
95 | echo "$usage"; exit ;;
96 | -- ) # Stop option processing
97 | shift; break ;;
98 | - ) # Use stdin as input.
99 | break ;;
100 | -* )
101 | echo "$me: invalid option $1$help" >&2
102 | exit 1 ;;
103 |
104 | *local*)
105 | # First pass through any local machine types.
106 | echo "$1"
107 | exit ;;
108 |
109 | * )
110 | break ;;
111 | esac
112 | done
113 |
114 | case $# in
115 | 0) echo "$me: missing argument$help" >&2
116 | exit 1;;
117 | 1) ;;
118 | *) echo "$me: too many arguments$help" >&2
119 | exit 1;;
120 | esac
121 |
122 | # Split fields of configuration type
123 | # shellcheck disable=SC2162
124 | saved_IFS=$IFS
125 | IFS="-" read field1 field2 field3 field4 <&2
134 | exit 1
135 | ;;
136 | *-*-*-*)
137 | basic_machine=$field1-$field2
138 | basic_os=$field3-$field4
139 | ;;
140 | *-*-*)
141 | # Ambiguous whether COMPANY is present, or skipped and KERNEL-OS is two
142 | # parts
143 | maybe_os=$field2-$field3
144 | case $maybe_os in
145 | nto-qnx* | linux-* | uclinux-uclibc* \
146 | | uclinux-gnu* | kfreebsd*-gnu* | knetbsd*-gnu* | netbsd*-gnu* \
147 | | netbsd*-eabi* | kopensolaris*-gnu* | cloudabi*-eabi* \
148 | | storm-chaos* | os2-emx* | rtmk-nova*)
149 | basic_machine=$field1
150 | basic_os=$maybe_os
151 | ;;
152 | android-linux)
153 | basic_machine=$field1-unknown
154 | basic_os=linux-android
155 | ;;
156 | *)
157 | basic_machine=$field1-$field2
158 | basic_os=$field3
159 | ;;
160 | esac
161 | ;;
162 | *-*)
163 | # A lone config we happen to match not fitting any pattern
164 | case $field1-$field2 in
165 | decstation-3100)
166 | basic_machine=mips-dec
167 | basic_os=
168 | ;;
169 | *-*)
170 | # Second component is usually, but not always the OS
171 | case $field2 in
172 | # Prevent following clause from handling this valid os
173 | sun*os*)
174 | basic_machine=$field1
175 | basic_os=$field2
176 | ;;
177 | zephyr*)
178 | basic_machine=$field1-unknown
179 | basic_os=$field2
180 | ;;
181 | # Manufacturers
182 | dec* | mips* | sequent* | encore* | pc533* | sgi* | sony* \
183 | | att* | 7300* | 3300* | delta* | motorola* | sun[234]* \
184 | | unicom* | ibm* | next | hp | isi* | apollo | altos* \
185 | | convergent* | ncr* | news | 32* | 3600* | 3100* \
186 | | hitachi* | c[123]* | convex* | sun | crds | omron* | dg \
187 | | ultra | tti* | harris | dolphin | highlevel | gould \
188 | | cbm | ns | masscomp | apple | axis | knuth | cray \
189 | | microblaze* | sim | cisco \
190 | | oki | wec | wrs | winbond)
191 | basic_machine=$field1-$field2
192 | basic_os=
193 | ;;
194 | *)
195 | basic_machine=$field1
196 | basic_os=$field2
197 | ;;
198 | esac
199 | ;;
200 | esac
201 | ;;
202 | *)
203 | # Convert single-component short-hands not valid as part of
204 | # multi-component configurations.
205 | case $field1 in
206 | 386bsd)
207 | basic_machine=i386-pc
208 | basic_os=bsd
209 | ;;
210 | a29khif)
211 | basic_machine=a29k-amd
212 | basic_os=udi
213 | ;;
214 | adobe68k)
215 | basic_machine=m68010-adobe
216 | basic_os=scout
217 | ;;
218 | alliant)
219 | basic_machine=fx80-alliant
220 | basic_os=
221 | ;;
222 | altos | altos3068)
223 | basic_machine=m68k-altos
224 | basic_os=
225 | ;;
226 | am29k)
227 | basic_machine=a29k-none
228 | basic_os=bsd
229 | ;;
230 | amdahl)
231 | basic_machine=580-amdahl
232 | basic_os=sysv
233 | ;;
234 | amiga)
235 | basic_machine=m68k-unknown
236 | basic_os=
237 | ;;
238 | amigaos | amigados)
239 | basic_machine=m68k-unknown
240 | basic_os=amigaos
241 | ;;
242 | amigaunix | amix)
243 | basic_machine=m68k-unknown
244 | basic_os=sysv4
245 | ;;
246 | apollo68)
247 | basic_machine=m68k-apollo
248 | basic_os=sysv
249 | ;;
250 | apollo68bsd)
251 | basic_machine=m68k-apollo
252 | basic_os=bsd
253 | ;;
254 | aros)
255 | basic_machine=i386-pc
256 | basic_os=aros
257 | ;;
258 | aux)
259 | basic_machine=m68k-apple
260 | basic_os=aux
261 | ;;
262 | balance)
263 | basic_machine=ns32k-sequent
264 | basic_os=dynix
265 | ;;
266 | blackfin)
267 | basic_machine=bfin-unknown
268 | basic_os=linux
269 | ;;
270 | cegcc)
271 | basic_machine=arm-unknown
272 | basic_os=cegcc
273 | ;;
274 | convex-c1)
275 | basic_machine=c1-convex
276 | basic_os=bsd
277 | ;;
278 | convex-c2)
279 | basic_machine=c2-convex
280 | basic_os=bsd
281 | ;;
282 | convex-c32)
283 | basic_machine=c32-convex
284 | basic_os=bsd
285 | ;;
286 | convex-c34)
287 | basic_machine=c34-convex
288 | basic_os=bsd
289 | ;;
290 | convex-c38)
291 | basic_machine=c38-convex
292 | basic_os=bsd
293 | ;;
294 | cray)
295 | basic_machine=j90-cray
296 | basic_os=unicos
297 | ;;
298 | crds | unos)
299 | basic_machine=m68k-crds
300 | basic_os=
301 | ;;
302 | da30)
303 | basic_machine=m68k-da30
304 | basic_os=
305 | ;;
306 | decstation | pmax | pmin | dec3100 | decstatn)
307 | basic_machine=mips-dec
308 | basic_os=
309 | ;;
310 | delta88)
311 | basic_machine=m88k-motorola
312 | basic_os=sysv3
313 | ;;
314 | dicos)
315 | basic_machine=i686-pc
316 | basic_os=dicos
317 | ;;
318 | djgpp)
319 | basic_machine=i586-pc
320 | basic_os=msdosdjgpp
321 | ;;
322 | ebmon29k)
323 | basic_machine=a29k-amd
324 | basic_os=ebmon
325 | ;;
326 | es1800 | OSE68k | ose68k | ose | OSE)
327 | basic_machine=m68k-ericsson
328 | basic_os=ose
329 | ;;
330 | gmicro)
331 | basic_machine=tron-gmicro
332 | basic_os=sysv
333 | ;;
334 | go32)
335 | basic_machine=i386-pc
336 | basic_os=go32
337 | ;;
338 | h8300hms)
339 | basic_machine=h8300-hitachi
340 | basic_os=hms
341 | ;;
342 | h8300xray)
343 | basic_machine=h8300-hitachi
344 | basic_os=xray
345 | ;;
346 | h8500hms)
347 | basic_machine=h8500-hitachi
348 | basic_os=hms
349 | ;;
350 | harris)
351 | basic_machine=m88k-harris
352 | basic_os=sysv3
353 | ;;
354 | hp300 | hp300hpux)
355 | basic_machine=m68k-hp
356 | basic_os=hpux
357 | ;;
358 | hp300bsd)
359 | basic_machine=m68k-hp
360 | basic_os=bsd
361 | ;;
362 | hppaosf)
363 | basic_machine=hppa1.1-hp
364 | basic_os=osf
365 | ;;
366 | hppro)
367 | basic_machine=hppa1.1-hp
368 | basic_os=proelf
369 | ;;
370 | i386mach)
371 | basic_machine=i386-mach
372 | basic_os=mach
373 | ;;
374 | isi68 | isi)
375 | basic_machine=m68k-isi
376 | basic_os=sysv
377 | ;;
378 | m68knommu)
379 | basic_machine=m68k-unknown
380 | basic_os=linux
381 | ;;
382 | magnum | m3230)
383 | basic_machine=mips-mips
384 | basic_os=sysv
385 | ;;
386 | merlin)
387 | basic_machine=ns32k-utek
388 | basic_os=sysv
389 | ;;
390 | mingw64)
391 | basic_machine=x86_64-pc
392 | basic_os=mingw64
393 | ;;
394 | mingw32)
395 | basic_machine=i686-pc
396 | basic_os=mingw32
397 | ;;
398 | mingw32ce)
399 | basic_machine=arm-unknown
400 | basic_os=mingw32ce
401 | ;;
402 | monitor)
403 | basic_machine=m68k-rom68k
404 | basic_os=coff
405 | ;;
406 | morphos)
407 | basic_machine=powerpc-unknown
408 | basic_os=morphos
409 | ;;
410 | moxiebox)
411 | basic_machine=moxie-unknown
412 | basic_os=moxiebox
413 | ;;
414 | msdos)
415 | basic_machine=i386-pc
416 | basic_os=msdos
417 | ;;
418 | msys)
419 | basic_machine=i686-pc
420 | basic_os=msys
421 | ;;
422 | mvs)
423 | basic_machine=i370-ibm
424 | basic_os=mvs
425 | ;;
426 | nacl)
427 | basic_machine=le32-unknown
428 | basic_os=nacl
429 | ;;
430 | ncr3000)
431 | basic_machine=i486-ncr
432 | basic_os=sysv4
433 | ;;
434 | netbsd386)
435 | basic_machine=i386-pc
436 | basic_os=netbsd
437 | ;;
438 | netwinder)
439 | basic_machine=armv4l-rebel
440 | basic_os=linux
441 | ;;
442 | news | news700 | news800 | news900)
443 | basic_machine=m68k-sony
444 | basic_os=newsos
445 | ;;
446 | news1000)
447 | basic_machine=m68030-sony
448 | basic_os=newsos
449 | ;;
450 | necv70)
451 | basic_machine=v70-nec
452 | basic_os=sysv
453 | ;;
454 | nh3000)
455 | basic_machine=m68k-harris
456 | basic_os=cxux
457 | ;;
458 | nh[45]000)
459 | basic_machine=m88k-harris
460 | basic_os=cxux
461 | ;;
462 | nindy960)
463 | basic_machine=i960-intel
464 | basic_os=nindy
465 | ;;
466 | mon960)
467 | basic_machine=i960-intel
468 | basic_os=mon960
469 | ;;
470 | nonstopux)
471 | basic_machine=mips-compaq
472 | basic_os=nonstopux
473 | ;;
474 | os400)
475 | basic_machine=powerpc-ibm
476 | basic_os=os400
477 | ;;
478 | OSE68000 | ose68000)
479 | basic_machine=m68000-ericsson
480 | basic_os=ose
481 | ;;
482 | os68k)
483 | basic_machine=m68k-none
484 | basic_os=os68k
485 | ;;
486 | paragon)
487 | basic_machine=i860-intel
488 | basic_os=osf
489 | ;;
490 | parisc)
491 | basic_machine=hppa-unknown
492 | basic_os=linux
493 | ;;
494 | psp)
495 | basic_machine=mipsallegrexel-sony
496 | basic_os=psp
497 | ;;
498 | pw32)
499 | basic_machine=i586-unknown
500 | basic_os=pw32
501 | ;;
502 | rdos | rdos64)
503 | basic_machine=x86_64-pc
504 | basic_os=rdos
505 | ;;
506 | rdos32)
507 | basic_machine=i386-pc
508 | basic_os=rdos
509 | ;;
510 | rom68k)
511 | basic_machine=m68k-rom68k
512 | basic_os=coff
513 | ;;
514 | sa29200)
515 | basic_machine=a29k-amd
516 | basic_os=udi
517 | ;;
518 | sei)
519 | basic_machine=mips-sei
520 | basic_os=seiux
521 | ;;
522 | sequent)
523 | basic_machine=i386-sequent
524 | basic_os=
525 | ;;
526 | sps7)
527 | basic_machine=m68k-bull
528 | basic_os=sysv2
529 | ;;
530 | st2000)
531 | basic_machine=m68k-tandem
532 | basic_os=
533 | ;;
534 | stratus)
535 | basic_machine=i860-stratus
536 | basic_os=sysv4
537 | ;;
538 | sun2)
539 | basic_machine=m68000-sun
540 | basic_os=
541 | ;;
542 | sun2os3)
543 | basic_machine=m68000-sun
544 | basic_os=sunos3
545 | ;;
546 | sun2os4)
547 | basic_machine=m68000-sun
548 | basic_os=sunos4
549 | ;;
550 | sun3)
551 | basic_machine=m68k-sun
552 | basic_os=
553 | ;;
554 | sun3os3)
555 | basic_machine=m68k-sun
556 | basic_os=sunos3
557 | ;;
558 | sun3os4)
559 | basic_machine=m68k-sun
560 | basic_os=sunos4
561 | ;;
562 | sun4)
563 | basic_machine=sparc-sun
564 | basic_os=
565 | ;;
566 | sun4os3)
567 | basic_machine=sparc-sun
568 | basic_os=sunos3
569 | ;;
570 | sun4os4)
571 | basic_machine=sparc-sun
572 | basic_os=sunos4
573 | ;;
574 | sun4sol2)
575 | basic_machine=sparc-sun
576 | basic_os=solaris2
577 | ;;
578 | sun386 | sun386i | roadrunner)
579 | basic_machine=i386-sun
580 | basic_os=
581 | ;;
582 | sv1)
583 | basic_machine=sv1-cray
584 | basic_os=unicos
585 | ;;
586 | symmetry)
587 | basic_machine=i386-sequent
588 | basic_os=dynix
589 | ;;
590 | t3e)
591 | basic_machine=alphaev5-cray
592 | basic_os=unicos
593 | ;;
594 | t90)
595 | basic_machine=t90-cray
596 | basic_os=unicos
597 | ;;
598 | toad1)
599 | basic_machine=pdp10-xkl
600 | basic_os=tops20
601 | ;;
602 | tpf)
603 | basic_machine=s390x-ibm
604 | basic_os=tpf
605 | ;;
606 | udi29k)
607 | basic_machine=a29k-amd
608 | basic_os=udi
609 | ;;
610 | ultra3)
611 | basic_machine=a29k-nyu
612 | basic_os=sym1
613 | ;;
614 | v810 | necv810)
615 | basic_machine=v810-nec
616 | basic_os=none
617 | ;;
618 | vaxv)
619 | basic_machine=vax-dec
620 | basic_os=sysv
621 | ;;
622 | vms)
623 | basic_machine=vax-dec
624 | basic_os=vms
625 | ;;
626 | vsta)
627 | basic_machine=i386-pc
628 | basic_os=vsta
629 | ;;
630 | vxworks960)
631 | basic_machine=i960-wrs
632 | basic_os=vxworks
633 | ;;
634 | vxworks68)
635 | basic_machine=m68k-wrs
636 | basic_os=vxworks
637 | ;;
638 | vxworks29k)
639 | basic_machine=a29k-wrs
640 | basic_os=vxworks
641 | ;;
642 | xbox)
643 | basic_machine=i686-pc
644 | basic_os=mingw32
645 | ;;
646 | ymp)
647 | basic_machine=ymp-cray
648 | basic_os=unicos
649 | ;;
650 | *)
651 | basic_machine=$1
652 | basic_os=
653 | ;;
654 | esac
655 | ;;
656 | esac
657 |
658 | # Decode 1-component or ad-hoc basic machines
659 | case $basic_machine in
660 | # Here we handle the default manufacturer of certain CPU types. It is in
661 | # some cases the only manufacturer, in others, it is the most popular.
662 | w89k)
663 | cpu=hppa1.1
664 | vendor=winbond
665 | ;;
666 | op50n)
667 | cpu=hppa1.1
668 | vendor=oki
669 | ;;
670 | op60c)
671 | cpu=hppa1.1
672 | vendor=oki
673 | ;;
674 | ibm*)
675 | cpu=i370
676 | vendor=ibm
677 | ;;
678 | orion105)
679 | cpu=clipper
680 | vendor=highlevel
681 | ;;
682 | mac | mpw | mac-mpw)
683 | cpu=m68k
684 | vendor=apple
685 | ;;
686 | pmac | pmac-mpw)
687 | cpu=powerpc
688 | vendor=apple
689 | ;;
690 |
691 | # Recognize the various machine names and aliases which stand
692 | # for a CPU type and a company and sometimes even an OS.
693 | 3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc)
694 | cpu=m68000
695 | vendor=att
696 | ;;
697 | 3b*)
698 | cpu=we32k
699 | vendor=att
700 | ;;
701 | bluegene*)
702 | cpu=powerpc
703 | vendor=ibm
704 | basic_os=cnk
705 | ;;
706 | decsystem10* | dec10*)
707 | cpu=pdp10
708 | vendor=dec
709 | basic_os=tops10
710 | ;;
711 | decsystem20* | dec20*)
712 | cpu=pdp10
713 | vendor=dec
714 | basic_os=tops20
715 | ;;
716 | delta | 3300 | motorola-3300 | motorola-delta \
717 | | 3300-motorola | delta-motorola)
718 | cpu=m68k
719 | vendor=motorola
720 | ;;
721 | dpx2*)
722 | cpu=m68k
723 | vendor=bull
724 | basic_os=sysv3
725 | ;;
726 | encore | umax | mmax)
727 | cpu=ns32k
728 | vendor=encore
729 | ;;
730 | elxsi)
731 | cpu=elxsi
732 | vendor=elxsi
733 | basic_os=${basic_os:-bsd}
734 | ;;
735 | fx2800)
736 | cpu=i860
737 | vendor=alliant
738 | ;;
739 | genix)
740 | cpu=ns32k
741 | vendor=ns
742 | ;;
743 | h3050r* | hiux*)
744 | cpu=hppa1.1
745 | vendor=hitachi
746 | basic_os=hiuxwe2
747 | ;;
748 | hp3k9[0-9][0-9] | hp9[0-9][0-9])
749 | cpu=hppa1.0
750 | vendor=hp
751 | ;;
752 | hp9k2[0-9][0-9] | hp9k31[0-9])
753 | cpu=m68000
754 | vendor=hp
755 | ;;
756 | hp9k3[2-9][0-9])
757 | cpu=m68k
758 | vendor=hp
759 | ;;
760 | hp9k6[0-9][0-9] | hp6[0-9][0-9])
761 | cpu=hppa1.0
762 | vendor=hp
763 | ;;
764 | hp9k7[0-79][0-9] | hp7[0-79][0-9])
765 | cpu=hppa1.1
766 | vendor=hp
767 | ;;
768 | hp9k78[0-9] | hp78[0-9])
769 | # FIXME: really hppa2.0-hp
770 | cpu=hppa1.1
771 | vendor=hp
772 | ;;
773 | hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893)
774 | # FIXME: really hppa2.0-hp
775 | cpu=hppa1.1
776 | vendor=hp
777 | ;;
778 | hp9k8[0-9][13679] | hp8[0-9][13679])
779 | cpu=hppa1.1
780 | vendor=hp
781 | ;;
782 | hp9k8[0-9][0-9] | hp8[0-9][0-9])
783 | cpu=hppa1.0
784 | vendor=hp
785 | ;;
786 | i*86v32)
787 | cpu=`echo "$1" | sed -e 's/86.*/86/'`
788 | vendor=pc
789 | basic_os=sysv32
790 | ;;
791 | i*86v4*)
792 | cpu=`echo "$1" | sed -e 's/86.*/86/'`
793 | vendor=pc
794 | basic_os=sysv4
795 | ;;
796 | i*86v)
797 | cpu=`echo "$1" | sed -e 's/86.*/86/'`
798 | vendor=pc
799 | basic_os=sysv
800 | ;;
801 | i*86sol2)
802 | cpu=`echo "$1" | sed -e 's/86.*/86/'`
803 | vendor=pc
804 | basic_os=solaris2
805 | ;;
806 | j90 | j90-cray)
807 | cpu=j90
808 | vendor=cray
809 | basic_os=${basic_os:-unicos}
810 | ;;
811 | iris | iris4d)
812 | cpu=mips
813 | vendor=sgi
814 | case $basic_os in
815 | irix*)
816 | ;;
817 | *)
818 | basic_os=irix4
819 | ;;
820 | esac
821 | ;;
822 | miniframe)
823 | cpu=m68000
824 | vendor=convergent
825 | ;;
826 | *mint | mint[0-9]* | *MiNT | *MiNT[0-9]*)
827 | cpu=m68k
828 | vendor=atari
829 | basic_os=mint
830 | ;;
831 | news-3600 | risc-news)
832 | cpu=mips
833 | vendor=sony
834 | basic_os=newsos
835 | ;;
836 | next | m*-next)
837 | cpu=m68k
838 | vendor=next
839 | case $basic_os in
840 | openstep*)
841 | ;;
842 | nextstep*)
843 | ;;
844 | ns2*)
845 | basic_os=nextstep2
846 | ;;
847 | *)
848 | basic_os=nextstep3
849 | ;;
850 | esac
851 | ;;
852 | np1)
853 | cpu=np1
854 | vendor=gould
855 | ;;
856 | op50n-* | op60c-*)
857 | cpu=hppa1.1
858 | vendor=oki
859 | basic_os=proelf
860 | ;;
861 | pa-hitachi)
862 | cpu=hppa1.1
863 | vendor=hitachi
864 | basic_os=hiuxwe2
865 | ;;
866 | pbd)
867 | cpu=sparc
868 | vendor=tti
869 | ;;
870 | pbb)
871 | cpu=m68k
872 | vendor=tti
873 | ;;
874 | pc532)
875 | cpu=ns32k
876 | vendor=pc532
877 | ;;
878 | pn)
879 | cpu=pn
880 | vendor=gould
881 | ;;
882 | power)
883 | cpu=power
884 | vendor=ibm
885 | ;;
886 | ps2)
887 | cpu=i386
888 | vendor=ibm
889 | ;;
890 | rm[46]00)
891 | cpu=mips
892 | vendor=siemens
893 | ;;
894 | rtpc | rtpc-*)
895 | cpu=romp
896 | vendor=ibm
897 | ;;
898 | sde)
899 | cpu=mipsisa32
900 | vendor=sde
901 | basic_os=${basic_os:-elf}
902 | ;;
903 | simso-wrs)
904 | cpu=sparclite
905 | vendor=wrs
906 | basic_os=vxworks
907 | ;;
908 | tower | tower-32)
909 | cpu=m68k
910 | vendor=ncr
911 | ;;
912 | vpp*|vx|vx-*)
913 | cpu=f301
914 | vendor=fujitsu
915 | ;;
916 | w65)
917 | cpu=w65
918 | vendor=wdc
919 | ;;
920 | w89k-*)
921 | cpu=hppa1.1
922 | vendor=winbond
923 | basic_os=proelf
924 | ;;
925 | none)
926 | cpu=none
927 | vendor=none
928 | ;;
929 | leon|leon[3-9])
930 | cpu=sparc
931 | vendor=$basic_machine
932 | ;;
933 | leon-*|leon[3-9]-*)
934 | cpu=sparc
935 | vendor=`echo "$basic_machine" | sed 's/-.*//'`
936 | ;;
937 |
938 | *-*)
939 | # shellcheck disable=SC2162
940 | saved_IFS=$IFS
941 | IFS="-" read cpu vendor <&2
1289 | exit 1
1290 | ;;
1291 | esac
1292 | ;;
1293 | esac
1294 |
1295 | # Here we canonicalize certain aliases for manufacturers.
1296 | case $vendor in
1297 | digital*)
1298 | vendor=dec
1299 | ;;
1300 | commodore*)
1301 | vendor=cbm
1302 | ;;
1303 | *)
1304 | ;;
1305 | esac
1306 |
1307 | # Decode manufacturer-specific aliases for certain operating systems.
1308 |
1309 | if test x$basic_os != x
1310 | then
1311 |
1312 | # First recognize some ad-hoc cases, or perhaps split kernel-os, or else just
1313 | # set os.
1314 | case $basic_os in
1315 | gnu/linux*)
1316 | kernel=linux
1317 | os=`echo "$basic_os" | sed -e 's|gnu/linux|gnu|'`
1318 | ;;
1319 | os2-emx)
1320 | kernel=os2
1321 | os=`echo "$basic_os" | sed -e 's|os2-emx|emx|'`
1322 | ;;
1323 | nto-qnx*)
1324 | kernel=nto
1325 | os=`echo "$basic_os" | sed -e 's|nto-qnx|qnx|'`
1326 | ;;
1327 | *-*)
1328 | # shellcheck disable=SC2162
1329 | saved_IFS=$IFS
1330 | IFS="-" read kernel os <&2
1767 | exit 1
1768 | ;;
1769 | esac
1770 |
1771 | # As a final step for OS-related things, validate the OS-kernel combination
1772 | # (given a valid OS), if there is a kernel.
1773 | case $kernel-$os in
1774 | linux-gnu* | linux-dietlibc* | linux-android* | linux-newlib* \
1775 | | linux-musl* | linux-relibc* | linux-uclibc* )
1776 | ;;
1777 | uclinux-uclibc* )
1778 | ;;
1779 | -dietlibc* | -newlib* | -musl* | -relibc* | -uclibc* )
1780 | # These are just libc implementations, not actual OSes, and thus
1781 | # require a kernel.
1782 | echo "Invalid configuration \`$1': libc \`$os' needs explicit kernel." 1>&2
1783 | exit 1
1784 | ;;
1785 | kfreebsd*-gnu* | kopensolaris*-gnu*)
1786 | ;;
1787 | vxworks-simlinux | vxworks-simwindows | vxworks-spe)
1788 | ;;
1789 | nto-qnx*)
1790 | ;;
1791 | os2-emx)
1792 | ;;
1793 | *-eabi* | *-gnueabi*)
1794 | ;;
1795 | -*)
1796 | # Blank kernel with real OS is always fine.
1797 | ;;
1798 | *-*)
1799 | echo "Invalid configuration \`$1': Kernel \`$kernel' not known to work with OS \`$os'." 1>&2
1800 | exit 1
1801 | ;;
1802 | esac
1803 |
1804 | # Here we handle the case where we know the os, and the CPU type, but not the
1805 | # manufacturer. We pick the logical manufacturer.
1806 | case $vendor in
1807 | unknown)
1808 | case $cpu-$os in
1809 | *-riscix*)
1810 | vendor=acorn
1811 | ;;
1812 | *-sunos*)
1813 | vendor=sun
1814 | ;;
1815 | *-cnk* | *-aix*)
1816 | vendor=ibm
1817 | ;;
1818 | *-beos*)
1819 | vendor=be
1820 | ;;
1821 | *-hpux*)
1822 | vendor=hp
1823 | ;;
1824 | *-mpeix*)
1825 | vendor=hp
1826 | ;;
1827 | *-hiux*)
1828 | vendor=hitachi
1829 | ;;
1830 | *-unos*)
1831 | vendor=crds
1832 | ;;
1833 | *-dgux*)
1834 | vendor=dg
1835 | ;;
1836 | *-luna*)
1837 | vendor=omron
1838 | ;;
1839 | *-genix*)
1840 | vendor=ns
1841 | ;;
1842 | *-clix*)
1843 | vendor=intergraph
1844 | ;;
1845 | *-mvs* | *-opened*)
1846 | vendor=ibm
1847 | ;;
1848 | *-os400*)
1849 | vendor=ibm
1850 | ;;
1851 | s390-* | s390x-*)
1852 | vendor=ibm
1853 | ;;
1854 | *-ptx*)
1855 | vendor=sequent
1856 | ;;
1857 | *-tpf*)
1858 | vendor=ibm
1859 | ;;
1860 | *-vxsim* | *-vxworks* | *-windiss*)
1861 | vendor=wrs
1862 | ;;
1863 | *-aux*)
1864 | vendor=apple
1865 | ;;
1866 | *-hms*)
1867 | vendor=hitachi
1868 | ;;
1869 | *-mpw* | *-macos*)
1870 | vendor=apple
1871 | ;;
1872 | *-*mint | *-mint[0-9]* | *-*MiNT | *-MiNT[0-9]*)
1873 | vendor=atari
1874 | ;;
1875 | *-vos*)
1876 | vendor=stratus
1877 | ;;
1878 | esac
1879 | ;;
1880 | esac
1881 |
1882 | echo "$cpu-$vendor-${kernel:+$kernel-}$os"
1883 | exit
1884 |
1885 | # Local variables:
1886 | # eval: (add-hook 'before-save-hook 'time-stamp)
1887 | # time-stamp-start: "timestamp='"
1888 | # time-stamp-format: "%:y-%02m-%02d"
1889 | # time-stamp-end: "'"
1890 | # End:
1891 |
--------------------------------------------------------------------------------
/configure.ac:
--------------------------------------------------------------------------------
1 | AC_PREREQ(2.68)
2 | AC_INIT([try], [0.2.0], [https://github.com/binpash/try/issues])
3 |
4 | # make sure we're in the right place
5 | AC_CONFIG_SRCDIR([try])
6 | AC_REQUIRE_AUX_FILE([docs/try.1.md])
7 |
8 | AC_ARG_ENABLE([utils],
9 | [AS_HELP_STRING([--disable-utils],
10 | [don't compile C utilities for summarizing and committing changes (default is yes)])],
11 | [enable_utils=${enableval}], [enable_utils=yes])
12 |
13 | if test "$enable_utils" = "yes"
14 | then
15 | AC_REQUIRE_AUX_FILE([utils/try-commit.c])
16 |
17 | # build tools
18 | AC_PROG_CC
19 |
20 | # CFLAGS and CPPFLAGGS
21 | if test -z "$CFLAGS"
22 | then
23 | AUTO_CFLAGS="-g -Wall -O2"
24 | else
25 | AUTO_CFLAGS=""
26 | fi
27 | AUTO_CPPFLAGS=""
28 |
29 | CFLAGS=${CFLAGS-"$AUTO_CFLAGS"}
30 | CPPFLAGS=${CPPFLAGS-"$AUTO_CPPFLAGS"}
31 |
32 | AC_SUBST(CFLAGS)
33 | AC_SUBST(CPPFLAGS)
34 |
35 | # needed C types
36 | AC_TYPE_PID_T
37 | AC_TYPE_SIZE_T
38 | AC_TYPE_SSIZE_T
39 |
40 | # needed C functions
41 | AC_FUNC_MALLOC
42 | AC_FUNC_REALLOC
43 | AC_FUNC_FORK
44 | AC_FUNC_LSTAT_FOLLOWS_SLASHED_SYMLINK
45 |
46 | AC_CHECK_HEADERS([fcntl.h unistd.h fts.h])
47 | AC_CHECK_FUNCS([regcomp perror strerror getline getxattr fts_open])
48 |
49 | missing_funs=""
50 | for fun in regcomp perror strerror getline getxattr fts_open
51 | do
52 | varname="\$ac_cv_func_$fun"
53 | if test "$(eval "echo ${varname-no}")" != "yes"
54 | then
55 | missing_funs="$missing_funs $fun"
56 | fi
57 | done
58 |
59 |
60 | if ! test -z "$missing_funs"
61 | then
62 | AC_MSG_NOTICE([
63 | ------------------------------------------------------------------------
64 | WARNING:
65 |
66 | Some critical functions are missing, so utilities cannot be compiled.
67 | Missing functions:$missing_funs
68 |
69 | Continuing as if --disable-utils was passed.
70 | ------------------------------------------------------------------------
71 | ]);
72 | enable_utils=no
73 | fi
74 | fi
75 | AC_SUBST(enable_utils)
76 |
77 | # install
78 | AC_PROG_INSTALL
79 |
80 | dnl TRY_REQUIRE_PROG(progname, checking_msg, pre, true_if_failed, post, fail_msg)
81 | AC_DEFUN([TRY_REQUIRE_PROG], [
82 | AC_MSG_CHECKING([ifelse([$2], [], [for $1], [$2])])
83 | $3
84 | if ifelse([$4], [], [! type -p $1 >/dev/null 2>&1], [$4])
85 | then
86 | AC_MSG_RESULT([no])
87 | AC_MSG_ERROR([try needs $1 to work, but ifelse([$6], [], [it could not be found], [$6]).])
88 | else
89 | AC_MSG_RESULT([yes])
90 | $5
91 | fi
92 | ])
93 |
94 | # symlinks
95 | AC_PROG_LN_S
96 | if test "$LN_S" != "ln -s"
97 | then
98 | AC_MSG_NOTICE([
99 | ----------------------------------------------------------------------
100 | WARNING
101 |
102 | Your system seems to not have a working implementation of symbolic
103 | links. `try` may behave strangely.
104 | ----------------------------------------------------------------------
105 | ])
106 | fi
107 |
108 | #mktemp
109 | TRY_REQUIRE_PROG([mktemp])
110 | TRY_REQUIRE_PROG([mktemp -d], [whether mktemp -d works], [d=$(mktemp -d 2>/dev/null)], [test "$?" != 0 || ! test -d "$d"], [rmdir "$d"], [your mktemp does not support the -d option])
111 |
112 | TRY_REQUIRE_PROG([df])
113 | TRY_REQUIRE_PROG([find])
114 | TRY_REQUIRE_PROG([grep])
115 | TRY_REQUIRE_PROG([findmnt])
116 | TRY_REQUIRE_PROG([sort])
117 | TRY_REQUIRE_PROG([getfattr])
118 |
119 | TRY_REQUIRE_PROG([overlayfs],[for overlayfs],[],[! lsmod | grep -q overlay], [], [the overlay module did not appear in the output of lsmod])
120 |
121 | AC_CHECK_PROG([mergerfs], [mergerfs], [yes], [no])
122 | AC_CHECK_PROG([unionfs], [unionfs], [yes], [no])
123 |
124 | AC_MSG_CHECKING([for union helpers (unionfs or mergerfs)])
125 | if test "$ac_cv_prog_mergerfs" = "no" && test "$ac_cv_prog_unionfs" = "no"
126 | then
127 | AC_MSG_RESULT([no; try may issue warnings and error messages])
128 | else
129 | AC_MSG_RESULT([yes])
130 | fi
131 |
132 | TRY_REQUIRE_PROG([readlink])
133 | TRY_REQUIRE_PROG([unshare], [for unshare], [
134 | res=$(unshare --mount --map-root-user --user --pid --fork -- ls $PWD/try 2>/dev/null)
135 | ], [
136 | test "$?" != 0 || test "$res" != "$PWD/try"
137 | ], [], [could not run unshare])
138 |
139 | # for manpages
140 | if test -f man/try.1.gz
141 | then
142 | need_manpage=no
143 | else
144 | need_manpage=yes
145 | fi
146 | AC_ARG_ENABLE([manpage],
147 | [AS_HELP_STRING([--enable-manpage],
148 | [generate the manpage instead of using the cached version (requires pandoc, default is no when man/try.1.gz is present)])],
149 | [enable_manpage=${enableval}], [enable_manpage=${need_manpage}])
150 | if test "$enable_manpage" = "yes"
151 | then
152 | AC_MSG_CHECKING([for pandoc (for manpage)])
153 | if ! type -p pandoc >/dev/null 2>&1
154 | then
155 | AC_MSG_RESULT([no])
156 | enable_manpage="no"
157 | else
158 | AC_MSG_RESULT([yes])
159 | fi
160 |
161 | AC_MSG_CHECKING([for gzip (for manpage)])
162 | if ! type -p gzip >/dev/null 2>&1
163 | then
164 | AC_MSG_RESULT([no])
165 | enable_manpage="no"
166 | else
167 | AC_MSG_RESULT([yes])
168 | fi
169 |
170 | if test "$enable_manpage" = no
171 | then
172 | if test -f man/try.1.gz
173 | then
174 | AC_MSG_NOTICE([
175 | ------------------------------------------------------------------------
176 | WARNING
177 |
178 | Your system is missing some programs used to generate the manpage, so
179 | the cached version will be used instead.
180 | ------------------------------------------------------------------------
181 | ])
182 | else
183 | AC_MSG_ERROR([
184 | ------------------------------------------------------------------------
185 | WARNING
186 |
187 | Your system is missing some programs used to generate the manpage and
188 | no cached manpage is available.
189 | ------------------------------------------------------------------------
190 | ])
191 | fi
192 | fi
193 | fi
194 | AC_SUBST(enable_manpage)
195 |
196 |
197 | # programs just used in tests
198 | missing_test_programs=0
199 |
200 | AC_PROG_AWK
201 | AC_MSG_CHECKING([whether awk is gawk (for tests)])
202 | if test "$(stat -L -c %i "$(type -p "$AWK")")" != "$(stat -L -c %i "$(type -p awk)")"
203 | then
204 | AC_MSG_RESULT([no; some tests may behave strangely])
205 | : $((missing_test_programs += 1))
206 | else
207 | AC_MSG_RESULT([yes])
208 | fi
209 |
210 | dnl TRY_REQUIRE_PROG(progname)
211 | AC_DEFUN([TRY_TEST_PROG], [
212 | AC_MSG_CHECKING([for $1 (for tests)])
213 | if ! type -p $1 >/dev/null 2>&1
214 | then
215 | AC_MSG_RESULT([no; some tests may fail])
216 | : $((missing_test_programs += 1))
217 | else
218 | AC_MSG_RESULT([yes])
219 | fi
220 | ])
221 |
222 | TRY_TEST_PROG([expect])
223 | TRY_TEST_PROG([curl])
224 | TRY_TEST_PROG([diff])
225 | TRY_TEST_PROG([touch])
226 | TRY_TEST_PROG([gunzip])
227 |
228 | if test "$missing_test_programs" -ne 0
229 | then
230 | AC_MSG_NOTICE([
231 | ------------------------------------------------------------------------
232 | WARNING
233 |
234 | Your system is missing some programs used in tests, so some tests may
235 | fail.
236 | ------------------------------------------------------------------------
237 | ])
238 |
239 | fi
240 |
241 | AC_CONFIG_FILES([Makefile:Makefile.in])
242 | AC_OUTPUT
243 |
244 | cat <&2
148 | exit 1;;
149 | esac
150 | shift;;
151 |
152 | -o) chowncmd="$chownprog $2"
153 | shift;;
154 |
155 | -p) cpprog="$cpprog -p";;
156 |
157 | -s) stripcmd=$stripprog;;
158 |
159 | -S) backupsuffix="$2"
160 | shift;;
161 |
162 | -t)
163 | is_target_a_directory=always
164 | dst_arg=$2
165 | # Protect names problematic for 'test' and other utilities.
166 | case $dst_arg in
167 | -* | [=\(\)!]) dst_arg=./$dst_arg;;
168 | esac
169 | shift;;
170 |
171 | -T) is_target_a_directory=never;;
172 |
173 | --version) echo "$0 $scriptversion"; exit $?;;
174 |
175 | --) shift
176 | break;;
177 |
178 | -*) echo "$0: invalid option: $1" >&2
179 | exit 1;;
180 |
181 | *) break;;
182 | esac
183 | shift
184 | done
185 |
186 | # We allow the use of options -d and -T together, by making -d
187 | # take the precedence; this is for compatibility with GNU install.
188 |
189 | if test -n "$dir_arg"; then
190 | if test -n "$dst_arg"; then
191 | echo "$0: target directory not allowed when installing a directory." >&2
192 | exit 1
193 | fi
194 | fi
195 |
196 | if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then
197 | # When -d is used, all remaining arguments are directories to create.
198 | # When -t is used, the destination is already specified.
199 | # Otherwise, the last argument is the destination. Remove it from $@.
200 | for arg
201 | do
202 | if test -n "$dst_arg"; then
203 | # $@ is not empty: it contains at least $arg.
204 | set fnord "$@" "$dst_arg"
205 | shift # fnord
206 | fi
207 | shift # arg
208 | dst_arg=$arg
209 | # Protect names problematic for 'test' and other utilities.
210 | case $dst_arg in
211 | -* | [=\(\)!]) dst_arg=./$dst_arg;;
212 | esac
213 | done
214 | fi
215 |
216 | if test $# -eq 0; then
217 | if test -z "$dir_arg"; then
218 | echo "$0: no input file specified." >&2
219 | exit 1
220 | fi
221 | # It's OK to call 'install-sh -d' without argument.
222 | # This can happen when creating conditional directories.
223 | exit 0
224 | fi
225 |
226 | if test -z "$dir_arg"; then
227 | if test $# -gt 1 || test "$is_target_a_directory" = always; then
228 | if test ! -d "$dst_arg"; then
229 | echo "$0: $dst_arg: Is not a directory." >&2
230 | exit 1
231 | fi
232 | fi
233 | fi
234 |
235 | if test -z "$dir_arg"; then
236 | do_exit='(exit $ret); exit $ret'
237 | trap "ret=129; $do_exit" 1
238 | trap "ret=130; $do_exit" 2
239 | trap "ret=141; $do_exit" 13
240 | trap "ret=143; $do_exit" 15
241 |
242 | # Set umask so as not to create temps with too-generous modes.
243 | # However, 'strip' requires both read and write access to temps.
244 | case $mode in
245 | # Optimize common cases.
246 | *644) cp_umask=133;;
247 | *755) cp_umask=22;;
248 |
249 | *[0-7])
250 | if test -z "$stripcmd"; then
251 | u_plus_rw=
252 | else
253 | u_plus_rw='% 200'
254 | fi
255 | cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;;
256 | *)
257 | if test -z "$stripcmd"; then
258 | u_plus_rw=
259 | else
260 | u_plus_rw=,u+rw
261 | fi
262 | cp_umask=$mode$u_plus_rw;;
263 | esac
264 | fi
265 |
266 | for src
267 | do
268 | # Protect names problematic for 'test' and other utilities.
269 | case $src in
270 | -* | [=\(\)!]) src=./$src;;
271 | esac
272 |
273 | if test -n "$dir_arg"; then
274 | dst=$src
275 | dstdir=$dst
276 | test -d "$dstdir"
277 | dstdir_status=$?
278 | # Don't chown directories that already exist.
279 | if test $dstdir_status = 0; then
280 | chowncmd=""
281 | fi
282 | else
283 |
284 | # Waiting for this to be detected by the "$cpprog $src $dsttmp" command
285 | # might cause directories to be created, which would be especially bad
286 | # if $src (and thus $dsttmp) contains '*'.
287 | if test ! -f "$src" && test ! -d "$src"; then
288 | echo "$0: $src does not exist." >&2
289 | exit 1
290 | fi
291 |
292 | if test -z "$dst_arg"; then
293 | echo "$0: no destination specified." >&2
294 | exit 1
295 | fi
296 | dst=$dst_arg
297 |
298 | # If destination is a directory, append the input filename.
299 | if test -d "$dst"; then
300 | if test "$is_target_a_directory" = never; then
301 | echo "$0: $dst_arg: Is a directory" >&2
302 | exit 1
303 | fi
304 | dstdir=$dst
305 | dstbase=`basename "$src"`
306 | case $dst in
307 | */) dst=$dst$dstbase;;
308 | *) dst=$dst/$dstbase;;
309 | esac
310 | dstdir_status=0
311 | else
312 | dstdir=`dirname "$dst"`
313 | test -d "$dstdir"
314 | dstdir_status=$?
315 | fi
316 | fi
317 |
318 | case $dstdir in
319 | */) dstdirslash=$dstdir;;
320 | *) dstdirslash=$dstdir/;;
321 | esac
322 |
323 | obsolete_mkdir_used=false
324 |
325 | if test $dstdir_status != 0; then
326 | case $posix_mkdir in
327 | '')
328 | # With -d, create the new directory with the user-specified mode.
329 | # Otherwise, rely on $mkdir_umask.
330 | if test -n "$dir_arg"; then
331 | mkdir_mode=-m$mode
332 | else
333 | mkdir_mode=
334 | fi
335 |
336 | posix_mkdir=false
337 | # The $RANDOM variable is not portable (e.g., dash). Use it
338 | # here however when possible just to lower collision chance.
339 | tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$
340 |
341 | trap '
342 | ret=$?
343 | rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" 2>/dev/null
344 | exit $ret
345 | ' 0
346 |
347 | # Because "mkdir -p" follows existing symlinks and we likely work
348 | # directly in world-writeable /tmp, make sure that the '$tmpdir'
349 | # directory is successfully created first before we actually test
350 | # 'mkdir -p'.
351 | if (umask $mkdir_umask &&
352 | $mkdirprog $mkdir_mode "$tmpdir" &&
353 | exec $mkdirprog $mkdir_mode -p -- "$tmpdir/a/b") >/dev/null 2>&1
354 | then
355 | if test -z "$dir_arg" || {
356 | # Check for POSIX incompatibilities with -m.
357 | # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or
358 | # other-writable bit of parent directory when it shouldn't.
359 | # FreeBSD 6.1 mkdir -m -p sets mode of existing directory.
360 | test_tmpdir="$tmpdir/a"
361 | ls_ld_tmpdir=`ls -ld "$test_tmpdir"`
362 | case $ls_ld_tmpdir in
363 | d????-?r-*) different_mode=700;;
364 | d????-?--*) different_mode=755;;
365 | *) false;;
366 | esac &&
367 | $mkdirprog -m$different_mode -p -- "$test_tmpdir" && {
368 | ls_ld_tmpdir_1=`ls -ld "$test_tmpdir"`
369 | test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1"
370 | }
371 | }
372 | then posix_mkdir=:
373 | fi
374 | rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir"
375 | else
376 | # Remove any dirs left behind by ancient mkdir implementations.
377 | rmdir ./$mkdir_mode ./-p ./-- "$tmpdir" 2>/dev/null
378 | fi
379 | trap '' 0;;
380 | esac
381 |
382 | if
383 | $posix_mkdir && (
384 | umask $mkdir_umask &&
385 | $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir"
386 | )
387 | then :
388 | else
389 |
390 | # mkdir does not conform to POSIX,
391 | # or it failed possibly due to a race condition. Create the
392 | # directory the slow way, step by step, checking for races as we go.
393 |
394 | case $dstdir in
395 | /*) prefix='/';;
396 | [-=\(\)!]*) prefix='./';;
397 | *) prefix='';;
398 | esac
399 |
400 | oIFS=$IFS
401 | IFS=/
402 | set -f
403 | set fnord $dstdir
404 | shift
405 | set +f
406 | IFS=$oIFS
407 |
408 | prefixes=
409 |
410 | for d
411 | do
412 | test X"$d" = X && continue
413 |
414 | prefix=$prefix$d
415 | if test -d "$prefix"; then
416 | prefixes=
417 | else
418 | if $posix_mkdir; then
419 | (umask $mkdir_umask &&
420 | $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break
421 | # Don't fail if two instances are running concurrently.
422 | test -d "$prefix" || exit 1
423 | else
424 | case $prefix in
425 | *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;;
426 | *) qprefix=$prefix;;
427 | esac
428 | prefixes="$prefixes '$qprefix'"
429 | fi
430 | fi
431 | prefix=$prefix/
432 | done
433 |
434 | if test -n "$prefixes"; then
435 | # Don't fail if two instances are running concurrently.
436 | (umask $mkdir_umask &&
437 | eval "\$doit_exec \$mkdirprog $prefixes") ||
438 | test -d "$dstdir" || exit 1
439 | obsolete_mkdir_used=true
440 | fi
441 | fi
442 | fi
443 |
444 | if test -n "$dir_arg"; then
445 | { test -z "$chowncmd" || $doit $chowncmd "$dst"; } &&
446 | { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } &&
447 | { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false ||
448 | test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1
449 | else
450 |
451 | # Make a couple of temp file names in the proper directory.
452 | dsttmp=${dstdirslash}_inst.$$_
453 | rmtmp=${dstdirslash}_rm.$$_
454 |
455 | # Trap to clean up those temp files at exit.
456 | trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0
457 |
458 | # Copy the file name to the temp name.
459 | (umask $cp_umask &&
460 | { test -z "$stripcmd" || {
461 | # Create $dsttmp read-write so that cp doesn't create it read-only,
462 | # which would cause strip to fail.
463 | if test -z "$doit"; then
464 | : >"$dsttmp" # No need to fork-exec 'touch'.
465 | else
466 | $doit touch "$dsttmp"
467 | fi
468 | }
469 | } &&
470 | $doit_exec $cpprog "$src" "$dsttmp") &&
471 |
472 | # and set any options; do chmod last to preserve setuid bits.
473 | #
474 | # If any of these fail, we abort the whole thing. If we want to
475 | # ignore errors from any of these, just make sure not to ignore
476 | # errors from the above "$doit $cpprog $src $dsttmp" command.
477 | #
478 | { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } &&
479 | { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } &&
480 | { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } &&
481 | { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } &&
482 |
483 | # If -C, don't bother to copy if it wouldn't change the file.
484 | if $copy_on_change &&
485 | old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` &&
486 | new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` &&
487 | set -f &&
488 | set X $old && old=:$2:$4:$5:$6 &&
489 | set X $new && new=:$2:$4:$5:$6 &&
490 | set +f &&
491 | test "$old" = "$new" &&
492 | $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1
493 | then
494 | rm -f "$dsttmp"
495 | else
496 | # If $backupsuffix is set, and the file being installed
497 | # already exists, attempt a backup. Don't worry if it fails,
498 | # e.g., if mv doesn't support -f.
499 | if test -n "$backupsuffix" && test -f "$dst"; then
500 | $doit $mvcmd -f "$dst" "$dst$backupsuffix" 2>/dev/null
501 | fi
502 |
503 | # Rename the file to the real destination.
504 | $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null ||
505 |
506 | # The rename failed, perhaps because mv can't rename something else
507 | # to itself, or perhaps because mv is so ancient that it does not
508 | # support -f.
509 | {
510 | # Now remove or move aside any old file at destination location.
511 | # We try this two ways since rm can't unlink itself on some
512 | # systems and the destination file might be busy for other
513 | # reasons. In this case, the final cleanup might fail but the new
514 | # file should still install successfully.
515 | {
516 | test ! -f "$dst" ||
517 | $doit $rmcmd "$dst" 2>/dev/null ||
518 | { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null &&
519 | { $doit $rmcmd "$rmtmp" 2>/dev/null; :; }
520 | } ||
521 | { echo "$0: cannot unlink or rename $dst" >&2
522 | (exit 1); exit 1
523 | }
524 | } &&
525 |
526 | # Now rename the file to the real destination.
527 | $doit $mvcmd "$dsttmp" "$dst"
528 | }
529 | fi || exit 1
530 |
531 | trap '' 0
532 | fi
533 | done
534 |
535 | # Local variables:
536 | # eval: (add-hook 'before-save-hook 'time-stamp)
537 | # time-stamp-start: "scriptversion="
538 | # time-stamp-format: "%:y-%02m-%02d.%02H"
539 | # time-stamp-time-zone: "UTC0"
540 | # time-stamp-end: "; # UTC"
541 | # End:
542 |
--------------------------------------------------------------------------------
/man/.gitignore:
--------------------------------------------------------------------------------
1 | try.1
2 | try.1.gz
3 |
--------------------------------------------------------------------------------
/package.nix:
--------------------------------------------------------------------------------
1 | {
2 | stdenv,
3 | autoreconfHook,
4 | lib,
5 | fetchFromGitHub,
6 | util-linux,
7 | mergerfs,
8 | attr,
9 | makeWrapper,
10 | pandoc,
11 | coreutils,
12 | installShellFiles,
13 | versionCheckHook,
14 | }:
15 | stdenv.mkDerivation {
16 | pname = "try";
17 | version = "latest";
18 |
19 | src = fetchFromGitHub {
20 | owner = "binpash";
21 | repo = "try";
22 | rev = "67052d8f20725f3cdc22ffaec33f7b7c14f1eb6b";
23 | hash = "sha256-8mfCmqN50pRAeNTJUlRVrRQulWon4b2OL4Ug/ygBhB0=";
24 | };
25 |
26 | # skip TRY_REQUIRE_PROG as it detects executable dependencies by running it
27 | postPatch = ''
28 | sed -i '/^AC_DEFUN(\[TRY_REQUIRE_PROG\]/,/^])$/c\AC_DEFUN([TRY_REQUIRE_PROG], [])' configure.ac
29 | '';
30 |
31 | nativeBuildInputs = [
32 | makeWrapper
33 | autoreconfHook
34 | pandoc
35 | installShellFiles
36 | ];
37 |
38 | installPhase = ''
39 | runHook preInstall
40 | install -Dt $out/bin try
41 | install -Dt $out/bin utils/try-commit
42 | install -Dt $out/bin utils/try-summary
43 | wrapProgram $out/bin/try --prefix PATH : ${
44 | lib.makeBinPath [
45 | coreutils
46 | util-linux
47 | mergerfs
48 | attr
49 | ]
50 | }
51 | installManPage man/try.1.gz
52 | installShellCompletion --bash --name try.bash completions/try.bash
53 | runHook postInstall
54 | '';
55 |
56 | doInstallCheck = true;
57 | nativeInstallCheckInputs = [
58 | versionCheckHook
59 | ];
60 | preVersionCheck = ''
61 | export version=0.2.0
62 | '';
63 | versionCheckProgramArg = "-v";
64 |
65 | meta = {
66 | homepage = "https://github.com/binpash/try";
67 | description = "Lets you run a command and inspect its effects before changing your live system";
68 | mainProgram = "try";
69 | maintainers = with lib.maintainers; [
70 | pasqui23
71 | ezrizhu
72 | ];
73 | license = with lib.licenses; [ mit ];
74 | platforms = lib.platforms.linux;
75 | };
76 | }
77 |
78 |
--------------------------------------------------------------------------------
/scripts/README.md:
--------------------------------------------------------------------------------
1 | This directory holds utility scripts used in development of `try`.
2 |
3 | The `lint.sh` script runs basic file linting.
4 |
5 | The `run_tests.sh` script runs the test suite.
6 |
--------------------------------------------------------------------------------
/scripts/check_trycase.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | ################################################################################
4 | # pluralize a word
5 | ################################################################################
6 |
7 | plural() {
8 | if [ "$1" -eq 1 ]
9 | then
10 | echo ""
11 | else
12 | echo "s"
13 | fi
14 | }
15 |
16 | check_file() {
17 | trycase="$(mktemp -d)"
18 |
19 | for cf in file dir symlink opaque whiteout
20 | do
21 | mkdir "$trycase/$cf"
22 | for lf in file dir symlink nonexist
23 | do
24 | touch "$trycase/$cf/$lf"
25 | done
26 | done
27 | rm "$trycase/opaque/nonexist" # this case cannot occur
28 |
29 | ERRORS=0
30 |
31 | CASES=$(mktemp)
32 | grep -e "// TRYCASE" "$1" | cut -d'/' -f3 | cut -c10- >"$CASES"
33 |
34 | while IFS="" read -r c
35 | do
36 | cf="${c%,*}"
37 | lf="${c#*, }"
38 | lf="${lf%)}"
39 |
40 | case "$cf" in
41 | (file|dir|symlink|nonexist|opaque|whiteout);;
42 | (*) printf "ERROR: $1: invalid changed file: %s\n" "$cf"
43 | : $((ERRORS += 1))
44 | ;;
45 | esac
46 | case "$lf" in
47 | (file|dir|symlink|nonexist|"*");;
48 | (*) printf "ERROR: $1: invalid local file: %s\n" "$lf"
49 | : $((ERRORS += 1))
50 | ;;
51 | esac
52 |
53 | # shellcheck disable=SC2086 # deliberately letting $lf be *!
54 | rm "$trycase"/"$cf"/$lf 2>/dev/null
55 | done <"$CASES"
56 | rm "$CASES"
57 |
58 | # shellcheck disable=SC2045 # globs are annoying here
59 | for cf in $(ls "$trycase")
60 | do
61 | # shellcheck disable=SC2045 # globs are annoying here
62 | for lf in $(ls "$trycase/$cf")
63 | do
64 | printf "ERROR: $1: missing TRYCASE(%s, %s)\n" "$cf" "$lf"
65 | : $((ERRORS += 1))
66 | done
67 | done
68 | rm -r "$trycase"
69 |
70 | if [ "$ERRORS" -eq 0 ]
71 | then
72 | printf "\033[32;1m✓ %s PASSED\033[0m\n" "$1"
73 | else
74 | printf "\n❌ \033[31;1m%s FAILED (%d error%s)\033[0m\n" "$1" "$ERRORS" "$(plural "$ERRORS")"
75 | FOUND_ERROR=1
76 | fi
77 | }
78 |
79 | TRY_TOP="${TRY_TOP-$(git rev-parse --show-toplevel --show-superproject-working-tree)}"
80 |
81 | FOUND_ERROR=0
82 | check_file "$TRY_TOP/try"
83 | check_file "$TRY_TOP/utils/try-summary.c"
84 | check_file "$TRY_TOP/utils/try-commit.c"
85 | check_file "$TRY_TOP/test/all-commit-cases.sh"
86 | exit "$FOUND_ERROR"
87 |
--------------------------------------------------------------------------------
/scripts/check_version.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | SCRIPT_VERSION="$(grep 'TRY_VERSION=' try | cut -d'"' -f 2)"
4 | MANPAGE_VERSION="$(grep 'TRY(1)' docs/try.1.md | cut -d' ' -f 4)"
5 | INCLUDE_VERSION="$(grep '#define TRY_VERSION' utils/version.h | cut -d'"' -f 2)"
6 | CONFIGAC_VERSION="$(grep AC_INIT configure.ac | cut -d'[' -f3 | cut -d']' -f1)"
7 |
8 | if [ "$SCRIPT_VERSION" = "$MANPAGE_VERSION" ] &&
9 | [ "$SCRIPT_VERSION" = "$INCLUDE_VERSION" ] &&
10 | [ "$SCRIPT_VERSION" = "$CONFIGAC_VERSION" ]
11 | then
12 | printf "\033[32;1m✓ VERSIONS MATCH (%s) \033[0m\n" "$SCRIPT_VERSION"
13 | else
14 | echo " SCRIPT_VERSION = '$SCRIPT_VERSION'"
15 | echo " MANPAGE_VERSION = '$MANPAGE_VERSION'"
16 | echo " INCLUDE_VERSION = '$INCLUDE_VERSION'"
17 | echo "CONFIGAC_VERSION = '$CONFIGAC_VERSION'"
18 | printf "\n❌ \033[31;1mVERSIONS DO NOT MATCH\033[0m\n"
19 | fi
20 |
--------------------------------------------------------------------------------
/scripts/lint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | NUM_WARNINGS=0
4 |
5 | ################################################################################
6 | # issue a warning
7 | ################################################################################
8 |
9 | warn() {
10 | msg="$1"
11 |
12 | printf "%s\n" "$msg" >&2
13 | }
14 |
15 | ################################################################################
16 | # pluralize a word
17 | ################################################################################
18 |
19 | plural() {
20 | if [ "$1" -eq 1 ]
21 | then
22 | echo ""
23 | else
24 | echo "s"
25 | fi
26 | }
27 |
28 | ################################################################################
29 | # check for a trailing newline
30 | ################################################################################
31 |
32 | trailing_newline() {
33 | file="$1"
34 |
35 | [ -f "$file" ] || warn "trailing_newline: '$file' is not a normal file"
36 |
37 | # empty file is fine
38 | [ -s "$file" ] || return
39 |
40 | last="$(tail -c1 "$file")"
41 | if [ "$last" != "$(printf '\n')" ]
42 | then
43 | warn "$file: missing a trailing newline"
44 | fi
45 | }
46 |
47 | ################################################################################
48 | # check for trailing whitespace on a line
49 | ################################################################################
50 |
51 | trailing_whitespace() {
52 | file="$1"
53 |
54 | [ -f "$file" ] || warn "trailing_whitespace: '$file' is not a normal file"
55 |
56 | # empty file is fine
57 | [ -s "$file" ] || return
58 |
59 | if grep -I -lq -e '[[:blank:]]$' "$file"
60 | then
61 | warn "$file: trailing whitespace (tabs shown as underscores)"
62 | grep --line-number -e '[[:blank:]]$' "$file" | sed 's/[[:blank:]]\+$/\o33[41m&\o033[0m/' | tr "$(printf '\t')" '____'
63 | fi
64 | }
65 |
66 | ################################################################################
67 | # check for tabs
68 | ################################################################################
69 |
70 | tabs() {
71 | file="$1"
72 |
73 | # it's supposed to be there!
74 | case "$file" in
75 | (*Makefile*) return;;
76 | esac
77 |
78 | [ -f "$file" ] || warn "tabs: '$file' is not a normal file"
79 |
80 | # empty file is fine
81 | [ -s "$file" ] || return
82 |
83 | # so it doesn't literally appear here, lol
84 | tab="$(printf '\t')"
85 | if grep -I -lq -e "$tab" "$file"
86 | then
87 | warn "$file: tabs (shown as underscores here)"
88 | grep --line-number -e "$tab" "$file" | sed 's/\t/\o33[41m____\o033[0m/'
89 | fi
90 | }
91 |
92 | ################################################################################
93 | # entry point
94 | ################################################################################
95 |
96 | if [ "$#" -eq 0 ]
97 | then
98 | FILES=$(find . -type f -a \( \! -path "./.git/*" \) -a \( \! -path "./.vagrant/*" \) -a \( \! -path "./autom4te.cache/*" \) -a \( \! -name install-sh \) -a \( \! -name "config*" \) -a \( \! -name "*~" \) -a \! \( -name "*.png" -o -name "*.gif" -o -name "*.gz" \))
99 | else
100 | # shellcheck disable=SC2124
101 | FILES="$@"
102 | fi
103 |
104 |
105 | num_files="$(echo "$FILES" | wc -l)"
106 | printf "Linting %s file%s in parallel..." "$num_files" "$(plural "$num_files")"
107 | OUTPUTS=""
108 | for file in $FILES
109 | do
110 | out=$(mktemp)
111 | printf "."
112 |
113 | (
114 | trailing_whitespace "$file"
115 | trailing_newline "$file"
116 | tabs "$file"
117 | ) >"$out" 2>&1 &
118 |
119 | OUTPUTS="$OUTPUTS $out"
120 | done
121 |
122 | wait
123 | printf "done.\n"
124 |
125 | for out in $OUTPUTS
126 | do
127 | [ -s "$out" ] && : $((NUM_WARNINGS += 1)) && echo
128 | cat "$out"
129 | rm "$out"
130 | done
131 |
132 | if [ "$NUM_WARNINGS" -eq 0 ]
133 | then
134 | printf "\033[32;1m✓ LINT CHECK PASSED\033[0m\n"
135 | else
136 | printf "\n❌ \033[31;1mLINT CHECK FAILED (%d warning%s)\033[0m\n" "$NUM_WARNINGS" "$(plural "$NUM_WARNINGS")"
137 | exit 1
138 | fi
139 |
--------------------------------------------------------------------------------
/scripts/run_tests.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | : "${TRY_TOP=$(git rev-parse --show-toplevel --show-superproject-working-tree 2>/dev/null || echo "${0%/*}")}"
4 |
5 | # something else to try
6 | if ! [ -d "$TRY_TOP/test" ]
7 | then
8 | TRY_TOP="$PWD"
9 | fi
10 | TEST_DIR="$TRY_TOP/test"
11 |
12 | [ -d "$TEST_DIR" ] || {
13 | echo "Couldn't find test directory (looked in $TEST_DIR)"
14 | exit 2
15 | }
16 | export TRY_TOP
17 |
18 | # set the DEBUG env variable to see detailed output
19 | DEBUG=${DEBUG:-0}
20 |
21 | TOTAL_TESTS=0
22 | PASSED_TESTS=0
23 | FAILING_TESTS=""
24 | SKIPPED_TESTS=""
25 |
26 | if type try-commit >/dev/null 2>&1
27 | then
28 | have_utils() { true; }
29 | else
30 | have_utils() { false; }
31 | fi
32 |
33 | run_test()
34 | {
35 | test="$1"
36 | name="$(basename "$test")"
37 |
38 | if ! [ -f "$test" ]
39 | then
40 | echo "Test '$name' does not exist (at path $test)."
41 | return 1
42 | elif ! [ -x "$test" ]
43 | then
44 | echo "Test '$name' is not executable (at path $test)."
45 | return 1
46 | fi
47 |
48 | # check if we can read from /run dir
49 | ls /run/systemd >/dev/null || {
50 | echo "Cannot read from /run/systemd, aborting test '$name'." >&2
51 | return 1
52 | }
53 |
54 | printf "\nRunning %s...%$((60 - (8 + ${#name} + 3) ))s" "$name" ""
55 |
56 | if grep -q -e "needs-try-utils" "$test" && ! have_utils
57 | then
58 | printf "SKIP (no utils)\n"
59 | SKIPPED_TESTS="$SKIPPED_TESTS $name"
60 | return
61 | fi
62 |
63 | # actually run the test
64 | out=$(mktemp)
65 | err=$(mktemp)
66 | "$test" >"$out" 2>"$err"
67 | ec="$?"
68 |
69 | : $((TOTAL_TESTS += 1))
70 | if [ "$ec" -eq 0 ]; then
71 | : $((PASSED_TESTS += 1))
72 | printf "PASS\n"
73 | else
74 | FAILING_TESTS="$FAILING_TESTS $name"
75 | printf "FAIL (%d)\n" "$ec"
76 |
77 | printf "=======\n"
78 | printf "STDOUT:\n"
79 | printf "=======\n"
80 | cat "$out"
81 | printf "=======\n\n"
82 |
83 | printf "=======\n"
84 | printf "STDERR:\n"
85 | printf "=======\n"
86 | cat "$err"
87 | printf "=======\n\n"
88 | fi
89 |
90 | rm "$out" "$err"
91 | }
92 |
93 | pats="$(mktemp)"
94 | if [ "$#" -eq 0 ]
95 | then
96 | # if no patterns are specified
97 | echo '.*' >"$pats"
98 | else
99 | printf "%s\n" "$@" >>"$pats"
100 | fi
101 |
102 | TESTS="$(find "$TEST_DIR" -type f -executable -name '*.sh' | grep -f "$pats")"
103 | rm "$pats"
104 |
105 | echo "========================| Try Tests |==========================="
106 | echo "Running $(echo "$TESTS" | wc -l) tests..."
107 | echo "================================================================"
108 |
109 | for test in $TESTS
110 | do
111 | run_test "$test"
112 | done
113 |
114 | echo
115 | echo "========================| Test Summary |========================"
116 | echo "Failing tests:${FAILING_TESTS}"
117 | echo "Skipped tests:${SKIPPED_TESTS}"
118 | echo "Summary: ${PASSED_TESTS}/${TOTAL_TESTS} tests passed."
119 | echo "================================================================"
120 |
121 | if [ "$PASSED_TESTS" -ne "$TOTAL_TESTS" ]
122 | then
123 | exit 1
124 | fi
125 |
--------------------------------------------------------------------------------
/shell.nix:
--------------------------------------------------------------------------------
1 | { pkgs ? import {}}:
2 |
3 | pkgs.mkShell {
4 | buildInputs = with pkgs; [
5 | expect
6 | mergerfs
7 | attr
8 | util-linux
9 | time
10 | shellcheck
11 | autoconf
12 | pandoc
13 | (pkgs.callPackage ./package.nix {})
14 | ];
15 | }
16 |
17 |
--------------------------------------------------------------------------------
/test/README.md:
--------------------------------------------------------------------------------
1 | This directory holds tests and testing resources.
2 |
3 | `scripts/run_tests.sh` will run all of the tests in this directory
4 | (and subdirectories); to be a test, a file must be:
5 |
6 | - a regular file
7 | - executable
8 | - end in `.sh`
9 |
10 | You can select a specific test by running `scripts/run_tests.sh a b`;
11 | this will run tests that pass `a` _or_ `b` as grep patterns.
12 |
13 | Some files are named `.sh.skip`---these will always be skipped. Rename
14 | them if you need to run them.
15 |
16 | Tests have a pretty standard preamble that should handle basic cleanup
17 | functions.
18 |
--------------------------------------------------------------------------------
/test/all-commit-cases.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # shellcheck disable=SC2119 # warnings about fail's arguments (put before first command to be global)
4 | TRY_TOP="${TRY_TOP:-$(git rev-parse --show-toplevel --show-superproject-working-tree 2>/dev/null || echo "${0%/*}")}"
5 | TRY="$TRY_TOP/try"
6 |
7 | cleanup() {
8 | cd /
9 |
10 | if [ -d "$try_workspace" ]
11 | then
12 | rm -rf "$try_workspace" >/dev/null 2>&1
13 | fi
14 | }
15 |
16 | trap 'cleanup' EXIT
17 |
18 | try_workspace="$(mktemp -d)"
19 | cd "$try_workspace" || exit 99
20 |
21 | # we have a case for every TRYCASE(changed_file, local_file) in the utils
22 | # - first arg is incoming change
23 | # - second arg is local file status
24 | # see utils/README.md for more info
25 |
26 | COUNT=0
27 | fail() {
28 | echo Case $COUNT: "$@"
29 | exit ${COUNT}
30 | }
31 |
32 |
33 | # // TRYCASE(opaque, dir)
34 | # // TRYCASE(dir, dir)
35 |
36 | : $((COUNT += 1))
37 |
38 | ! [ -e olddir ] || fail
39 | mkdir olddir || fail
40 | echo hi >olddir/oldfile || fail
41 | [ -d olddir ] || fail
42 | [ -f olddir/oldfile ] || fail
43 | "$TRY" -y "rm -r olddir; mkdir olddir; echo fresh >olddir/newfile" || fail
44 | [ -d olddir ] || fail
45 | [ -f olddir/newfile ] || fail
46 | ! [ -e olddir/oldfile ] || fail opaque directories worked incorrectly
47 | [ "$(cat olddir/newfile)" = "fresh" ] || fail
48 | rm -r olddir || fail
49 |
50 | # // TRYCASE(dir, nonexist)
51 |
52 | : $((COUNT += 1))
53 |
54 | ! [ -e newdir ] || fail
55 | "$TRY" -y "mkdir newdir" || fail
56 | [ -d newdir ] || fail
57 | rmdir newdir || fail
58 |
59 | # // TRYCASE(opaque, file)
60 | # // TRYCASE(dir, file)
61 |
62 | : $((COUNT += 1))
63 |
64 | ! [ -e wasfile ] || fail
65 | echo hi >wasfile || fail
66 | [ -f wasfile ] || fail
67 | "$TRY" -y "mv wasfile stillfile; mkdir wasfile; mv stillfile wasfile" || fail
68 | [ -d wasfile ] || fail
69 | [ -f wasfile/stillfile ] || fail
70 | [ "$(cat wasfile/stillfile)" = "hi" ] || fail
71 | rm -r wasfile || fail
72 |
73 | # // TRYCASE(opaque, symlink)
74 | # // TRYCASE(dir, symlink)
75 |
76 | : $((COUNT += 1))
77 |
78 | ! [ -e waslink ] || fail
79 | ln -s "$TRY" waslink || fail
80 | [ -L waslink ] || fail
81 | "$TRY" -y "rm waslink; mkdir waslink; ln -s \"$TRY\" waslink/newlink" || fail
82 | [ -d waslink ] || fail
83 | [ -L waslink/newlink ] || fail
84 | [ "$(readlink waslink/newlink)" = "$TRY" ] || fail
85 | rm -r waslink || fail
86 |
87 | # // TRYCASE(whiteout, dir)
88 |
89 | : $((COUNT += 1))
90 |
91 | mkdir existdir || fail
92 | touch existdir/file || fail
93 | [ -d existdir ] || fail
94 | "$TRY" -y "rm -r existdir" || fail
95 | ! [ -d existdir ] || fail
96 |
97 | # // TRYCASE(whiteout, file)
98 |
99 | : $((COUNT += 1))
100 |
101 | ! [ -e goner ] || fail
102 | echo alas >goner || fail
103 | [ -f goner ] || fail
104 | "$TRY" -y "rm goner" || fail
105 | ! [ -e goner ] || fail
106 |
107 | # // TRYCASE(whiteout, symlink)
108 |
109 | : $((COUNT += 1))
110 |
111 | ! [ -e trylink ] || fail
112 | ln -s "$TRY" trylink || fail
113 | [ -L trylink ] || fail
114 | "$TRY" -y "rm trylink" || fail
115 | ! [ -e trylink ] || fail
116 |
117 | # // TRYCASE(whiteout, nonexist)
118 | # this case is not really possible, but the following _could_ exercise it
119 |
120 | : $((COUNT += 1))
121 |
122 | ! [ -e absent ] || fail
123 | "$TRY" -y "touch absent; sync; rm absent" || fail
124 | ! [ -e absent ] || fail
125 |
126 | # // TRYCASE(file, symlink)
127 |
128 | : $((COUNT += 1))
129 |
130 | ! [ -e sneaky ] || fail
131 | ln -s "$TRY" sneaky || fail
132 | [ -L sneaky ] || fail
133 | "$TRY" -y "rm sneaky; echo gotcha >sneaky" || fail
134 | [ -f sneaky ] || fail
135 | [ "$(cat sneaky)" = "gotcha" ] || fail
136 | rm sneaky || fail
137 |
138 | # // TRYCASE(file, nonexist)
139 |
140 | : $((COUNT += 1))
141 |
142 | ! [ -e missingno ] || fail
143 | "$TRY" -y "echo arceus >missingno" || fail
144 | [ -f missingno ] || fail
145 | [ "$(cat missingno)" = "arceus" ] || fail
146 | rm missingno || fail
147 |
148 | # // TRYCASE(file, dir)
149 |
150 | : $((COUNT += 1))
151 |
152 | ! [ -e wasdir ] || fail
153 | mkdir wasdir || fail
154 | mkdir wasdir/inner || fail
155 | touch wasdir/inner/goodbye || fail
156 | [ -d wasdir ] || fail
157 | [ -d wasdir/inner ] || fail
158 | [ -f wasdir/inner/goodbye ] || fail
159 | "$TRY" -y "rm -r wasdir; echo a humble file >wasdir" || fail
160 | [ -f wasdir ] || fail
161 | [ "$(cat wasdir)" = "a humble file" ] || fail
162 |
163 | # // TRYCASE(file, file)
164 |
165 | : $((COUNT += 1))
166 |
167 | ! [ -e wasfile ] || fail
168 | echo sup >wasfile || fail
169 | [ -f wasfile ] || fail
170 | "$TRY" -y "echo what is up >wasfile" || fail
171 | [ -f wasfile ] || fail
172 | [ "$(cat wasfile)" = "what is up" ] || fail
173 |
174 | # // TRYCASE(symlink, nonexist)
175 |
176 | : $((COUNT += 1))
177 |
178 | ! [ -e linktobe ] || fail
179 | "$TRY" -y "ln -s \"$TRY\" linktobe" || fail
180 | [ -L linktobe ] || fail
181 | [ "$(readlink linktobe)" = "$TRY" ] || fail
182 |
183 | # // TRYCASE(symlink, file)
184 |
185 | : $((COUNT += 1))
186 |
187 | ! [ -e gonnago ] || fail
188 | echo so long >gonnago || fail
189 | "$TRY" -y "ln -sf \"$TRY\" gonnago" || fail
190 | [ -L gonnago ] || fail
191 | [ "$(readlink gonnago)" = "$TRY" ] || fail
192 | rm gonnago || fail
193 |
194 | # // TRYCASE(symlink, symlink)
195 |
196 | : $((COUNT += 1))
197 |
198 | ! [ -e otherlink ] || fail
199 | ln -s /etc/passwd otherlink || fail
200 | "$TRY" -y "ln -sf \"$TRY\" otherlink" || fail
201 | [ -L otherlink ] || fail
202 | [ "$(readlink otherlink)" = "$TRY" ] || fail
203 | rm otherlink || fail
204 |
205 | # // TRYCASE(symlink, dir)
206 |
207 | : $((COUNT += 1))
208 |
209 | ! [ -e formerdir ] || fail
210 | mkdir formerdir || fail
211 | mkdir formerdir/en formerdir/it || fail
212 | echo hello >formerdir/en/file1 || fail
213 | echo goodbye >formerdir/en/file2 || fail
214 | echo buuongiorno >formerdir/it/file1 || fail
215 | echo arrivederci >formerdir/it/file2 || fail
216 | [ -d formerdir ] || fail
217 | [ -d formerdir/en ] || fail
218 | [ -d formerdir/it ] || fail
219 | [ -f formerdir/en/file1 ] || fail
220 | [ -f formerdir/en/file2 ] || fail
221 | [ -f formerdir/it/file1 ] || fail
222 | [ -f formerdir/it/file2 ] || fail
223 | "$TRY" -y "rm -r formerdir; ln -s /this/is/a/broken/symlink formerdir" || fail
224 | [ -L formerdir ] || fail
225 | [ "$(readlink formerdir)" = "/this/is/a/broken/symlink" ] || fail
226 | rm formerdir || fail
227 |
--------------------------------------------------------------------------------
/test/command_substitution.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | TRY_TOP="${TRY_TOP:-$(git rev-parse --show-toplevel --show-superproject-working-tree 2>/dev/null || echo "${0%/*}")}"
4 | TRY="$TRY_TOP/try"
5 |
6 | cleanup() {
7 | cd /
8 |
9 | if [ -d "$try_workspace" ]
10 | then
11 | rm -rf "$try_workspace" >/dev/null 2>&1
12 | fi
13 |
14 | if [ -f "$expected" ]
15 | then
16 | rm "$expected"
17 | fi
18 | }
19 |
20 | trap 'cleanup' EXIT
21 |
22 | try_workspace="$(mktemp -d)"
23 | cd "$try_workspace" || exit 9
24 |
25 | # Set up expected output
26 | expected="$(mktemp)"
27 | pwd >"$expected"
28 |
29 | cat >script.sh <<"EOF"
30 | mypwd="$(pwd)"
31 | echo "$mypwd"
32 | EOF
33 |
34 | "$TRY" sh script.sh >out.txt || exit 1
35 |
36 | diff -q "$expected" out.txt
37 |
--------------------------------------------------------------------------------
/test/dev_urandom.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | TRY_TOP="${TRY_TOP:-$(git rev-parse --show-toplevel --show-superproject-working-tree 2>/dev/null || echo "${0%/*}")}"
4 | TRY="$TRY_TOP/try"
5 |
6 | cleanup() {
7 | cd /
8 |
9 | if [ -d "$try_workspace" ]
10 | then
11 | rm -rf "$try_workspace" >/dev/null 2>&1
12 | fi
13 | }
14 |
15 | trap 'cleanup' EXIT
16 |
17 | try_workspace="$(mktemp -d)"
18 | cd "$try_workspace" || exit 9
19 |
20 | # Ignore changes to foo
21 | "$TRY" -y "head -c 5 /dev/urandom >target" || exit 1
22 | [ -s target ] || exit 2
23 |
--------------------------------------------------------------------------------
/test/empty_summary.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | TRY_TOP="${TRY_TOP:-$(git rev-parse --show-toplevel --show-superproject-working-tree 2>/dev/null || echo "${0%/*}")}"
4 | TRY="$TRY_TOP/try"
5 |
6 | cleanup() {
7 | cd /
8 |
9 | if [ -d "$try_workspace" ]
10 | then
11 | rm -rf "$try_workspace" >/dev/null 2>&1
12 | fi
13 |
14 | if [ -d "$try_example_dir" ]
15 | then
16 | rm -rf "$try_example_dir"
17 | fi
18 | }
19 |
20 | trap 'cleanup' EXIT
21 |
22 | try_workspace="$(mktemp -d)"
23 | cd "$try_workspace" || exit 9
24 |
25 | try_example_dir="$(mktemp -d)"
26 | "$TRY" -D "$try_example_dir" -- echo hi >/dev/null || exit 1
27 | "$TRY" summary "$try_example_dir" >summary.out
28 |
29 | # an empty summary yields exit status 1
30 | [ "$?" -eq 1 ] || exit 2
31 |
32 | # We want ec 0 if the following line is not found!
33 | ! grep -q -e "Changes detected in the following files:" summary.out
34 |
--------------------------------------------------------------------------------
/test/exit_status.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | TRY_TOP="${TRY_TOP:-$(git rev-parse --show-toplevel --show-superproject-working-tree 2>/dev/null || echo "${0%/*}")}"
4 | TRY="$TRY_TOP/try"
5 |
6 | cleanup() {
7 | cd /
8 |
9 | if [ -d "$try_workspace" ]
10 | then
11 | rm -rf "$try_workspace" >/dev/null 2>&1
12 | fi
13 | }
14 |
15 | trap 'cleanup' EXIT
16 |
17 | try_workspace="$(mktemp -d)"
18 | cd "$try_workspace" || exit 9
19 |
20 | "$TRY" exit 3
21 |
22 | [ "$?" -eq 3 ]
23 |
--------------------------------------------------------------------------------
/test/explore.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | TRY_TOP="${TRY_TOP:-$(git rev-parse --show-toplevel --show-superproject-working-tree 2>/dev/null || echo "${0%/*}")}"
4 | TRY="$TRY_TOP/try"
5 |
6 | cleanup() {
7 | cd /
8 |
9 | if [ -d "$try_workspace" ]
10 | then
11 | rm -rf "$try_workspace" >/dev/null 2>&1
12 | fi
13 | }
14 |
15 | trap 'cleanup' EXIT
16 |
17 | try_workspace="$(mktemp -d)"
18 | cd "$try_workspace" || exit 9
19 |
20 | SHELL="/usr/bin/env bash --norc"
21 | export SHELL
22 | PS1="# "
23 | export PS1
24 |
25 | echo hi >expected.out
26 |
27 | cat >explore.exp <test.txt\r"
41 | }
42 | }
43 | expect "#"
44 | # Send exit
45 | send \x04
46 |
47 | # Ignore all output and just send a y at the end
48 | expect ""
49 | expect "Commit*"
50 | send -- "y\r"
51 | expect eof
52 | EOF
53 |
54 | # Debug using the -d flag
55 | expect explore.exp >/dev/null || exit 1
56 |
57 | diff -q expected.out test.txt
58 |
--------------------------------------------------------------------------------
/test/fail.sh.skip:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | #
3 | # rename this to fail.sh to force a CI failure (e.g., when testing CI changes)
4 |
5 | exit 1
6 |
--------------------------------------------------------------------------------
/test/hidden_variables.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | TRY_TOP="${TRY_TOP:-$(git rev-parse --show-toplevel --show-superproject-working-tree 2>/dev/null || echo "${0%/*}")}"
4 | TRY="$TRY_TOP/try"
5 |
6 | cleanup() {
7 | cd /
8 |
9 | if [ -d "$try_workspace" ]
10 | then
11 | rm -rf "$try_workspace" >/dev/null 2>&1
12 | fi
13 | }
14 |
15 | trap 'cleanup' EXIT
16 |
17 | try_workspace="$(mktemp -d)"
18 | cd "$try_workspace" || exit 9
19 |
20 | # Set up expected output
21 | echo 'no sandbox' >expected.out
22 |
23 | # SANDBOX_DIR should not be set in the final execution env
24 | "$TRY" -y -- "echo \${SANDBOX_DIR-no sandbox}" >got.out 2>/dev/null || exit 1
25 |
26 | diff -q expected.out got.out
27 |
--------------------------------------------------------------------------------
/test/ignore_flag.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | TRY_TOP="${TRY_TOP:-$(git rev-parse --show-toplevel --show-superproject-working-tree 2>/dev/null || echo "${0%/*}")}"
4 | TRY="$TRY_TOP/try"
5 |
6 | cleanup() {
7 | cd /
8 |
9 | if [ -d "$try_workspace" ]
10 | then
11 | rm -rf "$try_workspace" >/dev/null 2>&1
12 | fi
13 | }
14 |
15 | trap 'cleanup' EXIT
16 |
17 | try_workspace="$(mktemp -d)"
18 | cd "$try_workspace" || exit 9
19 |
20 | # Set up expected output
21 | touch expected.bar
22 |
23 | # Ignore changes to foo
24 | "$TRY" -y -i foo1 -i foo2 "touch foo1.txt; touch foo2.txt; touch bar.txt" || exit 1
25 |
26 | diff -q expected.bar bar.txt || exit 2
27 | ! [ -f foo1.txt ] || exit 3
28 | ! [ -f foo2.txt ] || exit 4
29 |
--------------------------------------------------------------------------------
/test/merge_multiple_dirs.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | TRY_TOP="${TRY_TOP:-$(git rev-parse --show-toplevel --show-superproject-working-tree 2>/dev/null || echo "${0%/*}")}"
4 | TRY="$TRY_TOP/try"
5 |
6 | cleanup() {
7 | cd /
8 |
9 | if [ -d "$try_workspace" ]
10 | then
11 | rm -rf "$try_workspace" >/dev/null 2>&1
12 | fi
13 |
14 | if [ -f "$try_example_dir1" ]
15 | then
16 | rm "$try_example_dir1"
17 | fi
18 |
19 | if [ -f "$try_example_dir2" ]
20 | then
21 | rm "$try_example_dir2"
22 | fi
23 |
24 | if [ -f "$try_example_dir3" ]
25 | then
26 | rm "$try_example_dir3"
27 | fi
28 |
29 | if [ -f "$expected1" ]
30 | then
31 | rm "$expected1"
32 | fi
33 |
34 | if [ -f "$expected2" ]
35 | then
36 | rm "$expected2"
37 | fi
38 | if [ -f "$expected3" ]
39 | then
40 | rm "$expected3"
41 | fi
42 | }
43 |
44 | trap 'cleanup' EXIT
45 |
46 | # (ezri) gh ci gid fix
47 | cd ~ || exit 1
48 |
49 | try_workspace="$(mktemp -d -p .)"
50 | cp "$TRY_TOP/test/resources/file.txt.gz" "$try_workspace/"
51 | cd "$try_workspace" || exit 1
52 |
53 | try_example_dir1="$(mktemp -d)"
54 | try_example_dir2="$(mktemp -d)"
55 | try_example_dir3="$(mktemp -d)"
56 |
57 | expected1="$(mktemp)"
58 | expected2="$(mktemp)"
59 | expected3="$(mktemp)"
60 |
61 | touch "$expected1"
62 | echo "test2" > "$expected2"
63 | echo "test3" > "$expected3"
64 |
65 | "$TRY" -D "$try_example_dir1" "touch file_1.txt; echo test > file_2.txt; rm file.txt.gz" || exit 2
66 | "$TRY" -D "$try_example_dir2" "echo test2 > file_2.txt" || exit 3
67 | "$TRY" -D "$try_example_dir3" "echo test3 > file_3.txt" || exit 4
68 | "$TRY" -L "$try_example_dir3:$try_example_dir2:$try_example_dir1" -y "cat file_1.txt > out1; cat file_2.txt > out2; cat file_3.txt > out3"|| exit 5
69 |
70 | diff -q "$expected1" out1 || exit 6
71 | diff -q "$expected2" out2 || exit 7
72 | diff -q "$expected3" out3 || exit 8
73 |
74 | ! [ -f out4 ] || exit 9
75 |
--------------------------------------------------------------------------------
/test/missing_unionfs_mergerfs.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | #
3 | # checking that try works when mergerfs/unionfs are not present (but also not necessary)
4 |
5 | TRY_TOP="${TRY_TOP:-$(git rev-parse --show-toplevel --show-superproject-working-tree 2>/dev/null || echo "${0%/*}")}"
6 | TRY="$TRY_TOP/try"
7 |
8 | cleanup() {
9 | cd /
10 |
11 | if [ -d "$try_workspace" ]
12 | then
13 | rm -rf "$try_workspace" >/dev/null 2>&1
14 | fi
15 |
16 | if [ -d "$new_bin_dir" ]
17 | then
18 | rm -rf "$new_bin_dir"
19 | fi
20 | }
21 |
22 | trap 'cleanup' EXIT
23 |
24 | run_regular() {
25 | new_bin_dir="$(mktemp -d)"
26 | mkdir "$new_bin_dir/usr"
27 | # -s makes symlinks
28 | cp -rs /usr/bin "$new_bin_dir/usr/bin"
29 |
30 | # Delete mergerfs and unionfs and set the new PATH to the temporary directory
31 | rm -f "$new_bin_dir/usr/bin/mergerfs" 2>/dev/null
32 | rm -f "$new_bin_dir/usr/bin/unionfs" 2>/dev/null
33 |
34 | echo hi >expected
35 | PATH="$new_bin_dir/usr/bin" "$TRY" -y "echo hi" >target 2>/dev/null || exit 1
36 | diff -q expected target || exit 2
37 | }
38 |
39 | run_nix() {
40 | cat > shell.nix <<'EOF'
41 | { pkgs ? import {} }:
42 | pkgs.mkShell {
43 | buildInputs = with pkgs; [
44 | attr
45 | ];
46 | }
47 | EOF
48 |
49 | echo hi >expected
50 | # Run the command in a nix-shell with only the specified packages
51 | nix-shell --pure shell.nix --run "\"$TRY\" -y \"echo hi\"" >target 2>/dev/null || exit 3
52 | diff -q expected target || exit 4
53 | }
54 |
55 | # particularly important that we run in mktemp: in some test machines,
56 | # the cwd is mounted, hence inaccessable.
57 | try_workspace="$(mktemp -d)"
58 | cd "$try_workspace" || exit 9
59 |
60 | if [ -e /etc/NIXOS ]
61 | then
62 | run_nix
63 | else
64 | run_regular
65 | fi
66 |
--------------------------------------------------------------------------------
/test/mkdir_on_file.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | TRY_TOP="${TRY_TOP:-$(git rev-parse --show-toplevel --show-superproject-working-tree 2>/dev/null || echo "${0%/*}")}"
4 | TRY="$TRY_TOP/try"
5 |
6 | cleanup() {
7 | cd /
8 |
9 | if [ -d "$try_workspace" ]
10 | then
11 | rm -rf "$try_workspace" >/dev/null 2>&1
12 | fi
13 | }
14 |
15 | trap 'cleanup' EXIT
16 |
17 | try_workspace="$(mktemp -d)"
18 | cd "$try_workspace" || exit 9
19 |
20 | # Set up expected output
21 | touch target
22 | mkdir expected
23 |
24 | "$TRY" -y "rm target; mkdir target" || exit 1
25 | diff -qr expected target
26 |
--------------------------------------------------------------------------------
/test/network.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | TRY_TOP="${TRY_TOP:-$(git rev-parse --show-toplevel --show-superproject-working-tree 2>/dev/null || echo "${0%/*}")}"
4 | TRY="$TRY_TOP/try"
5 |
6 | # Test if network works normally
7 | # using curl due to #131 (1.1 expands to 1.0.0.1)
8 | "$TRY" curl 1.1 || exit 1
9 |
10 | # Test if curl fails when network is unshared
11 | # curl exit code 7 means Failed to connect to host.
12 | "$TRY" -x curl 1.1
13 | if [ $? -eq 7 ]
14 | then
15 | exit 0
16 | else
17 | exit 1
18 | fi
19 |
--------------------------------------------------------------------------------
/test/newline-filename.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # needs-try-utils
4 |
5 | TRY_TOP="${TRY_TOP:-$(git rev-parse --show-toplevel --show-superproject-working-tree 2>/dev/null || echo "${0%/*}")}"
6 | TRY="$TRY_TOP/try"
7 |
8 | cleanup() {
9 | cd /
10 |
11 | if [ -d "$try_workspace" ]
12 | then
13 | rm -rf "$try_workspace" >/dev/null 2>&1
14 | fi
15 | }
16 |
17 | trap 'cleanup' EXIT
18 |
19 | try_workspace="$(mktemp -d)"
20 | cd "$try_workspace" || exit 9
21 |
22 | badname="$(printf "this\\nsucks")"
23 | touch "$badname" || exit 1
24 | [ -f "$badname" ] || exit 2
25 | # shellcheck disable=SC2016
26 | "$TRY" -y 'rm "$(printf "this\nsucks")"' || exit 3
27 | ! [ -f "$badname" ] || exit 4
28 |
--------------------------------------------------------------------------------
/test/nonexistent_sandbox.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | TRY_TOP="${TRY_TOP:-$(git rev-parse --show-toplevel --show-superproject-working-tree 2>/dev/null || echo "${0%/*}")}"
4 | TRY="$TRY_TOP/try"
5 |
6 | cleanup() {
7 | cd /
8 |
9 | if [ -d "$try_workspace" ]
10 | then
11 | rm -rf "$try_workspace" >/dev/null 2>&1
12 | fi
13 |
14 | # shouldn't fire
15 | if [ -d "$try_example_dir" ]
16 | then
17 | rm -rf "$try_example_dir"
18 | fi
19 | }
20 |
21 | trap 'cleanup' EXIT
22 |
23 | try_workspace="$(mktemp -d)"
24 | cp "$TRY_TOP/test/resources/file.txt.gz" "$try_workspace/"
25 | cd "$try_workspace" || exit 9
26 |
27 | try_example_dir="non-existent"
28 | ! [ -d "$try_example_dir" ] || exit 2
29 | ! "$TRY" -D $try_example_dir "touch file_1.txt" 2>/dev/null &&
30 | ! "$TRY" summary $try_example_dir 2>/dev/null &&
31 | ! "$TRY" commit $try_example_dir 2>/dev/null &&
32 | ! "$TRY" explore $try_example_dir 2>/dev/null
33 |
--------------------------------------------------------------------------------
/test/pipeline.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | TRY_TOP="${TRY_TOP:-$(git rev-parse --show-toplevel --show-superproject-working-tree 2>/dev/null || echo "${0%/*}")}"
4 | TRY="$TRY_TOP/try"
5 |
6 | cleanup() {
7 | cd /
8 |
9 | if [ -d "$try_workspace" ]
10 | then
11 | rm -rf "$try_workspace" >/dev/null 2>&1
12 | fi
13 |
14 | if [ -f "$expected" ]
15 | then
16 | rm "$expected"
17 | fi
18 | }
19 |
20 | trap 'cleanup' EXIT
21 |
22 | try_workspace="$(mktemp -d)"
23 | cp "$TRY_TOP/test/resources/file.txt.gz" "$try_workspace/"
24 | cd "$try_workspace" || exit 9
25 |
26 | # Set up expected output
27 | expected="$(mktemp)"
28 | echo 'TesT' >"$expected"
29 |
30 | "$TRY" 'echo test | tr t T' >out.txt || exit 1
31 |
32 | diff -q "$expected" out.txt
33 |
--------------------------------------------------------------------------------
/test/resources/file.txt.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binpash/try/351896f51409423aeaa93f6d1fed98614035751f/test/resources/file.txt.gz
--------------------------------------------------------------------------------
/test/reuse_problematic_sandbox.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | TRY_TOP="${TRY_TOP:-$(git rev-parse --show-toplevel --show-superproject-working-tree 2>/dev/null || echo "${0%/*}")}"
4 | TRY="$TRY_TOP/try"
5 |
6 | cleanup() {
7 | cd /
8 |
9 | if [ -d "$try_workspace" ]
10 | then
11 | rm -rf "$try_workspace" >/dev/null 2>&1
12 | fi
13 |
14 | if [ -d "$try_example_dir" ]
15 | then
16 | rm -rf "$try_example_dir"
17 | fi
18 | }
19 |
20 | trap 'cleanup' EXIT
21 |
22 | try_workspace="$(mktemp -d)"
23 | cp "$TRY_TOP/test/resources/file.txt.gz" "$try_workspace/"
24 | cd "$try_workspace" || exit 9
25 |
26 | try_example_dir=$(mktemp -d)
27 | "$TRY" -D "$try_example_dir" "touch file_1.txt; echo test >file_2.txt; rm file.txt.gz" || exit 1
28 |
29 | # KK 2023-06-29 This test is meant to modify the sandbox directory in an illegal way,
30 | # at the moment, this modification will be caught as illegal by `try`,
31 | # but it doesn't seem to both overlayfs at all.
32 | # TODO: Extend this with more problematic overlayfs modifications.
33 | touch "$try_example_dir/temproot/etc/foo"
34 | ! "$TRY" -D "$try_example_dir" "rm file_1.txt; echo test2 >>file_2.txt; touch file.txt.gz" 2>/dev/null
35 |
--------------------------------------------------------------------------------
/test/reuse_sandbox.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | TRY_TOP="${TRY_TOP:-$(git rev-parse --show-toplevel --show-superproject-working-tree 2>/dev/null || echo "${0%/*}")}"
4 | TRY="$TRY_TOP/try"
5 |
6 | cleanup() {
7 | cd /
8 |
9 | if [ -d "$try_workspace" ]
10 | then
11 | rm -rf "$try_workspace" >/dev/null 2>&1
12 | fi
13 |
14 | if [ -d "$try_example_dir" ]
15 | then
16 | rm -rf "$try_example_dir"
17 | fi
18 |
19 | if [ -f "$expected1" ]
20 | then
21 | rm "$expected1"
22 | fi
23 |
24 | if [ -f "$expected2" ]
25 | then
26 | rm "$expected2"
27 | fi
28 | }
29 |
30 | trap 'cleanup' EXIT
31 |
32 | try_workspace="$(mktemp -d)"
33 | cp "$TRY_TOP/test/resources/file.txt.gz" "$try_workspace/"
34 | cd "$try_workspace" || exit 9
35 |
36 | expected1="$(mktemp)"
37 | expected2="$(mktemp)"
38 | echo test >"$expected1"
39 | echo test2 >>"$expected1"
40 | touch "$expected2"
41 |
42 | try_example_dir="$(mktemp -d)"
43 | "$TRY" -D "$try_example_dir" "touch file_1.txt; echo test >file_2.txt; rm file.txt.gz" || exit 1
44 | "$TRY" -D "$try_example_dir" "rm file_1.txt; echo test2 >>file_2.txt; touch file.txt.gz" || exit 2
45 | "$TRY" commit "$try_example_dir" || exit 3
46 |
47 | ! [ -f file_1.txt ] || exit 4
48 | diff -q "$expected1" file_2.txt || exit 5
49 | diff -q "$expected2" file.txt.gz || exit 6
50 |
--------------------------------------------------------------------------------
/test/stdstream.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | TRY_TOP="${TRY_TOP:-$(git rev-parse --show-toplevel --show-superproject-working-tree 2>/dev/null || echo "${0%/*}")}"
4 | TRY="$TRY_TOP/try"
5 |
6 | cmdfile="$(mktemp)"
7 |
8 | cat > "$cmdfile" <<'EOF'
9 | read x < /dev/stdin
10 | echo $((x * 2)) > /dev/stdout
11 | echo $((x * 3)) > /dev/stderr
12 |
13 | EOF
14 |
15 | chmod +x "$cmdfile"
16 |
17 | try_stdout=$(mktemp)
18 | try_stderr=$(mktemp)
19 | sh_stdout=$(mktemp)
20 | sh_stderr=$(mktemp)
21 |
22 | # test stdout
23 | echo 5 | "$TRY" "$cmdfile" >"$try_stdout" 2>"$try_stderr"
24 | echo 5 | sh "$cmdfile" >"$sh_stdout" 2>"$sh_stderr"
25 |
26 | diff "$try_stdout" "$sh_stdout" || exit 1
27 |
28 | # using grep because there's try errors printed
29 | grep -q 15 "$try_stderr"
30 | grep -q 15 "$sh_stderr"
31 |
32 | rm "$try_stdout" "$try_stderr" "$sh_stdout" "$sh_stderr"
33 |
34 | cat > "$cmdfile" <<'EOF'
35 | read x <&0
36 | echo $((x * 2)) >&1
37 | echo $((x * 3)) >&2
38 |
39 | EOF
40 |
41 | # test stdout
42 | echo 5 | "$TRY" "$cmdfile" >"$try_stdout" 2>"$try_stderr"
43 | echo 5 | sh "$cmdfile" >"$sh_stdout" 2>"$sh_stderr"
44 |
45 | diff "$try_stdout" "$sh_stdout" || exit 1
46 |
47 | # using grep because there's try errors printed
48 | grep -q 15 "$try_stderr"
49 | grep -q 15 "$sh_stderr"
50 |
51 | rm "$try_stdout" "$try_stderr" "$sh_stdout" "$sh_stderr"
52 |
--------------------------------------------------------------------------------
/test/summary.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | TRY_TOP="${TRY_TOP-$(git rev-parse --show-toplevel --show-superproject-working-tree 2>/dev/null || echo "${0%/*}")}"
4 | TRY="$TRY_TOP/try"
5 |
6 | cleanup() {
7 | cd /
8 |
9 | if [ -d "$try_workspace" ]
10 | then
11 | rm -rf "$try_workspace" >/dev/null 2>&1
12 | fi
13 |
14 | if [ -d "$try_example_dir" ]
15 | then
16 | rm -rf "$try_example_dir"
17 | fi
18 | }
19 |
20 | trap 'cleanup' EXIT
21 |
22 | try_workspace="$(mktemp -d)"
23 | cd "$try_workspace" || exit 9
24 |
25 | cp "$TRY_TOP/test/resources/file.txt.gz" "$try_workspace/"
26 |
27 | # Set up expected output
28 | touch expected1.txt
29 | echo 'test' >expected2.txt
30 |
31 | echo 'fail' >file_2.txt
32 | touch target
33 |
34 | try_example_dir=$(mktemp -d)
35 | "$TRY" -D "$try_example_dir" "touch file_1.txt; echo test >file_2.txt; rm file.txt.gz; rm target; mkdir target; mkdir new_dir" || exit 1
36 | "$TRY" summary "$try_example_dir" >summary.out || exit 2
37 |
38 | # Check that the summary correctly identifies every change
39 | grep -qx -e "$PWD/file_1.txt (added)" summary.out || exit 3
40 | grep -qx -e "$PWD/file_2.txt (modified)" summary.out || exit 4
41 | grep -qx -e "$PWD/file.txt.gz (deleted)" summary.out || exit 5
42 | grep -qx -e "$PWD/target (replaced with dir)" summary.out || exit 6
43 | grep -qx -e "$PWD/new_dir (created dir)" summary.out || exit 7
44 |
--------------------------------------------------------------------------------
/test/symlinks.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | TRY_TOP="${TRY_TOP:-$(git rev-parse --show-toplevel --show-superproject-working-tree 2>/dev/null || echo "${0%/*}")}"
4 | TRY="$TRY_TOP/try"
5 |
6 | cleanup() {
7 | cd /
8 |
9 | if [ -d "$try_workspace" ]
10 | then
11 | rm -rf "$try_workspace" >/dev/null 2>&1
12 | fi
13 | }
14 |
15 | trap 'cleanup' EXIT
16 |
17 | try_workspace="$(mktemp -d)"
18 | cd "$try_workspace" || exit 9
19 |
20 | "$TRY" -y ln -s foo bar
21 |
22 | result=$(readlink bar)
23 | expected="foo"
24 |
25 | [ "$result" = "$expected" ] || exit 1
26 |
--------------------------------------------------------------------------------
/test/tempfiles.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # shellcheck disable=SC2010,SC2126,SC2181
3 |
4 | TRY_TOP="${TRY_TOP:-$(git rev-parse --show-toplevel --show-superproject-working-tree 2>/dev/null || echo "${0%/*}")}"
5 | TRY="$TRY_TOP/try"
6 |
7 | workdir="$(mktemp -d)"
8 | cd "$workdir" || exit 1
9 |
10 | initial_count="$(ls "${TMPDIR-/tmp}" | grep -e "^.*\.try-[0-9]*$" | wc -l)"
11 |
12 | sandbox=$($TRY -n "touch $HOME/foo")
13 | [ $? -eq 0 ] || exit 2
14 |
15 | post_count="$(ls "${TMPDIR-/tmp}" | grep -e "^.*\.try-[0-9]*$" | wc -l)"
16 |
17 | # just one new tempfile
18 | [ "$((initial_count + 1))" -eq "$post_count" ] || exit 3
19 | [ -f "$sandbox/upperdir$HOME/foo" ] || exit 4
20 |
21 | # deliberately not the pattern of try sandboxes
22 | sandbox=local
23 | mkdir "$sandbox" || exit 5
24 | $TRY -D "$sandbox" "touch $HOME/bar" || exit 6
25 |
26 | final_count="$(ls "${TMPDIR-/tmp}" | grep -e "^.*\.try-[0-9]*$" | wc -l)"
27 |
28 | # no new tempfiles!
29 | [ "$post_count" -eq "$final_count" ] || exit 7
30 | [ -f "$sandbox/upperdir$HOME/bar" ] || exit 8
31 |
--------------------------------------------------------------------------------
/test/toplevel-perms.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | TRY_TOP="${TRY_TOP:-$(git rev-parse --show-toplevel --show-superproject-working-tree 2>/dev/null || echo "${0%/*}")}"
4 | TRY="$TRY_TOP/try"
5 |
6 | cleanup() {
7 | cd /
8 |
9 | if [ -d "$try_workspace" ]
10 | then
11 | rm -rf "$try_workspace" >/dev/null 2>&1
12 | fi
13 |
14 | if [ -f "$expected" ]
15 | then
16 | rm "$expected"
17 | fi
18 |
19 | if [ -f "$target" ]
20 | then
21 | rm "$target"
22 | fi
23 |
24 | if [ -f "$cmd" ]
25 | then
26 | rm "$cmd"
27 | fi
28 | }
29 |
30 | trap 'cleanup' EXIT
31 |
32 | try_workspace="$(mktemp -d)"
33 | cd "$try_workspace" || exit 9
34 | touch test
35 |
36 | cmd="$(mktemp)"
37 | echo "find / -maxdepth 1 -print0 | xargs -0 ls -ld | awk '{print substr(\$1, 1, 10), \$9, \$10, \$11}' | grep -v 'proc' | grep -v 'swap' | grep -v 'vmlinuz' | grep -v 'initrd'" > "$cmd"
38 | # Use this after gidmapper to show user and group ownership
39 | #echo "find / -maxdepth 1 -print0 | xargs -0 ls -ld | awk '{print substr(\$1, 1, 10), \$3, \$4, \$9, \$10, \$11}' | grep -v 'proc' | grep -v 'swap'" > "$cmd"
40 |
41 | # Set up expected output
42 | expected="$(mktemp)"
43 | sh "$cmd" >"$expected"
44 |
45 | # Set up target output
46 | target="$(mktemp)"
47 |
48 | "$TRY" "sh $cmd" > "$target" || exit 1
49 | #diff -q "$expected" "$target"
50 | diff "$expected" "$target"
51 |
--------------------------------------------------------------------------------
/test/touch_and_rm_D_flag_commit.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | TRY_TOP="${TRY_TOP:-$(git rev-parse --show-toplevel --show-superproject-working-tree 2>/dev/null || echo "${0%/*}")}"
4 | TRY="$TRY_TOP/try"
5 |
6 | cleanup() {
7 | cd /
8 |
9 | if [ -d "$try_workspace" ]
10 | then
11 | rm -rf "$try_workspace" >/dev/null 2>&1
12 | fi
13 |
14 | if [ -d "$try_example_dir" ]
15 | then
16 | rm -rf "$try_example_dir"
17 | fi
18 |
19 | if [ -f "$expected1" ]
20 | then
21 | rm "$expected1"
22 | fi
23 |
24 | if [ -f "$expected2" ]
25 | then
26 | rm "$expected2"
27 | fi
28 | }
29 |
30 | trap 'cleanup' EXIT
31 |
32 | try_workspace="$(mktemp -d)"
33 | cp "$TRY_TOP/test/resources/file.txt.gz" "$try_workspace/"
34 | cd "$try_workspace" || exit 9
35 |
36 | expected1="$(mktemp)"
37 | expected2="$(mktemp)"
38 | touch "$expected1"
39 | echo 'test' >"$expected2"
40 |
41 | try_example_dir="$(mktemp -d)"
42 | "$TRY" -D "$try_example_dir" "touch file_1.txt; echo test >file_2.txt; rm file.txt.gz" || exit 1
43 | "$TRY" commit "$try_example_dir" || exit 2
44 |
45 | diff -q "$expected1" file_1.txt || exit 3
46 | diff -q "$expected2" file_2.txt || exit 4
47 | ! [ -f file.txt.gz ] || exit 5
48 |
--------------------------------------------------------------------------------
/test/touch_and_rm_no_flag.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | TRY_TOP="${TRY_TOP:-$(git rev-parse --show-toplevel --show-superproject-working-tree 2>/dev/null || echo "${0%/*}")}"
4 | TRY="$TRY_TOP/try"
5 |
6 | cleanup() {
7 | cd /
8 |
9 | if [ -d "$try_workspace" ]
10 | then
11 | rm -rf "$try_workspace" >/dev/null 2>&1
12 | fi
13 |
14 | if [ -f "$expected1" ]
15 | then
16 | rm "$expected1"
17 | fi
18 |
19 | if [ -f "$expected2" ]
20 | then
21 | rm "$expected2"
22 | fi
23 | }
24 |
25 | trap 'cleanup' EXIT
26 |
27 | try_workspace="$(mktemp -d)"
28 | cp "$TRY_TOP/test/resources/file.txt.gz" "$try_workspace/"
29 | cd "$try_workspace" || exit 9
30 |
31 | expected1="$(mktemp)"
32 | expected2="$(mktemp)"
33 | touch "$expected1"
34 | echo 'test' >"$expected2"
35 |
36 | "$TRY" -y "touch file_1.txt; echo test > file_2.txt; rm file.txt.gz" || exit 1
37 |
38 | diff -q "$expected1" file_1.txt || exit 2
39 | diff -q "$expected2" file_2.txt || exit 3
40 | ! [ -f file.txt.gz ] || exit 4
41 |
--------------------------------------------------------------------------------
/test/touch_and_rm_with_cleanup.sh.skip:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | TRY_TOP=${TRY_TOP:-$(git rev-parse --show-toplevel --show-superproject-working-tree)}
4 | TRY="$TRY_TOP/try"
5 | RESOURCE_DIR="$TRY_TOP/test/resources"
6 |
7 | cleanup() {
8 | if [ -d "$try_workspace" ]
9 | then
10 | rm -rf "$try_workspace"
11 | fi
12 |
13 | if [ -d "$try_example_dir" ]
14 | then
15 | rm -rf "$try_example_dir"
16 | fi
17 | }
18 |
19 | trap 'cleanup' EXIT
20 |
21 | try_workspace="$(mktemp -d)"
22 |
23 | cp $RESOURCE_DIR/file.txt.gz "$try_workspace/"
24 | cd "$try_workspace/"
25 |
26 | : ${TMPDIR=/tmp}
27 |
28 | orig_tmp=$(ls "$TMPDIR")
29 | "$try" -y -- "touch file_1.txt; echo test > file_2.txt; rm file.txt.gz" || return 1
30 | new_tmp=$(ls "$TMPDIR")
31 |
32 | if ! diff -q <(echo "$orig_tmp") <(echo "$new_tmp")
33 | then
34 | echo "temporary directory was not cleaned up; diff:"
35 | diff --color -u <(echo "$orig_tmp") <(echo "$new_tmp")
36 | return 1
37 | fi
38 |
--------------------------------------------------------------------------------
/test/try-commit-c.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # needs-try-utils
4 |
5 | TRY_TOP="${TRY_TOP:-$(git rev-parse --show-toplevel --show-superproject-working-tree 2>/dev/null || echo "${0%/*}")}"
6 | TRY="$TRY_TOP/try"
7 |
8 | cleanup() {
9 | cd /
10 |
11 | if [ -d "$try_workspace" ]
12 | then
13 | rm -rf "$try_workspace" >/dev/null 2>&1
14 | fi
15 | }
16 |
17 | trap 'cleanup' EXIT
18 |
19 | try_workspace="$(mktemp -d)"
20 | cd "$try_workspace" || exit 99
21 |
22 | ! [ -e f ] || exit 1
23 | sandbox=$("$TRY" -n "echo hi>f")
24 | # shellcheck disable=SC2181
25 | [ "$?" = 0 ] || exit 2
26 | [ -d "$sandbox" ] || exit 3
27 | [ -d "$sandbox/upperdir" ] || exit 4
28 | [ -f "$sandbox/upperdir/$try_workspace/f" ] || exit 5
29 | [ "$(cat "$sandbox/upperdir/$try_workspace/f")" = "hi" ] || exit 6
30 | try-summary "$sandbox" | grep -q "$try_workspace/f (added)" || exit 7
31 | ! [ -e f ] || exit 8
32 | try-commit -c "$sandbox" || exit 9
33 | [ -f f ] || exit 10
34 | [ "$(cat f)" = "hi" ] || exit 11
35 | [ -f "$sandbox/upperdir/$try_workspace/f" ] || exit 12
36 | [ "$(cat "$sandbox/upperdir/$try_workspace/f")" = "hi" ] || exit 13
37 |
--------------------------------------------------------------------------------
/test/unzip_D_flag_commit.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | TRY_TOP="${TRY_TOP:-$(git rev-parse --show-toplevel --show-superproject-working-tree 2>/dev/null || echo "${0%/*}")}"
4 | TRY="$TRY_TOP/try"
5 |
6 | cleanup() {
7 | cd /
8 |
9 | if [ -d "$try_workspace" ]
10 | then
11 | rm -rf "$try_workspace" >/dev/null 2>&1
12 | fi
13 |
14 | if [ -d "$try_example_dir" ]
15 | then
16 | rm -rf "$try_example_dir"
17 | fi
18 |
19 | if [ -f "$expected" ]
20 | then
21 | rm "$expected"
22 | fi
23 | }
24 |
25 | trap 'cleanup' EXIT
26 |
27 | try_workspace=$(mktemp -d)
28 | cd "$try_workspace" || exit 9
29 |
30 | # Set up expected output
31 | expected="$(mktemp)"
32 | echo 'Hello World!' >"$expected"
33 | cp "$TRY_TOP/test/resources/file.txt.gz" "$try_workspace/"
34 |
35 | try_example_dir="$(mktemp -d)"
36 | "$TRY" -D "$try_example_dir" gunzip file.txt.gz || exit 1
37 | "$TRY" commit "$try_example_dir" || exit 2
38 | diff -q "$expected" file.txt || exit 3
39 |
--------------------------------------------------------------------------------
/test/unzip_D_flag_commit_without_cleanup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | TRY_TOP="${TRY_TOP:-$(git rev-parse --show-toplevel --show-superproject-working-tree 2>/dev/null || echo "${0%/*}")}"
4 | TRY="$TRY_TOP/try"
5 |
6 | cleanup() {
7 | cd /
8 |
9 | if [ -d "$try_workspace" ]
10 | then
11 | rm -rf "$try_workspace" >/dev/null 2>&1
12 | fi
13 |
14 | if [ -d "$try_example_dir" ]
15 | then
16 | rm -rf "$try_example_dir"
17 | fi
18 | }
19 |
20 | trap 'cleanup' EXIT
21 |
22 | try_workspace="$(mktemp -d)"
23 | cd "$try_workspace" || exit 9
24 |
25 | cp "$TRY_TOP/test/resources/file.txt.gz" "$try_workspace/"
26 | try_example_dir="$(mktemp -d)"
27 |
28 | "$TRY" -D "$try_example_dir" gunzip file.txt.gz || exit 1
29 | if ! [ -d "$try_example_dir" ]
30 | then
31 | echo "try_example_dir disappeared with no commit"
32 | exit 2
33 | fi
34 | "$TRY" commit "$try_example_dir" || exit 3
35 | if ! [ -d "$try_example_dir" ]
36 | then
37 | echo "try_example_dir disappeared after manual commit"
38 | exit 4
39 | fi
40 |
--------------------------------------------------------------------------------
/test/unzip_no_flag.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | TRY_TOP="${TRY_TOP:-$(git rev-parse --show-toplevel --show-superproject-working-tree 2>/dev/null || echo "${0%/*}")}"
4 | TRY="$TRY_TOP/try"
5 |
6 | cleanup() {
7 | cd /
8 |
9 | if [ -d "$try_workspace" ]
10 | then
11 | rm -rf "$try_workspace" >/dev/null 2>&1
12 | fi
13 |
14 | if [ -f "$expected" ]
15 | then
16 | rm "$expected"
17 | fi
18 | }
19 |
20 | trap 'cleanup' EXIT
21 |
22 | try_workspace="$(mktemp -d)"
23 | cd "$try_workspace" || exit 9
24 |
25 | # Set up expected output
26 | expected="$(mktemp)"
27 | echo 'Hello World!' >"$expected"
28 |
29 | cp "$TRY_TOP/test/resources/file.txt.gz" "$try_workspace/"
30 |
31 | "$TRY" -y gunzip file.txt.gz || exit 1
32 | diff -q "$expected" file.txt
33 |
--------------------------------------------------------------------------------
/try:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Copyright (c) 2023 The PaSh Authors.
4 | #
5 | # Usage of this source code is governed by the MIT license, you can find the
6 | # LICENSE file in the root directory of this project.
7 | #
8 | # https://github.com/binpash/try
9 |
10 | TRY_VERSION="0.2.0"
11 | TRY_COMMAND="${0##*/}"
12 | EXECID="$(date +%s%3N)"
13 | export EXECID
14 | export TRY_COMMAND
15 |
16 | # exit status invariants
17 | #
18 | # 0 -- command ran
19 | # 1 -- consistency error/failure
20 | # 2 -- input error
21 |
22 | ################################################################################
23 | # Run a command (in `$@`) in an overlay (in `$SANDBOX_DIR`)
24 | ################################################################################
25 |
26 | try() {
27 | START_DIR="$PWD"
28 |
29 | if [ "$SANDBOX_DIR" ]
30 | then
31 | ## If the name of a sandbox is given then we need to exit prematurely if its directory doesn't exist
32 | [ -d "$SANDBOX_DIR" ] || error "could not find sandbox directory $SANDBOX_DIR" 2
33 | # Force absolute path
34 | SANDBOX_DIR="$(cd "$SANDBOX_DIR" && pwd)"
35 |
36 | # shellcheck disable=SC2181
37 | [ "$?" -eq 0 ] || error "could not find sandbox directory $SANDBOX_DIR (could not cd in)" 2
38 | else
39 | ## Create a new sandbox if one was not given
40 | SANDBOX_DIR="$(mktemp -d --suffix ".try-$EXECID")"
41 | fi
42 |
43 | ## If the sandbox is not valid we exit early
44 | if ! sandbox_valid_or_empty "$SANDBOX_DIR"
45 | then
46 | error "given sandbox '$SANDBOX_DIR' is invalid" 1
47 | fi
48 |
49 | ## Make any directories that don't already exist, this is OK to do here
50 | ## because we have already checked if it valid.
51 | export SANDBOX_DIR
52 |
53 | # We created "$IGNORE_FILE" up front, but now we can stash it in the sandbox.
54 | mv "$IGNORE_FILE" "$SANDBOX_DIR"/ignore
55 | IGNORE_FILE="$SANDBOX_DIR"/ignore
56 |
57 | try_mount_log="$SANDBOX_DIR"/mount.log
58 | export try_mount_log
59 |
60 | # If we're in a docker container, we want to mount tmpfs on sandbox_dir, #136
61 | # tail -n +2 to ignore the first line with the column name
62 | tmpfstype=$(df --output=fstype "$SANDBOX_DIR" | tail -n +2)
63 | if [ "$tmpfstype" = "overlay" ] && [ "$(id -u)" -eq "0" ]
64 | then
65 | echo "mounting sandbox '$SANDBOX_DIR' as tmpfs (underlying fs is overlayfs)" >> "$try_mount_log"
66 | echo "consider docker volumes if you want persistence" >> "$try_mount_log"
67 | mount -t tmpfs tmpfs "$SANDBOX_DIR"
68 | fi
69 |
70 | mkdir -p "$SANDBOX_DIR/upperdir" "$SANDBOX_DIR/workdir" "$SANDBOX_DIR/temproot"
71 |
72 | ## Find all the directories and mounts that need to be mounted
73 | DIRS_AND_MOUNTS="$SANDBOX_DIR"/mounts
74 | export DIRS_AND_MOUNTS
75 | find / -maxdepth 1 >"$DIRS_AND_MOUNTS"
76 | sort -u -o "$DIRS_AND_MOUNTS" "$DIRS_AND_MOUNTS"
77 |
78 | # Calculate UPDATED_DIRS_AND_MOUNTS that contains the merge arguments in LOWER_DIRS
79 | UPDATED_DIRS_AND_MOUNTS="$SANDBOX_DIR"/mounts.updated
80 | export UPDATED_DIRS_AND_MOUNTS
81 | while IFS="" read -r mountpoint
82 | do
83 | new_mountpoint=""
84 | OLDIFS=$IFS
85 | IFS=":"
86 |
87 | for lower_dir in $LOWER_DIRS
88 | do
89 | temp_mountpoint="$lower_dir/upperdir$mountpoint"
90 | # Make sure we put : between, but not at the beginning
91 | new_mountpoint="${new_mountpoint:+$new_mountpoint:}$temp_mountpoint"
92 | done
93 | IFS=$OLDIFS
94 | # Add the original mountpoint at the end
95 | new_mountpoint="${new_mountpoint:+$new_mountpoint:}$mountpoint"
96 | echo "$new_mountpoint" >> "$UPDATED_DIRS_AND_MOUNTS"
97 | done <"$DIRS_AND_MOUNTS"
98 |
99 |
100 | # we will overlay-mount each root directory separately (instead of all at once) because some directories cannot be overlayed
101 | # so we set up the mount points now
102 | #
103 | # KK 2023-06-29 This approach (of mounting each root directory separately) was necessary because we could not mount `/` in an overlay.
104 | # However, this might be solvable using mergerfs/unionfs, allowing us to mount an overlay on a unionfs of the `/` once.
105 | while IFS="" read -r mountpoint
106 | do
107 | ## Only make the directory if the original is a directory too
108 | if [ -d "$mountpoint" ] && ! [ -L "$mountpoint" ]
109 | then
110 | # shellcheck disable=SC2174 # warning acknowledged, "When used with -p, -m only applies to the deepest directory."
111 | mkdir -m "$(stat -c %a "$mountpoint")" -p "${SANDBOX_DIR}/upperdir/${mountpoint}" "${SANDBOX_DIR}/workdir/${mountpoint}" "${SANDBOX_DIR}/temproot/${mountpoint}"
112 | fi
113 | done <"$DIRS_AND_MOUNTS"
114 |
115 | chmod "$(stat -c %a /)" "$SANDBOX_DIR/temproot"
116 |
117 | mount_and_execute="$SANDBOX_DIR"/mount_and_execute.sh
118 | chroot_executable="$SANDBOX_DIR"/chroot_executable.sh
119 | script_to_execute="$SANDBOX_DIR"/script_to_execute.sh
120 |
121 | export chroot_executable
122 | export script_to_execute
123 |
124 | cat >"$mount_and_execute" <<"EOF"
125 | #!/bin/sh
126 |
127 | TRY_COMMAND="$TRY_COMMAND($0)"
128 |
129 | ## A wrapper of `mount -t overlay` to have cleaner looking code
130 | make_overlay() {
131 | sandbox_dir="$1"
132 | lowerdirs="$2"
133 | overlay_mountpoint="$3"
134 | mount -t overlay overlay -o userxattr -o "lowerdir=$lowerdirs,upperdir=$sandbox_dir/upperdir/$overlay_mountpoint,workdir=$sandbox_dir/workdir/$overlay_mountpoint" "$sandbox_dir/temproot/$overlay_mountpoint"
135 | }
136 |
137 |
138 | devices_to_mount="tty null zero full random urandom"
139 |
140 | ## Mounts and unmounts a few select devices instead of the whole `/dev`
141 | mount_devices() {
142 | sandbox_dir="$1"
143 | for dev in $devices_to_mount
144 | do
145 | touch "$sandbox_dir/temproot/dev/$dev"
146 | mount -o bind /dev/$dev "$sandbox_dir/temproot/dev/$dev"
147 | done
148 | }
149 |
150 | unmount_devices() {
151 | sandbox_dir="$1"
152 | for dev in $devices_to_mount
153 | do
154 | umount "$sandbox_dir/temproot/dev/$dev" 2>>"$try_mount_log"
155 | rm -f "$sandbox_dir/temproot/dev/$dev"
156 | done
157 | }
158 |
159 | ## Try to autodetect union helper: {mergerfs | unionfs}
160 | ## Returns an empty string if no union helper is found
161 | autodetect_union_helper() {
162 | if command -v mergerfs >/dev/null; then
163 | UNION_HELPER=mergerfs
164 | elif command -v unionfs >/dev/null; then
165 | UNION_HELPER=unionfs
166 | fi
167 | }
168 |
169 | # Detect if union_helper is set, if not, we try to autodetect them
170 | if [ -z "$UNION_HELPER" ]
171 | then
172 | ## Try to detect the union_helper (the variable could still be empty afterwards).
173 | autodetect_union_helper
174 | fi
175 |
176 | # actually mount the overlays
177 | for mountpoint in $(cat "$UPDATED_DIRS_AND_MOUNTS")
178 | do
179 | pure_mountpoint=${mountpoint##*:}
180 |
181 | ## We are not interested in mounts that are not directories
182 | if ! [ -d "$pure_mountpoint" ]
183 | then
184 | continue
185 | fi
186 |
187 | ## Symlinks
188 | if [ -L "$pure_mountpoint" ]
189 | then
190 | ln -s $(readlink "$pure_mountpoint") "$SANDBOX_DIR/temproot/$pure_mountpoint"
191 | continue
192 | fi
193 |
194 | ## Don't do anything for the root and skip if it is /dev or /proc, we will mount it later
195 | case "$pure_mountpoint" in
196 | (/|/dev|/proc) continue;;
197 | esac
198 |
199 | # Try mounting everything normally
200 | make_overlay "$SANDBOX_DIR" "$mountpoint" "$pure_mountpoint" 2>>"$try_mount_log"
201 | # If mounting everything normally fails, we try using either using mergerfs or unionfs to mount them.
202 | if [ "$?" -ne 0 ]
203 | then
204 | ## If the overlay failed, it means that there is a nested mount inside the target mount, e.g., both `/home` and `/home/user/mnt` are mounts.
205 | ## To address this, we use unionfs/mergerfs (they support the same functionality) to show all mounts under the target mount as normal directories.
206 | ## Then we can normally make the overlay on the new union directory.
207 | ##
208 | ## MMG 2025-01-27
209 | ## There used to be more complicated logic here using `findmnt`, but we currently
210 | ## just build unions for every mount in the root.
211 |
212 | if [ -z "$UNION_HELPER" ]
213 | then
214 | ## We can ignore this mountpoint, if the user program tries to use it, it will crash, but if not we can run normally
215 | printf "%s: Warning: Failed mounting $mountpoint as an overlay and mergerfs or unionfs not set and could not be found, see \"$try_mount_log\"\n" "$TRY_COMMAND" >&2
216 | else
217 | merger_dir="$SANDBOX_DIR"/mergerdir"$(echo "$pure_mountpoint" | tr '/' '.')"
218 | mkdir "$merger_dir"
219 |
220 | ## Create a union directory
221 | "$UNION_HELPER" $mountpoint $merger_dir 2>>"$try_mount_log" ||
222 | printf "%s: Warning: Failed mounting $mountpoint via $UNION_HELPER, see \"$try_mount_log\"\n" "$TRY_COMMAND" >&2
223 | make_overlay "$SANDBOX_DIR" "$merger_dir" "$pure_mountpoint" 2>>"$try_mount_log" ||
224 | printf "%s: Warning: Failed mounting $mountpoint as an overlay via $UNION_HELPER, see \"$try_mount_log\"\n" "$TRY_COMMAND" >&2
225 | fi
226 | fi
227 | done
228 |
229 | ## Mount a few select devices in /dev
230 | mount_devices "$SANDBOX_DIR"
231 |
232 | ## Check if chroot_executable exists, #29
233 | if ! [ -f "$SANDBOX_DIR/temproot/$chroot_executable" ]
234 | then
235 | cp $chroot_executable "$SANDBOX_DIR/temproot/$chroot_executable"
236 | fi
237 |
238 | unshare --root="$SANDBOX_DIR/temproot" /bin/sh "$chroot_executable"
239 | exitcode="$?"
240 |
241 | # unmount the devices
242 | rm "$sandbox_dir/temproot/dev/stdin"
243 | rm "$sandbox_dir/temproot/dev/stdout"
244 | rm "$sandbox_dir/temproot/dev/stderr"
245 |
246 | unmount_devices "$SANDBOX_DIR"
247 |
248 | exit $exitcode
249 | EOF
250 |
251 | # NB we substitute in the heredoc, so the early unsets are okay!
252 | cat >"$chroot_executable" <"$script_to_execute"
267 |
268 | # `$script_to_execute` need not be +x to be sourced
269 | chmod +x "$mount_and_execute" "$chroot_executable"
270 |
271 | # enable job control so interactive commands will play nicely with try asking for user input later(for committing). #5
272 | [ -t 0 ] && set -m
273 |
274 | # --mount: mounting and unmounting filesystems will not affect the rest of the system outside the unshare
275 | # --map-root-user: map to the superuser UID and GID in the newly created user namespace.
276 | # --user: the process will have a distinct set of UIDs, GIDs and capabilities.
277 | # --pid: create a new process namespace (needed fr procfs to work right)
278 | # --fork: necessary if we do --pid
279 | # "Creation of a persistent PID namespace will fail if the --fork option is not also specified."
280 | # shellcheck disable=SC2086 # we want field splitting!
281 | unshare --mount --map-root-user --user --pid --fork $EXTRA_NS "$mount_and_execute"
282 | TRY_EXIT_STATUS=$?
283 |
284 | # remove symlink
285 | # first set temproot to be writible, rhel derivatives defaults / to r-xr-xr-x
286 | chmod 755 "${SANDBOX_DIR}/temproot"
287 | while IFS="" read -r mountpoint
288 | do
289 | pure_mountpoint=${mountpoint##*:}
290 | if [ -L "$pure_mountpoint" ]
291 | then
292 | rm "${SANDBOX_DIR}/temproot/${mountpoint}"
293 | fi
294 | done <"$DIRS_AND_MOUNTS"
295 |
296 | ################################################################################
297 | # commit?
298 |
299 | case "$NO_COMMIT" in
300 | (quiet) ;;
301 | (show) echo "$SANDBOX_DIR";;
302 | (commit) commit;;
303 | (interactive) summary >&2
304 | # shellcheck disable=SC2181
305 | if [ "$?" -eq 0 ]
306 | then
307 | printf "\nCommit these changes? [y/N] " >&2
308 | read -r DO_COMMIT
309 | case "$DO_COMMIT" in
310 | (y|Y|yes|YES) commit;;
311 | (*) echo "Not committing." >&2
312 | echo "$SANDBOX_DIR";;
313 | esac
314 | fi;;
315 | esac
316 | }
317 |
318 | ################################################################################
319 | # Summarize the overlay in `$SANDBOX_DIR`
320 | ################################################################################
321 |
322 | if type try-summary >/dev/null 2>&1
323 | then
324 | summary() {
325 | try-summary -i "$IGNORE_FILE" "$SANDBOX_DIR" || return 1
326 | TRY_EXIT_STATUS=0
327 | }
328 | else
329 | summary() {
330 | if ! [ -d "$SANDBOX_DIR" ]
331 | then
332 | error "could not find directory $SANDBOX_DIR" 2
333 | elif ! [ -d "$SANDBOX_DIR/upperdir" ]
334 | then
335 | error "could not find directory $SANDBOX_DIR/upperdir" 1
336 | fi
337 |
338 | ## Finds all potential changes
339 | changed_files=$(find_upperdir_changes "$SANDBOX_DIR" "$IGNORE_FILE")
340 | summary_output=$(process_changes "$SANDBOX_DIR" "$changed_files")
341 | if [ -z "$summary_output" ]
342 | then
343 | return 1
344 | fi
345 |
346 | echo
347 | echo "Changes detected in the following files:"
348 | echo
349 |
350 | echo "$summary_output" | while IFS= read -r summary_line
351 | do
352 | local_file="$(echo "$summary_line" | cut -c 4-)"
353 | case "$summary_line" in
354 | (ln*) echo "$local_file (symlink)";;
355 | (rd*) echo "$local_file (replaced with dir)";;
356 | (md*) echo "$local_file (created dir)";;
357 | (de*) echo "$local_file (deleted)";;
358 | (mo*) echo "$local_file (modified)";;
359 | (ad*) echo "$local_file (added)";;
360 | esac
361 | done
362 |
363 | TRY_EXIT_STATUS=0
364 | }
365 | fi
366 |
367 | ################################################################################
368 | # Commit the results of an overlay in `$SANDBOX_DIR`
369 | ################################################################################
370 |
371 | if type try-commit >/dev/null 2>&1
372 | then
373 | commit() {
374 | try-commit -i "$IGNORE_FILE" "$SANDBOX_DIR"
375 | TRY_EXIT_STATUS=$?
376 | }
377 | else
378 | commit() {
379 | if ! [ -d "$SANDBOX_DIR" ]
380 | then
381 | error "could not find directory $SANDBOX_DIR" "$TRY_COMMAND" 2
382 | elif ! [ -d "$SANDBOX_DIR/upperdir" ]
383 | then
384 | error "could not find directory $SANDBOX_DIR/upperdir" 1
385 | fi
386 |
387 | changed_files=$(find_upperdir_changes "$SANDBOX_DIR" "$IGNORE_FILE")
388 | summary_output=$(process_changes "$SANDBOX_DIR" "$changed_files")
389 |
390 | TRY_EXIT_STATUS=0
391 | echo "$summary_output" | while IFS= read -r summary_line; do
392 | local_file="$(echo "$summary_line" | cut -c 4-)"
393 | changed_file="$SANDBOX_DIR/upperdir$local_file"
394 | case $summary_line in
395 | (ln*) rm -rf "$local_file"; ln -s "$(readlink "$changed_file")" "$local_file";;
396 | (rd*) rm -rf "$local_file"; mkdir "$local_file";;
397 | (md*) mkdir "$local_file";;
398 | (de*) rm -rf "$local_file";;
399 | (mo*) rm -rf "$local_file"; mv "$changed_file" "$local_file";;
400 | (ad*) mv "$changed_file" "$local_file";;
401 | esac
402 |
403 | # shellcheck disable=SC2181
404 | if [ "$?" -ne 0 ]
405 | then
406 | warn "couldn't commit $changed_file"
407 | TRY_EXIT_STATUS=1
408 | fi
409 | done
410 | }
411 | fi
412 |
413 | ################################################################################
414 | ## Defines which changes we want to ignore in the summary and commit
415 | ################################################################################
416 |
417 | ignore_changes() {
418 | ignore_file="$1"
419 |
420 | grep -v -f "$ignore_file"
421 | }
422 |
423 | ################################################################################
424 | ## Lists all upperdir changes in raw format
425 | ################################################################################
426 |
427 | find_upperdir_changes() {
428 | sandbox_dir="$1"
429 | ignore_file="$2"
430 |
431 | find "$sandbox_dir/upperdir/" -type f -o \( -type c -size 0 \) -o -type d -o -type l | ignore_changes "$ignore_file"
432 | }
433 |
434 | ################################################################################
435 | # Processes upperdir changes to an internal format that can be processed by summary and commit
436 | #
437 | # Output format:
438 | #
439 | # XX PATH
440 | #
441 | # where:
442 | # XX is a two character code for the modification
443 | # - rd: Replaced with a directory
444 | # - md: Created a directory
445 | # - de: Deleted a file
446 | # - mo: Modified a file
447 | # - ad: Added a file
448 | #
449 | # PATH is the local/host path (i.e., without the upper
450 | ################################################################################
451 |
452 | process_changes() {
453 | sandbox_dir="$1"
454 | changed_files="$2"
455 |
456 | while IFS= read -r changed_file
457 | do
458 | local_file="${changed_file#"$sandbox_dir/upperdir"}"
459 | if [ -L "$changed_file" ]
460 | then
461 | # // TRYCASE(symlink, *)
462 | echo "ln $local_file"
463 | elif [ -d "$changed_file" ]
464 | then
465 | if ! [ -e "$local_file" ]
466 | then
467 | # // TRYCASE(dir, nonexist)
468 | echo "md $local_file"
469 | continue
470 | fi
471 |
472 | if [ "$(getfattr --absolute-names --only-values --e text -n user.overlay.opaque "$changed_file" 2>/dev/null)" = "y" ]
473 | then
474 | # // TRYCASE(opaque, *)
475 | # // TRYCASE(dir, dir)
476 | echo "rd $local_file"
477 | continue
478 | fi
479 |
480 | if ! [ -d "$local_file" ]
481 | then
482 | # // TRYCASE(dir, file)
483 | # // TRYCASE(dir, symlink)
484 | echo "rd $local_file"
485 | continue
486 | fi
487 |
488 | # must be a directory, but not opaque---leave it!
489 | elif [ -c "$changed_file" ] && ! [ -s "$changed_file" ] && [ "$(stat -c %t,%T "$changed_file")" = "0,0" ]
490 | then
491 | # // TRYCASE(whiteout, *)
492 | echo "de $local_file"
493 | elif [ -f "$changed_file" ]
494 | then
495 | if [ -f "$changed_file" ] && getfattr --absolute-names -d "$changed_file" 2>/dev/null | grep -q -e "user.overlay.whiteout"
496 | then
497 | # // TRYCASE(whiteout, *)
498 | echo "de $local_file"
499 | continue
500 | fi
501 |
502 | if [ -e "$local_file" ]
503 | then
504 | # // TRYCASE(file, file)
505 | # // TRYCASE(file, dir)
506 | # // TRYCASE(file, symlink)
507 | echo "mo $local_file"
508 | else
509 | # // TRYCASE(file, nonexist)
510 | echo "ad $local_file"
511 | fi
512 | fi
513 | done <&2
553 | }
554 |
555 | ################################################################################
556 | # Emit a warning and exit
557 | ################################################################################
558 |
559 | error() {
560 | msg="$1"
561 | exit_status="$2"
562 |
563 | warn "$msg"
564 | exit "$exit_status"
565 | }
566 |
567 | ################################################################################
568 | # Argument parsing
569 | ################################################################################
570 |
571 | usage() {
572 | cat >&2 <>"$IGNORE_FILE";;
614 | (D) if ! [ -d "$OPTARG" ]
615 | then
616 | error "could not find sandbox directory '$OPTARG'" 2
617 | fi
618 | SANDBOX_DIR="$OPTARG"
619 | NO_COMMIT="quiet";;
620 | (L) if [ -n "$LOWER_DIRS" ]
621 | then
622 | error "the -L option has been specified multiple times" 2
623 | fi
624 | LOWER_DIRS="$OPTARG"
625 | NO_COMMIT="quiet";;
626 | (v) echo "$TRY_COMMAND version $TRY_VERSION" >&2
627 | exit 0;;
628 | (U) if ! [ -x "$OPTARG" ]
629 | then
630 | error "could not find executable union helper '$OPTARG'" 2
631 | fi
632 | UNION_HELPER="$OPTARG"
633 | export UNION_HELPER;;
634 | (x) EXTRA_NS="--net";;
635 | (h|*) usage
636 | exit 0;;
637 | esac
638 | done
639 |
640 | shift $((OPTIND - 1))
641 |
642 | if [ "$#" -eq 0 ]
643 | then
644 | usage
645 | exit 2
646 | fi
647 |
648 | TRY_EXIT_STATUS=1
649 | case "$1" in
650 | (summary) : "${SANDBOX_DIR=$2}"
651 | summary
652 | rm "$IGNORE_FILE" # we didn't move it to the sandbox, so clean up
653 | ;;
654 | (commit) : "${SANDBOX_DIR=$2}"
655 | commit
656 | rm "$IGNORE_FILE" # we didn't move it to the sandbox, so clean up
657 | ;;
658 | (explore) : "${SANDBOX_DIR=$2}"
659 | try "$SHELL";;
660 | (--) shift
661 | try "$@";;
662 | (*) try "$@";;
663 | esac
664 |
665 | exit "$TRY_EXIT_STATUS"
666 |
--------------------------------------------------------------------------------
/utils/.gitignore:
--------------------------------------------------------------------------------
1 | try-summary
2 | try-commit
3 | *.o
4 |
--------------------------------------------------------------------------------
/utils/README.md:
--------------------------------------------------------------------------------
1 | This directory contains C code (GNU dialect, Linux only) that summarizes and commits the changes from `try`, in `try-summary` and `try-commit`, respectively.
2 |
3 | You do _not_ need these utilities to use `try`, which has POSIX shell fallback implementations.
4 |
5 | Every time `try` starts up, it looks for these executables on its path; if found, it will use them---otherwise it will use the shell fallbacks. The shell fallbacks are not quite as robust as these utilities and may behave strangely on files with newlines in their names.
6 |
7 | # Development
8 |
9 | These are meant to short, simple scripts.
10 |
11 | In order to make sure that we keep them parallel, both files are annotated with `TRYCASE(changed, local)` forms in comments that identify which case is being handled.
12 |
13 | Here `changed` and `local` adhere to `lf` the following grammar:
14 |
15 | ```
16 | changed ::= f | opaque | whiteout
17 | local ::= f | *
18 | f ::= file | dir | symlink | nonexist
19 | ```
20 |
21 | The script `../scripts/check_trycase.sh` will check to make sure that all cases are considered (excepting `TRYCASE(opaque, nonexist)`, which should not happen). If new files need to do a case analysis on how changed files and local files relate, annotate each case with `// TRYCASE(cf, lf)` and add a `check_file` call to the `check_trycase.sh` script.
22 |
23 | ## Assumptions
24 |
25 | We rely on `user.overlay.opaque` to be set for opaque directories, which we can assume will happen because `try` sets `-o userxattr` when it mounts the overlay.
26 |
27 | We do not handle [redirect directories](https://docs.kernel.org/filesystems/overlayfs.html#redirect-dir), instead relying on `cp`/`mv` to do the copying.
28 |
29 | We have not seen `user.overlay.whiteout` in practice---perhaps it only gets made when you can't `mknod` a `(0,0)` character device on the upperdir filesystem?
30 |
--------------------------------------------------------------------------------
/utils/ignores.c:
--------------------------------------------------------------------------------
1 | #define _GNU_SOURCE
2 | #include
3 | #include
4 |
5 | #include "ignores.h"
6 |
7 | static regex_t *ignores = NULL;
8 | static size_t ignores_len = 0;
9 | static int num_ignores = 0;
10 |
11 | int should_ignore(char *filename) {
12 | for (int i = 0; i < num_ignores; i += 1) {
13 | if (regexec(&ignores[i], filename, 0, NULL, 0) == 0) {
14 | return 1;
15 | }
16 | }
17 |
18 | return 0;
19 | }
20 |
21 | void load_ignores(char *progname, char *ignore_filename) {
22 | FILE *ignore_file = fopen(ignore_filename, "r");
23 | if (ignore_file == NULL) {
24 | fprintf(stderr, "%s: couldn't load ignore file '%s'\n", progname, ignore_filename);
25 | return;
26 | }
27 |
28 | ignores_len = 4;
29 | ignores = (regex_t *) malloc(sizeof(regex_t) * ignores_len);
30 | num_ignores = 0;
31 |
32 | char *line = NULL;
33 | size_t len = 0;
34 | ssize_t nread;
35 |
36 | while ((nread = getline(&line, &len, ignore_file)) != -1) {
37 | if (num_ignores == ignores_len) {
38 | ignores_len *= 2;
39 | ignores = realloc(ignores, sizeof(regex_t) * ignores_len);
40 | }
41 |
42 | line[nread-1] = '\0'; // kill newline before compilation
43 | int errcode = 0;
44 | if ((errcode = regcomp(&ignores[num_ignores], line, REG_EXTENDED | REG_NOSUB)) != 0) {
45 | size_t err_len = regerror(errcode, &ignores[num_ignores], NULL, 0);
46 | char err[err_len];
47 | regerror(errcode, &ignores[num_ignores], err, err_len);
48 |
49 | fprintf(stderr, "%s: couldn't process regex '%s': %s\n", progname, line, err);
50 | continue;
51 | }
52 |
53 | num_ignores += 1;
54 | }
55 |
56 | free(line);
57 | fclose(ignore_file);
58 | }
59 |
60 | void free_ignores() {
61 | for (int i = 0; i < num_ignores; i += 1) {
62 | regfree(&ignores[i]);
63 | }
64 | free(ignores);
65 | ignores = NULL;
66 | num_ignores = 0;
67 | ignores_len = 0;
68 | }
69 |
--------------------------------------------------------------------------------
/utils/ignores.h:
--------------------------------------------------------------------------------
1 | #ifndef __TRY_IGNORES_H
2 | #define __TRY_IGNORES_H
3 |
4 | #include
5 | #include
6 |
7 | void load_ignores(char *progname, char *ignore_file);
8 | int should_ignore(char *filename);
9 | void free_ignores();
10 |
11 | #endif
12 |
--------------------------------------------------------------------------------
/utils/try-commit.c:
--------------------------------------------------------------------------------
1 | #define _GNU_SOURCE
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 | #include
16 |
17 | #include "ignores.h"
18 | #include "version.h"
19 |
20 | static int should_copy = 0;
21 |
22 | static int num_errors = 0;
23 | void commit_error(char *changed_file, char *msg) {
24 | num_errors += 1;
25 |
26 | fprintf(stderr, "try-commit: couldn't commit %s (%s): %s\n", changed_file, msg, strerror(errno));
27 | }
28 |
29 | void run(char *argv[], char *file) {
30 | pid_t pid = fork();
31 |
32 | if (pid == 0) {
33 | execvp(argv[0], argv);
34 | return; // unreachable
35 | }
36 |
37 | int status = -1;
38 | waitpid(pid, &status, 0);
39 |
40 | if (status != 0) {
41 | commit_error(file, argv[0]);
42 | }
43 | }
44 |
45 | void commit(char *changed_file, char *local_file) {
46 | if (should_copy) {
47 | char *argv[5] = { "cp", "-fa", changed_file, local_file, NULL };
48 | run(argv, changed_file);
49 | return;
50 | }
51 |
52 | if (rename(changed_file, local_file) != 0) {
53 | if (errno != EXDEV) {
54 | commit_error(changed_file, "rename");
55 | return;
56 | }
57 |
58 | // cross-device move... not a simple call. defer to system utility
59 | char *argv[4] = { "mv", changed_file, local_file, NULL };
60 | run(argv, changed_file);
61 | }
62 | }
63 |
64 | void remove_local(char *local_file, int local_exists, struct stat *local_stat) {
65 | if (!local_exists) {
66 | return;
67 | }
68 |
69 | if (S_ISDIR(local_stat->st_mode)) {
70 | char *argv[4] = { "rm", "-rf", local_file, NULL };
71 | run(argv, local_file);
72 | return;
73 | }
74 |
75 | if (unlink(local_file) != 0) {
76 | commit_error(local_file, "rm");
77 | }
78 | }
79 |
80 | void usage(int status) {
81 | fprintf(stderr, "Usage: try-commit [-c] [-i IGNORE_FILE] SANDBOX_DIR\n");
82 | fprintf(stderr, "\t-c\tcopy files instead of moving them\n");
83 | exit(status);
84 | }
85 |
86 | int main(int argc, char *argv[]) {
87 | int opt;
88 | while ((opt = getopt(argc, argv, "hvci:")) != -1) {
89 | switch (opt) {
90 | case 'i':
91 | load_ignores("try-commit", optarg);
92 | break;
93 | case 'c':
94 | should_copy = 1;
95 | break;
96 | case 'v':
97 | fprintf(stderr, "try-commit version " TRY_VERSION "\n");
98 | exit(0);
99 | case 'h':
100 | usage(0);
101 | default:
102 | usage(2);
103 | }
104 | }
105 |
106 | if (argc != optind + 1) {
107 | usage(2);
108 | }
109 |
110 | struct stat sandbox_stat;
111 | if (stat(argv[optind], &sandbox_stat) == -1) {
112 | perror("try-commit: could not find sandbox dircteory");
113 | return 2;
114 | }
115 |
116 | if (!S_ISDIR(sandbox_stat.st_mode)) {
117 | perror("try-commit: sandbox is not a directory");
118 | return 2;
119 | }
120 |
121 | size_t prefix_len = strlen(argv[optind]);
122 |
123 | // trim final slashes
124 | while (argv[optind][prefix_len-1] == '/') {
125 | argv[optind][prefix_len-1] = '\0';
126 | prefix_len -= 1;
127 | }
128 |
129 | char upperdir_path[prefix_len + 10];
130 | strncpy(upperdir_path, argv[optind], prefix_len);
131 | strncpy(upperdir_path + prefix_len, "/upperdir", 10);
132 | prefix_len += 9;
133 |
134 | char *dirs[2] = { upperdir_path, NULL };
135 |
136 | FTS *fts = fts_open(dirs, FTS_PHYSICAL, NULL);
137 |
138 | if (fts == NULL) {
139 | perror("try-commit: fts_open");
140 | return 2;
141 | }
142 |
143 | FTSENT *ent = fts_read(fts);
144 |
145 | // skip first entry (the named directory, corresponds to root)
146 | if (ent == NULL) {
147 | perror("try-commit: fts_read");
148 | return 2;
149 | }
150 | assert(strcmp(ent->fts_path, upperdir_path) == 0);
151 | if (ent->fts_info != FTS_D) {
152 | fprintf(stderr, "try-commit: sandbox upperdir '%s' is not a directory\n", ent->fts_path);
153 | return 1;
154 | }
155 |
156 | while ((ent = fts_read(fts)) != NULL) {
157 | char *local_file = ent->fts_path + prefix_len;
158 |
159 | if (should_ignore(local_file)) { continue; }
160 |
161 | struct stat local_stat;
162 | int local_exists = lstat(local_file, &local_stat) != -1;
163 |
164 | switch (ent->fts_info) {
165 | case FTS_D: // preorder (first visit)
166 | if (!local_exists) {
167 | // TRYCASE(dir, nonexist)
168 | commit(ent->fts_path, local_file);
169 |
170 | // don't traverse children, we copied the whole thing
171 | fts_set(fts, ent, FTS_SKIP);
172 | break;
173 | }
174 |
175 | // special "OPAQUE" whiteout directory--delete the original
176 | char xattr_buf[2] = { '\0', '\0' };
177 | if (getxattr(ent->fts_path, "user.overlay.opaque", xattr_buf, 2) != -1 && xattr_buf[0] == 'y') {
178 | // TRYCASE(opaque, *)
179 | // TRYCASE(dir, dir)
180 | remove_local(local_file, local_exists, &local_stat);
181 |
182 | commit(ent->fts_path, local_file);
183 |
184 | // don't traverse children, we copied the whole thing
185 | fts_set(fts, ent, FTS_SKIP);
186 | break;
187 | }
188 |
189 | // non-directory replaced with a directory
190 | if (!S_ISDIR(local_stat.st_mode)) {
191 | // TRYCASE(dir, file)
192 | // TRYCASE(dir, symlink)
193 |
194 | if (unlink(local_file) != 0) {
195 | commit_error(ent->fts_path, "rm");
196 | }
197 |
198 | commit(ent->fts_path, local_file);
199 |
200 | // don't traverse children, we copied the whole thing
201 | fts_set(fts, ent, FTS_SKIP);
202 | break;
203 | }
204 |
205 | // nothing of interest! directory got made, but modifications must be inside
206 | break;
207 | case FTS_F: // regular file
208 | if (getxattr(ent->fts_path, "user.overlay.whiteout", NULL, 0) != -1) {
209 | // TRYCASE(whiteout, *)
210 | remove_local(local_file, local_exists, &local_stat);
211 | break;
212 | }
213 |
214 | if (local_exists) {
215 | // TRYCASE(file, file)
216 | // TRYCASE(file, dir)
217 | // TRYCASE(file, symlink)
218 | remove_local(local_file, local_exists, &local_stat);
219 | }
220 |
221 | // TRYCASE(file, nonexist)
222 | commit(ent->fts_path, local_file);
223 | break;
224 |
225 | case FTS_SL: // symbolic link
226 | case FTS_SLNONE: // dangling symbolic link
227 | // TRYCASE(symlink, *)
228 | remove_local(local_file, local_exists, &local_stat);
229 |
230 | // absolute shenanigans: what's the target (and how long is its name)?
231 | size_t tgt_len = ent->fts_statp->st_size + 1;
232 | if (tgt_len <= 1) { // procfs (and possibly others) return `st_size` of 0 :(
233 | tgt_len = PATH_MAX;
234 | }
235 |
236 | char *tgt = malloc(sizeof(char) * tgt_len);
237 | int nbytes = readlink(ent->fts_path, tgt, tgt_len);
238 | if (nbytes == -1) {
239 | commit_error(ent->fts_path, "ln -s");
240 | }
241 |
242 | while (nbytes == tgt_len) {
243 | tgt_len *= 2;
244 | tgt = realloc(tgt, sizeof(char) * tgt_len);
245 | nbytes = readlink(ent->fts_path, tgt, tgt_len);
246 | if (nbytes == -1) {
247 | commit_error(ent->fts_path, "ln -s");
248 | }
249 | }
250 | tgt[nbytes] = '\0'; // readlink doesn't put a null byte on the end lol nice work everyone
251 |
252 | if (symlink(tgt, local_file) != 0) {
253 | commit_error(ent->fts_path, "ln -s");
254 | }
255 | free(tgt);
256 | break;
257 |
258 | case FTS_DEFAULT:
259 | if (S_ISCHR(ent->fts_statp->st_mode) && ent->fts_statp->st_size == 0) {
260 | struct statx statxp;
261 | if (statx(AT_FDCWD, ent->fts_path, 0, STATX_TYPE | STATX_INO, &statxp) == -1) {
262 | fprintf(stderr, "try-commit: statx: %s: %s\n", ent->fts_path, strerror(errno));
263 | break;
264 | }
265 |
266 | if (statxp.stx_rdev_major == 0 && statxp.stx_rdev_minor == 0) {
267 | // TRYCASE(whiteout, *)
268 | remove_local(local_file, local_exists, &local_stat);
269 |
270 | break;
271 | }
272 | }
273 |
274 | case FTS_DC: // cycle
275 | case FTS_DP: // postorder (second visit)
276 | break;
277 |
278 | case FTS_DNR:
279 | case FTS_ERR:
280 | default:
281 | fprintf(stderr, "try-commit: fts_read: %s: %s\n", ent->fts_path, strerror(errno));
282 | break; // error with this directory
283 | }
284 | }
285 |
286 | if (errno != 0) {
287 | perror("try-commit: fts_read");
288 | return 2;
289 | }
290 |
291 | fts_close(fts);
292 | free_ignores();
293 |
294 | return num_errors == 0 ? 0 : 1;
295 | }
296 |
--------------------------------------------------------------------------------
/utils/try-summary.c:
--------------------------------------------------------------------------------
1 | #define _GNU_SOURCE
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 |
14 | #include "ignores.h"
15 | #include "version.h"
16 |
17 | static int changes_detected = 0;
18 | void show_change(char *local_file, char *msg) {
19 | if (!changes_detected) {
20 | changes_detected += 1;
21 | fputs("\nChanges detected in the following files:\n\n", stdout);
22 | }
23 |
24 | fputs(local_file, stdout);
25 | fputs(" (", stdout);
26 | fputs(msg, stdout);
27 | fputs(")\n", stdout);
28 | }
29 |
30 | void usage(int status) {
31 | fprintf(stderr, "Usage: try-summary [-i IGNORE_FILE] SANDBOX_DIR\n");
32 | exit(status);
33 | }
34 |
35 | int main(int argc, char *argv[]) {
36 | int opt;
37 | while ((opt = getopt(argc, argv, "hvi:")) != -1) {
38 | switch (opt) {
39 | case 'i':
40 | load_ignores("try-summary", optarg);
41 | break;
42 | case 'v':
43 | fprintf(stderr, "try-summary version " TRY_VERSION "\n");
44 | exit(0);
45 | case 'h':
46 | usage(0);
47 | default:
48 | usage(2);
49 | }
50 | }
51 |
52 | if (argc != optind + 1) {
53 | usage(2);
54 | }
55 |
56 | struct stat sandbox_stat;
57 | if (stat(argv[optind], &sandbox_stat) == -1) {
58 | perror("try-summary: could not find sandbox dircteory");
59 | return 2;
60 | }
61 |
62 | if (!S_ISDIR(sandbox_stat.st_mode)) {
63 | perror("try-summary: sandbox is not a directory");
64 | return 2;
65 | }
66 |
67 | size_t prefix_len = strlen(argv[optind]);
68 |
69 | // trim final slashes
70 | while (argv[optind][prefix_len-1] == '/') {
71 | argv[optind][prefix_len-1] = '\0';
72 | prefix_len -= 1;
73 | }
74 |
75 | char upperdir_path[prefix_len + 10];
76 | strncpy(upperdir_path, argv[optind], prefix_len);
77 | strncpy(upperdir_path + prefix_len, "/upperdir", 10);
78 | prefix_len += 9;
79 |
80 | char *dirs[2] = { upperdir_path, NULL };
81 |
82 | FTS *fts = fts_open(dirs, FTS_PHYSICAL, NULL);
83 |
84 | if (fts == NULL) {
85 | perror("try-summary: fts_open");
86 | return 2;
87 | }
88 |
89 | FTSENT *ent = fts_read(fts);
90 |
91 | // skip first entry (the named directory, corresponds to root)
92 | if (ent == NULL) {
93 | perror("try-summary: fts_read");
94 | return 2;
95 | }
96 | assert(strcmp(ent->fts_path, upperdir_path) == 0);
97 | if (ent->fts_info != FTS_D) {
98 | fprintf(stderr, "try-summary: %s is not a directory\n", ent->fts_path);
99 | return 1;
100 | }
101 |
102 | while ((ent = fts_read(fts)) != NULL) {
103 | char *local_file = ent->fts_path + prefix_len;
104 |
105 | if (should_ignore(local_file)) { continue; }
106 |
107 | struct stat local_stat;
108 | int local_exists = lstat(local_file, &local_stat) != -1;
109 |
110 | switch (ent->fts_info) {
111 | case FTS_D: // preorder (first visit)
112 | if (!local_exists) {
113 | // TRYCASE(dir, nonexist)
114 | show_change(local_file, "created dir");
115 |
116 | // don't traverse children, we copied the whole thing
117 | fts_set(fts, ent, FTS_SKIP);
118 | break;
119 | }
120 |
121 | // special "OPAQUE" whiteout directory--delete the original
122 | char xattr_buf[2] = { '\0', '\0' };
123 | if (getxattr(ent->fts_path, "user.overlay.opaque", xattr_buf, 2) != -1 && xattr_buf[0] == 'y') {
124 | // TRYCASE(opaque, *)
125 | // TRYCASE(dir, dir)
126 | show_change(local_file, "replaced with dir");
127 | break;
128 | }
129 |
130 | // non-directory replaced with a directory
131 | if (!S_ISDIR(local_stat.st_mode)) {
132 | // TRYCASE(dir, file)
133 | // TRYCASE(dir, symlink)
134 | show_change(local_file, "replaced with dir");
135 | break;
136 | }
137 |
138 | // nothing of interest! directory got made, but modifications must be inside
139 | break;
140 | case FTS_F: // regular file
141 | if (getxattr(ent->fts_path, "user.overlay.whiteout", NULL, 0) != -1) {
142 | // TRYCASE(whiteout, *)
143 | show_change(local_file, "deleted");
144 | break;
145 | }
146 |
147 | if (local_exists) {
148 | // TRYCASE(file, file)
149 | // TRYCASE(file, dir)
150 | // TRYCASE(file, symlink)
151 | show_change(local_file, "modified");
152 | break;
153 | }
154 |
155 | // TRYCASE(file, nonexist)
156 | show_change(local_file, "added");
157 | break;
158 |
159 | case FTS_SL: // symbolic link
160 | case FTS_SLNONE: // dangling symbolic link
161 | // TRYCASE(symlink, *)
162 | show_change(local_file, "symlink");
163 | break;
164 |
165 | case FTS_DEFAULT:
166 | if (S_ISCHR(ent->fts_statp->st_mode) && ent->fts_statp->st_size == 0) {
167 | struct statx statxp;
168 | if (statx(AT_FDCWD, ent->fts_path, 0, STATX_TYPE | STATX_INO, &statxp) == -1) {
169 | fprintf(stderr, "try-summary: statx: %s: %s\n", ent->fts_path, strerror(errno));
170 | break;
171 | }
172 |
173 | if (statxp.stx_rdev_major == 0 && statxp.stx_rdev_minor == 0) {
174 | // TRYCASE(whiteout, *)
175 | show_change(local_file, "deleted");
176 | break;
177 | }
178 | }
179 |
180 | case FTS_DC: // cycle
181 | case FTS_DP: // postorder (second visit)
182 | break;
183 |
184 | case FTS_DNR:
185 | case FTS_ERR:
186 | default:
187 | fprintf(stderr, "try-summary: fts_read: %s: %s\n", ent->fts_path, strerror(errno));
188 | break; // error with this directory
189 | }
190 | }
191 |
192 | if (errno != 0) {
193 | perror("try-summary: fts_read");
194 | return 2;
195 | }
196 |
197 | fts_close(fts);
198 | free_ignores();
199 |
200 | return changes_detected == 0 ? 1 : 0;
201 | }
202 |
--------------------------------------------------------------------------------
/utils/version.h:
--------------------------------------------------------------------------------
1 | #ifndef __TRY_VERSION_H
2 | #define __TRY_VERSION_H
3 |
4 | #define TRY_VERSION "0.2.0"
5 |
6 | #endif
7 |
--------------------------------------------------------------------------------