Skip to content

Commit 28c4aa2

Browse files
committedJun 23, 2024
Ignore temporary directories created when unpacking tarballs
1 parent 22ac068 commit 28c4aa2

11 files changed

+256
-41
lines changed
 

‎.github/workflows/test.yml

+3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ jobs:
2020
signingKey: ${{ secrets.CACHIX_SIGNING_KEY }}
2121
- name: Build and install cached-nix-shell
2222
run: nix-env -i -f default.nix
23+
- name: Workaround for Darwin
24+
if: matrix.os == 'macos-latest'
25+
run: cached-nix-shell -p --run true
2326
- name: Test cached-nix-shell
2427
run: ./tests/run.sh
2528
- name: Test nix-trace

‎nix-trace/README.md

+3
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ open() != -1 && read error: `f` FILENAME `\0` `e` `\0`
2929
3030
opendir() == NULL: `d` FILENAME `\0` `-` `\0`
3131
opendir() != NULL: `d` FILENAME `\0` b3sum(directory listing) `\0`
32+
33+
mkdir() == 0 `t` FILENAME `\0` `+` `\0`
34+
unlinkat() == NULL `t` FILENAME `\0` `-` `\0`
3235
```
3336

3437
Directory listing:

‎nix-trace/test.sh

+48-22
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@
22

33
run() {
44
rm -f test-tmp/log
5-
DYLD_INSERT_LIBRARIES=$PWD/build/trace-nix.so LD_PRELOAD=$PWD/build/trace-nix.so TRACE_NIX=test-tmp/log \
6-
nix-shell --run : -p -- "$@" 2>/dev/null
7-
}
8-
9-
run_without_p() {
10-
rm -f test-tmp/log
11-
DYLD_INSERT_LIBRARIES=$PWD/build/trace-nix.so LD_PRELOAD=$PWD/build/trace-nix.so TRACE_NIX=test-tmp/log \
12-
nix-shell --run : -- "$@" 2>/dev/null
5+
env \
6+
DYLD_INSERT_LIBRARIES="$PWD"/build/trace-nix.so \
7+
LD_PRELOAD="$PWD"/build/trace-nix.so \
8+
TRACE_NIX=test-tmp/log \
9+
nix-shell --run : "$@" 2>/dev/null &
10+
NIX_PID=$!
11+
wait
1312
}
1413

1514
result=0
@@ -23,6 +22,8 @@ dir_b3sum() {
2322
}
2423

2524
check() {
25+
local grep_opts=
26+
[ "$1" != "--first" ] || { shift; grep_opts=-m1; }
2627
local name="$1" key="$2" val="$3"
2728

2829
if ! grep -qzFx -- "$key" test-tmp/log; then
@@ -31,7 +32,11 @@ check() {
3132
result=1
3233
fi
3334

34-
local actual_val="$(grep -zFx -A1 -- "$key" test-tmp/log | tail -zn1 | tr -d '\0')"
35+
local actual_val=$(
36+
grep $grep_opts -zFx -A1 -- "$key" test-tmp/log |
37+
tail -zn1 |
38+
tr -d '\0'
39+
)
3540
if [ "$val" != "$actual_val" ]; then
3641
printf "\33[31mFail: %s: expected '%s', got '%s'\33[m\n" \
3742
"$name" "$val" "$actual_val"
@@ -48,71 +53,92 @@ echo '"foo"' > test-tmp/test.nix
4853
: > test-tmp/empty
4954
ln -s empty test-tmp/link
5055

56+
mkdir -p test-tmp/repo/data test-tmp/tmpdir
57+
echo '{}' > test-tmp/repo/data/default.nix
58+
tar -C test-tmp/repo -cf test-tmp/repo.tar .
59+
5160
x=""
5261
for i in {1..64};do
5362
x=x$x
5463
mkdir -p test-tmp/many-dirs/$x
5564
done
5665

57-
run 'with import <unstable> {}; bash'
66+
export XDG_CACHE_HOME="$PWD/test-tmp/xdg-cache"
67+
export TMPDIR="$PWD/test-tmp/tmpdir"
68+
69+
70+
run -p 'with import <unstable> {}; bash'
5871
check import-channel \
5972
"s/nix/var/nix/profiles/per-user/root/channels/unstable" \
6073
"l$(readlink /nix/var/nix/profiles/per-user/root/channels/unstable)"
6174

62-
run 'with import <nonexistentChannel> {}; bash'
75+
run -p 'with import <nonexistentChannel> {}; bash'
6376
check import-channel-ne \
6477
"s/nix/var/nix/profiles/per-user/root/channels/nonexistentChannel" '-'
6578

6679

67-
run 'import ./test-tmp/test.nix'
80+
run -p 'import ./test-tmp/test.nix'
6881
check import-relative-nix \
6982
"s$PWD/test-tmp/test.nix" "+"
7083

71-
run 'import ./test-tmp'
84+
run -p 'import ./test-tmp'
7285
check import-relative-nix-dir \
7386
"s$PWD/test-tmp" "d"
7487

75-
run 'import ./nonexistent.nix'
88+
run -p 'import ./nonexistent.nix'
7689
check import-relative-nix-ne \
7790
"s$PWD/nonexistent.nix" "-"
7891

7992

80-
run 'builtins.readFile ./test-tmp/test.nix'
93+
run -p 'builtins.readFile ./test-tmp/test.nix'
8194
check builtins.readFile \
8295
"f$PWD/test-tmp/test.nix" \
8396
"$(b3sum ./test-tmp/test.nix | head -c 32)"
8497

85-
run 'builtins.readFile "/nonexistent/readFile"'
98+
run -p 'builtins.readFile "/nonexistent/readFile"'
8699
check builtins.readFile-ne \
87100
"f/nonexistent/readFile" "-"
88101

89-
run 'builtins.readFile ./test-tmp'
102+
run -p 'builtins.readFile ./test-tmp'
90103
check builtins.readFile-dir \
91104
"f$PWD/test-tmp" "e"
92105

93-
run 'builtins.readFile ./test-tmp/empty'
106+
run -p 'builtins.readFile ./test-tmp/empty'
94107
check builtins.readFile-empty \
95108
"f$PWD/test-tmp/empty" \
96109
"$(b3sum ./test-tmp/empty | head -c 32)"
97110

98111

99-
run 'builtins.readDir ./test-tmp'
112+
run -p 'builtins.readDir ./test-tmp'
100113
check builtins.readDir \
101114
"d$PWD/test-tmp" "$(dir_b3sum ./test-tmp)"
102115

103-
run 'builtins.readDir "/nonexistent/readDir"'
116+
run -p 'builtins.readDir "/nonexistent/readDir"'
104117
check builtins.readDir-ne \
105118
"d/nonexistent/readDir" "-"
106119

107120

108-
run 'builtins.readDir ./test-tmp/many-dirs'
121+
run -p 'builtins.readDir ./test-tmp/many-dirs'
109122
check builtins.readDir-many-dirs \
110123
"d$PWD/test-tmp/many-dirs" "$(dir_b3sum ./test-tmp/many-dirs)"
111124

112-
run_without_p
125+
run
113126
check implicit:shell.nix \
114127
"s$PWD/shell.nix" "-"
115128
check implicit:default.nix \
116129
"s$PWD/default.nix" "-"
117130

131+
132+
run -p "fetchTarball file://$PWD/test-tmp/repo.tar?1"
133+
check --first fetchTarball:create \
134+
"t$PWD/test-tmp/tmpdir/nix-$NIX_PID-1" "+"
135+
check fetchTarball:delete \
136+
"t$PWD/test-tmp/tmpdir/nix-$NIX_PID-1" "-"
137+
138+
run -I "q=file://$PWD/test-tmp/repo.tar?2" -p "import <q>"
139+
check --first tarball-I:create \
140+
"t$PWD/test-tmp/tmpdir/nix-$NIX_PID-1" "+"
141+
check tarball-I:delete \
142+
"t$PWD/test-tmp/tmpdir/nix-$NIX_PID-1" "-"
143+
118144
exit $result

‎nix-trace/trace-nix.c

+82
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <dlfcn.h>
66
#include <errno.h>
77
#include <fcntl.h>
8+
#include <inttypes.h>
89
#include <limits.h>
910
#include <stdarg.h>
1011
#include <stdio.h>
@@ -17,6 +18,9 @@
1718

1819
static FILE *log_f = NULL;
1920
static const char *pwd = NULL;
21+
static char tmp_prefix[PATH_MAX]; // "$TMPDIR/nix-$$-"
22+
static size_t tmp_prefix_dirname_len = 0; // Length of "$TMPDIR"
23+
static size_t tmp_prefix_basename_len = 0; // Length of "nix-$$-"
2024

2125
#define FATAL() \
2226
do { \
@@ -88,6 +92,33 @@ static void __attribute__((constructor)) init() {
8892

8993
INIT_MUTEX(print_mutex);
9094
INIT_MUTEX(buf_mutex);
95+
96+
// References:
97+
// https://github.com/NixOS/nix/blob/2.15.1/src/libutil/filesystem.cc#L18
98+
// https://github.com/NixOS/nix/blob/2.15.1/src/libutil/util.hh#L337-L338
99+
const char *tmpdir = getenv("TMPDIR");
100+
if (tmpdir == NULL)
101+
tmpdir = "/tmp";
102+
char tmpdir_real[PATH_MAX];
103+
if (realpath(tmpdir, tmpdir_real) == NULL) {
104+
fprintf(stderr, "trace-nix: cannot resolve TMPDIR: %s\n", strerror(errno));
105+
tmp_prefix[0] = '\0';
106+
return;
107+
}
108+
const char *tmpdirend = tmpdir_real + strlen(tmpdir_real);
109+
while (tmpdirend > tmpdir_real && tmpdirend[-1] == '/')
110+
tmpdirend--;
111+
int len = snprintf(tmp_prefix, sizeof tmp_prefix,
112+
"%.*s/nix-%" PRIu64 "-",
113+
(int)(tmpdirend - tmpdir_real),
114+
tmpdir_real,
115+
(uint64_t)getpid());
116+
tmp_prefix_dirname_len = tmpdirend - tmpdir_real;
117+
tmp_prefix_basename_len = len - tmp_prefix_dirname_len - 1;
118+
if (len < 0 || len >= sizeof tmp_prefix) {
119+
fprintf(stderr, "trace-nix: TMPDIR too long\n");
120+
tmp_prefix[0] = '\0';
121+
}
91122
}
92123

93124
#ifdef __APPLE__
@@ -158,13 +189,64 @@ WRAPPER(DIR *, opendir, (const char *path)) {
158189
return dirp;
159190
}
160191

192+
WRAPPER(int, mkdir, (const char *path, mode_t mode)) {
193+
int result = REAL(mkdir)(path, mode);
194+
if (result == 0 && *tmp_prefix && memcmp(path, tmp_prefix,
195+
tmp_prefix_dirname_len + 1 + tmp_prefix_basename_len) == 0)
196+
print_log('t', path, "+");
197+
return result;
198+
}
199+
200+
WRAPPER(int, unlinkat, (int dirfd, const char *path, int flags)) {
201+
int result = REAL(unlinkat)(dirfd, path, flags);
202+
if (result != 0 || *tmp_prefix == '\0' || flags != AT_REMOVEDIR)
203+
return result;
204+
size_t path_len = strlen(path);
205+
if (path_len > 45) // 45 == len(f"nix-{2**64}-{2**64}")
206+
return result;
207+
// Check that the path starts with 'nix-$$-' and do not contain slash.
208+
if (memcmp(path, tmp_prefix + tmp_prefix_dirname_len + 1, tmp_prefix_basename_len) != 0 ||
209+
strchr(path + tmp_prefix_dirname_len + 1 + tmp_prefix_basename_len, '/'))
210+
return result;
211+
212+
char dir_path[PATH_MAX];
213+
#ifdef __linux__
214+
snprintf(dir_path, sizeof dir_path, "/proc/self/fd/%d", dirfd);
215+
#elif defined(__APPLE__)
216+
if (fcntl(dirfd, F_GETPATH, dir_path) == -1) {
217+
fprintf(stderr, "trace-nix: fcntl(%d, F_GETPATH): %s\n", dirfd, strerror(errno));
218+
return result;
219+
}
220+
#else
221+
#warning "Not implemented for this platform"
222+
return result;
223+
#endif
224+
char full_path[PATH_MAX];
225+
if (realpath(dir_path, full_path) == NULL) {
226+
fprintf(stderr, "trace-nix: realpath(%s): %s\n", dir_path, strerror(errno));
227+
return result;
228+
}
229+
230+
size_t dir_path_len = strlen(full_path);
231+
if (dir_path_len + 1 + path_len + 1 > sizeof full_path) {
232+
fprintf(stderr, "trace-nix: path too long: %s/%s\n", full_path, path);
233+
return result;
234+
}
235+
full_path[dir_path_len] = '/';
236+
memcpy(full_path + dir_path_len + 1, path, path_len + 1);
237+
238+
print_log('t', full_path, "-");
239+
return result;
240+
}
241+
161242
////////////////////////////////////////////////////////////////////////////////
162243

163244
static int enable(const char *path) {
164245
if (log_f == NULL || (*path != '/' && strcmp(path, "shell.nix")))
165246
return 0;
166247

167248
static const char *ignored_paths[] = {
249+
"/dev/urandom",
168250
"/etc/ssl/certs/ca-certificates.crt",
169251
"/nix/var/nix/daemon-socket/socket",
170252
"/nix",

‎src/main.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ fn run_nix_shell(inp: &NixShellInput) -> NixShellOutput {
279279
trace_file
280280
.read_to_end(&mut trace_data)
281281
.expect("Can't read trace file");
282-
let trace = Trace::load(trace_data);
282+
let trace = Trace::load_raw(trace_data);
283283
if trace.check_for_changes() {
284284
eprintln!("cached-nix-shell: some files are already updated, cache won't be reused");
285285
}
@@ -572,7 +572,7 @@ fn check_cache(hash: &str) -> Option<BTreeMap<OsString, OsString>> {
572572
return None;
573573
}
574574

575-
let trace = read(trace_fname).unwrap().pipe(Trace::load);
575+
let trace = read(trace_fname).unwrap().pipe(Trace::load_sorted);
576576
if trace.check_for_changes() {
577577
return None;
578578
}

0 commit comments

Comments
 (0)