karnel-termux 4.18.1 → 4.18.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/docs/CHANGELOG.md +15 -0
- package/karnel/RELEASE_COMMIT +1 -1
- package/karnel/cli/karnel.sh +17 -1
- package/karnel/tools/ai/all.sh +7 -0
- package/karnel/tools/ai/freebuff/install.sh +21 -0
- package/karnel/utils/compat.sh +109 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
10
|
<a href="https://github.com/karnel-termuxOFC/karnel-termux">
|
|
11
|
-
<img src="https://img.shields.io/badge/version-4.18.
|
|
11
|
+
<img src="https://img.shields.io/badge/version-4.18.3-0078D4?style=for-the-badge" alt="Version">
|
|
12
12
|
</a>
|
|
13
13
|
<a href="https://www.npmjs.com/package/karnel-termux">
|
|
14
14
|
<img src="https://img.shields.io/npm/v/karnel-termux?style=for-the-badge&logo=npm&color=cb3837" alt="npm">
|
|
@@ -78,7 +78,7 @@ Why it's great:
|
|
|
78
78
|
### Via checksummed GitHub release (recommended)
|
|
79
79
|
|
|
80
80
|
```bash
|
|
81
|
-
version=4.18.
|
|
81
|
+
version=4.18.3
|
|
82
82
|
tmpdir=$(mktemp -d) && trap 'rm -rf "$tmpdir"' EXIT
|
|
83
83
|
base="https://github.com/karnel-termuxOFC/karnel-termux/releases/download/v$version"
|
|
84
84
|
curl -fsSL "$base/karnel-termux-install.sh" -o "$tmpdir/karnel-termux-install.sh"
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,21 @@ layout: base
|
|
|
6
6
|
|
|
7
7
|
# Documentation Changelog
|
|
8
8
|
|
|
9
|
+
## 4.18.3
|
|
10
|
+
|
|
11
|
+
- **Every command that installs something now adapts what it put on PATH.** The dispatcher brackets `install`, `update`, `upgrade`, `reinstall`, `restore`, `plugin`, `supabase`, `deploy`, `voice`, `robin`, `pg`, `open` and `init` with a snapshot of the writable parts of PATH (`path|size|mtime` per entry) and afterwards adapts exactly the entries that appeared or changed. That closes the gap the registries could never close: an installer that never calls the compatibility layer, one that names a command its registry does not mention, and a file restored from an archive with its original timestamp all end up wrapped anyway. Sweeping the prefix instead was measured on this device at 51 seconds of `readelf` over 800 entries (and 875 seconds with `readelf -l`); diffing a snapshot takes 60 ms and touches nothing the command did not write. The exit code of the command is taken before adaptation and returned unchanged.
|
|
12
|
+
- **The AI reinstall fallback adapts what it installed.** `_run_ai_tool_action` called `$install_fn` and returned without passing through its adapt call, so those binaries stayed unwrapped.
|
|
13
|
+
- **An install can no longer report success for a command that does not start.** A device audit of all 169 catalogued tools found 21 installed and 147 absent — and exactly one of them dead: `$PREFIX/bin/freebuff` had been left as npm's shim, which exits 1 on Termux with "Unsupported platform: android arm64", while `karnel install ai --freebuff` still reported "already installed". Both paths of `install_freebuff` now prove the entry runs before returning, the same way `_fix_freebuff_runtime` already proves the runtime does. The audit is green afterwards: freebuff `0.2.22`, bun `1.4.2`, supabase `2.20.8`, zero broken.
|
|
14
|
+
- **The compatibility suite is part of the test run.** `tests/android-compat.sh` had never been listed in `tests/run.sh`, so its cases only ever ran by hand; it now runs in `npm test` and in CI, and every other file under `tests/` was checked against the list while adding it.
|
|
15
|
+
- **Tests.** Five new cases in `tests/android-compat.sh` (a binary created during a command, a binary rewritten in place, only the entries that changed, an exit code that cannot change, and the dispatcher wiring itself) plus two in `tests/ai-installer-hardening.sh` (a dead freebuff entry fails, a live one is accepted), taking the suites to 28 and 11 cases. Every branch was proven to fail first: dropping the adapt call fails 1 case, dropping the snapshot fails 1, making `compat_adapt_since` a no-op fails 2, removing the AI fallback's adapt fails 1, removing the already-installed verification fails 1, and turning that verification into a no-op fails both new freebuff cases.
|
|
16
|
+
|
|
17
|
+
## 4.18.2
|
|
18
|
+
|
|
19
|
+
- **Wrappers no longer hand a preload to glibc.** `login(1)` exports `LD_PRELOAD=$PREFIX/lib/libtermux-exec-ld-preload.so`, a bionic object; glibc's `ld.so` cannot load it, so every wrapped tool died before `main()` with `.../glibc/lib/libc.so: invalid ELF header`. Tier 3's own preload was fatal in the other direction: exporting the glibc `libtermux-exec.so` shim left every bionic child of the tool failing with `library "libc.so.6" not found`. Every wrapper now starts with `unset LD_PRELOAD`, and tier 3 exports `PATH` only.
|
|
20
|
+
- **Wrappers written by older Karnels are refreshed.** `compat_wrapper_is_current()` checks the invariants the current code emits; when one is missing the wrapper is rewritten in place before `compat_escalate` probes it, so a stale launcher can no longer keep a tool pinned to a tier it no longer reaches.
|
|
21
|
+
- **An orphaned parked copy no longer blocks adaptation.** Freebuff's own entry re-extracts its runtime in place, dropping a raw ELF over the wrapper and leaving `.karnel-real` behind; `compat_wrap` refused to touch that file, so the tool could never be adapted again — which is how `karnel install ai --freebuff` ended in "does not start at any tier" and then "Freebuff runtime still does not run after repair". Identical bytes are dropped (a 130 MB runtime is not copied to prove a point) and a different copy is parked as `.karnel-real.stale`.
|
|
22
|
+
- **Tests.** Four new cases pin the behaviour — no wrapper hands a preload to the loader on tier 2 or tier 3, a launcher from an older karnel is refreshed in place, an orphaned parked copy does not block adaptation — in a suite of 23 cases running in 8 seconds. Every branch was proven to fail first: removing the `unset` fails 2 cases, restoring the tier 3 preload fails 5, dropping the refresh fails 1, restoring the refusal fails 1, and always discarding the orphan fails 1.
|
|
23
|
+
|
|
9
24
|
## 4.18.1
|
|
10
25
|
|
|
11
26
|
- **The ladder is reachable from every installer, not just Freebuff:** `compat_adapt_installed()` is the single entry point behind `_tool_compat_adapt` (utils/tools.sh), `_ai_tool_compat_adapt` (tools/ai/all.sh) and the post-install loop in `cli/commands/install.sh`, so it already covered all 16 categories — but it only ever called `compat_wrap()`, which stops at the loader tier. Tiers 3 (glibc userland) and 4 (proot FHS root) were reachable solely from Freebuff's `compat_adapt()` call, which is why no other tool could ever climb above tier 2. It now escalates after wrapping, and re-probes wrappers already on disk so a tool that stops starting at its current tier climbs on the next install or update instead of staying broken behind a launcher nobody re-examines.
|
package/karnel/RELEASE_COMMIT
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
4f1840c76ffba58dde286b506142fa1f9e5ee1d8
|
package/karnel/cli/karnel.sh
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import "@/utils/log"
|
|
4
4
|
import "@/utils/colors"
|
|
5
|
+
import "@/utils/compat"
|
|
5
6
|
|
|
6
7
|
: "${KARNEL_VERSION:=unknown}"
|
|
7
8
|
|
|
@@ -67,8 +68,23 @@ _karnel_dispatch() {
|
|
|
67
68
|
|
|
68
69
|
if [[ -f "$command_file" ]]; then
|
|
69
70
|
import "@/cli/commands/$cmd"
|
|
71
|
+
# Every command that can put a binary on PATH is bracketed by a snapshot
|
|
72
|
+
# of PATH, and whatever changed inside it is adapted afterwards. That is
|
|
73
|
+
# what makes the layer cover *all* tools: an installer that never calls
|
|
74
|
+
# compat itself, one that names a command its registry does not mention,
|
|
75
|
+
# and one that restores a file from an archive all end up here anyway.
|
|
76
|
+
local compat_snap=""
|
|
77
|
+
case "$cmd" in
|
|
78
|
+
install | update | upgrade | reinstall | restore | plugin | supabase | deploy | voice | robin | pg | open | init)
|
|
79
|
+
compat_snap="$(compat_path_snapshot 2>/dev/null)" || compat_snap=""
|
|
80
|
+
;;
|
|
81
|
+
esac
|
|
70
82
|
"${cmd}_main" "$@"
|
|
71
|
-
|
|
83
|
+
local cmd_rc=$?
|
|
84
|
+
if [[ -n "$compat_snap" ]]; then
|
|
85
|
+
compat_adapt_since "$compat_snap" || true
|
|
86
|
+
fi
|
|
87
|
+
return "$cmd_rc"
|
|
72
88
|
fi
|
|
73
89
|
|
|
74
90
|
import "@/tools/plugins/install"
|
package/karnel/tools/ai/all.sh
CHANGED
|
@@ -230,6 +230,13 @@ _run_ai_tool_action() {
|
|
|
230
230
|
return "$uninstall_rc"
|
|
231
231
|
fi
|
|
232
232
|
"$install_fn"
|
|
233
|
+
local install_rc=$?
|
|
234
|
+
# The fallback path never passes through _run_ai_tool_action's adapt call,
|
|
235
|
+
# so the binaries this install just wrote would stay unwrapped.
|
|
236
|
+
if ((install_rc == 0)); then
|
|
237
|
+
_ai_tool_compat_adapt "$id"
|
|
238
|
+
fi
|
|
239
|
+
return "$install_rc"
|
|
233
240
|
}
|
|
234
241
|
|
|
235
242
|
_all_ai_tools_action() {
|
|
@@ -23,6 +23,7 @@ install_freebuff() {
|
|
|
23
23
|
log_info "Freebuff is already installed"
|
|
24
24
|
_fix_freebuff_runtime || true
|
|
25
25
|
_install_freebuff_entry || true
|
|
26
|
+
_verify_freebuff_entry || return 1
|
|
26
27
|
return 2
|
|
27
28
|
fi
|
|
28
29
|
|
|
@@ -42,6 +43,7 @@ install_freebuff() {
|
|
|
42
43
|
_fix_freebuff_shebang || return 1
|
|
43
44
|
_fix_freebuff_runtime || return 1
|
|
44
45
|
_install_freebuff_entry || return 1
|
|
46
|
+
_verify_freebuff_entry || return 1
|
|
45
47
|
log_success "Freebuff installed"
|
|
46
48
|
}
|
|
47
49
|
|
|
@@ -96,6 +98,25 @@ _freebuff_binary_runs() {
|
|
|
96
98
|
((rc == 0))
|
|
97
99
|
}
|
|
98
100
|
|
|
101
|
+
# npm recreates its shim at $PREFIX/bin/freebuff on every global operation, and
|
|
102
|
+
# that shim cannot start on Android - so an install can look successful while
|
|
103
|
+
# the command itself is dead. Every path therefore proves the entry runs before
|
|
104
|
+
# reporting success, the same way _fix_freebuff_runtime proves the runtime does.
|
|
105
|
+
_verify_freebuff_entry() {
|
|
106
|
+
local entry rc=0
|
|
107
|
+
entry="$(command -v freebuff 2>/dev/null)"
|
|
108
|
+
if [[ -z "$entry" || ! -e "$entry" ]]; then
|
|
109
|
+
log_error "Freebuff is not on PATH after install"
|
|
110
|
+
return 1
|
|
111
|
+
fi
|
|
112
|
+
{ timeout 30 "$entry" --version >/dev/null 2>&1; rc=$?; } 2>/dev/null
|
|
113
|
+
if ((rc != 0)); then
|
|
114
|
+
log_error "Freebuff does not run after install (exit $rc)"
|
|
115
|
+
return 1
|
|
116
|
+
fi
|
|
117
|
+
return 0
|
|
118
|
+
}
|
|
119
|
+
|
|
99
120
|
_freebuff_target_key() {
|
|
100
121
|
case "$(uname -m)" in
|
|
101
122
|
aarch64 | arm64) printf 'linux-arm64\n' ;;
|
package/karnel/utils/compat.sh
CHANGED
|
@@ -206,6 +206,19 @@ compat_wrapper_tier() {
|
|
|
206
206
|
esac
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
+
# A wrapper written by an older karnel is still a wrapper, but it may hand
|
|
210
|
+
# LD_PRELOAD straight to the glibc loader and die in a login shell. Returns 0
|
|
211
|
+
# only when the launcher carries every invariant the current code emits.
|
|
212
|
+
compat_wrapper_is_current() {
|
|
213
|
+
local file="$1"
|
|
214
|
+
_compat_is_wrapper "$file" || return 1
|
|
215
|
+
grep -qF 'unset LD_PRELOAD' -- "$file" 2>/dev/null || return 1
|
|
216
|
+
if grep -qF 'export LD_PRELOAD' -- "$file" 2>/dev/null; then
|
|
217
|
+
return 1
|
|
218
|
+
fi
|
|
219
|
+
return 0
|
|
220
|
+
}
|
|
221
|
+
|
|
209
222
|
# Builds (once) a synthetic filesystem root out of the glibc sysroot that is
|
|
210
223
|
# already on the device. proot then maps /lib, /usr and /etc onto it, which is
|
|
211
224
|
# everything a binary that hardcodes those paths needs -- for a few kilobytes
|
|
@@ -273,24 +286,23 @@ _compat_write_wrapper() {
|
|
|
273
286
|
printf '%s\n' "#!$bash_bin"
|
|
274
287
|
printf '%s\n' "$COMPAT_WRAPPER_MARKER"
|
|
275
288
|
printf '%s\n' "$COMPAT_WRAPPER_TIER_MARKER $tier"
|
|
289
|
+
# login(1) exports LD_PRELOAD=$PREFIX/lib/libtermux-exec-ld-preload.so,
|
|
290
|
+
# a bionic object. glibc's ld.so cannot load it and dies with
|
|
291
|
+
# "libc.so: invalid ELF header" on every start. The glibc termux-exec
|
|
292
|
+
# shim is equally unusable: every bionic child this tool spawns then
|
|
293
|
+
# fails with "library libc.so.6 not found". So no preload survives.
|
|
294
|
+
printf '%s\n' "unset LD_PRELOAD"
|
|
276
295
|
case "$tier" in
|
|
277
296
|
"$COMPAT_TIER_USERLAND")
|
|
278
|
-
# The $root/$PATH
|
|
297
|
+
# The $root/$PATH below must reach the wrapper verbatim.
|
|
279
298
|
# shellcheck disable=SC2016
|
|
280
299
|
printf '%s\n' "# Tier 3: the glibc loader plus the glibc userland, so the child"
|
|
281
|
-
printf '%s\n' "# processes this tool spawns find the same coreutils
|
|
300
|
+
printf '%s\n' "# processes this tool spawns find the same coreutils."
|
|
282
301
|
# Everything below is emitted literally and expanded by the wrapper at
|
|
283
302
|
# runtime, where "root" is the assignment written on the previous line.
|
|
284
303
|
printf 'root=%q\n' "$(compat_glibc_root)"
|
|
285
304
|
# shellcheck disable=SC2016 # expands in the wrapper
|
|
286
305
|
printf 'export PATH="$root/bin:$PATH"\n'
|
|
287
|
-
# The glibc termux-exec shim is optional; a preload pointing nowhere
|
|
288
|
-
# makes ld.so complain on every start of the tool.
|
|
289
|
-
# shellcheck disable=SC2016 # expands in the wrapper
|
|
290
|
-
printf 'if [ -f "$root/lib/libtermux-exec.so" ]; then\n'
|
|
291
|
-
# shellcheck disable=SC2016 # expands in the wrapper
|
|
292
|
-
printf ' export LD_PRELOAD="$root/lib/libtermux-exec.so${LD_PRELOAD:+:$LD_PRELOAD}"\n'
|
|
293
|
-
printf 'fi\n'
|
|
294
306
|
printf 'exec %q --library-path %q %q "$@"\n' "$loader" "$libpath" "$real"
|
|
295
307
|
;;
|
|
296
308
|
"$COMPAT_TIER_PROOT")
|
|
@@ -328,22 +340,43 @@ compat_wrap() {
|
|
|
328
340
|
[[ -f "$real" ]] || return 1
|
|
329
341
|
if _compat_is_wrapper "$real"; then
|
|
330
342
|
current="$(compat_wrapper_tier "$real")"
|
|
331
|
-
[[ "$current" == "$tier" ]] &&
|
|
332
|
-
|
|
333
|
-
|
|
343
|
+
if [[ "$current" == "$tier" ]] && compat_wrapper_is_current "$real"; then
|
|
344
|
+
return 0
|
|
345
|
+
fi
|
|
346
|
+
# Re-tier an existing wrapper, or refresh one written by an older karnel:
|
|
347
|
+
# the original is already parked next to it, so only the launcher changes.
|
|
334
348
|
stored="$real.karnel-real"
|
|
335
349
|
[[ -e "$stored" ]] || return 1
|
|
336
350
|
loader="$(compat_glibc_loader)" || loader="/lib/ld-linux-aarch64.so.1"
|
|
337
351
|
_compat_write_wrapper "$real" "$stored" "$loader" "$tier" || return 1
|
|
338
|
-
|
|
352
|
+
if [[ "$current" == "$tier" ]]; then
|
|
353
|
+
log_info "compat: $real wrapper refreshed"
|
|
354
|
+
else
|
|
355
|
+
log_info "compat: $real retiered to level $tier"
|
|
356
|
+
fi
|
|
339
357
|
return 0
|
|
340
358
|
fi
|
|
341
359
|
compat_is_glibc_elf "$real" || return 1
|
|
342
360
|
loader="$(compat_glibc_loader)" || return 1
|
|
343
361
|
stored="$real.karnel-real"
|
|
344
362
|
if [[ -e "$stored" ]]; then
|
|
345
|
-
|
|
346
|
-
|
|
363
|
+
# The file on disk is a real ELF, not our wrapper, while a parked copy is
|
|
364
|
+
# already sitting beside it: something outside karnel replaced the
|
|
365
|
+
# launcher (freebuff's node entry re-extracts its runtime in place) and
|
|
366
|
+
# orphaned the copy. The file on disk is authoritative now, so the parked
|
|
367
|
+
# copy is stale. A refusal here would leave the tool permanently
|
|
368
|
+
# unrunnable, so the copy is dropped when it is the same bytes and kept
|
|
369
|
+
# as .karnel-real.stale otherwise - a 130 MB runtime must not be copied
|
|
370
|
+
# just to prove a point.
|
|
371
|
+
if cmp -s -- "$stored" "$real" 2>/dev/null; then
|
|
372
|
+
rm -f -- "$stored" || return 1
|
|
373
|
+
log_warn "compat: dropped a stale parked copy for $real"
|
|
374
|
+
elif ! mv -f -- "$stored" "$stored.stale" 2>/dev/null; then
|
|
375
|
+
log_error "compat: could not move the stale parked copy for $real"
|
|
376
|
+
return 1
|
|
377
|
+
else
|
|
378
|
+
log_warn "compat: replaced a stale parked copy for $real"
|
|
379
|
+
fi
|
|
347
380
|
fi
|
|
348
381
|
if ! mv -- "$real" "$stored"; then
|
|
349
382
|
log_error "compat: could not move $real aside"
|
|
@@ -392,6 +425,11 @@ compat_escalate() {
|
|
|
392
425
|
real="$(readlink -f -- "$target" 2>/dev/null || printf '%s' "$target")"
|
|
393
426
|
_compat_is_wrapper "$real" || return 0
|
|
394
427
|
|
|
428
|
+
# Refresh a launcher written by an older karnel before probing it: the probe
|
|
429
|
+
# has to exercise the wrapper we are about to ship, not the stale one.
|
|
430
|
+
compat_wrapper_is_current "$real" ||
|
|
431
|
+
compat_wrap "$real" "$(compat_wrapper_tier "$real")" || true
|
|
432
|
+
|
|
395
433
|
compat_probe "$real" && return 0
|
|
396
434
|
|
|
397
435
|
if compat_wrap "$real" "$COMPAT_TIER_USERLAND" && compat_probe "$real"; then
|
|
@@ -496,13 +534,11 @@ compat_fix_shebangs() {
|
|
|
496
534
|
printf '%s\n' "$fixed"
|
|
497
535
|
}
|
|
498
536
|
|
|
499
|
-
# Adapts
|
|
500
|
-
#
|
|
501
|
-
#
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
local tool="$1" path real
|
|
505
|
-
path="$(command -v -- "$tool" 2>/dev/null)" || return 0
|
|
537
|
+
# Adapts one resolved path. Wraps a glibc ELF so the kernel can start it;
|
|
538
|
+
# leaves native binaries, scripts and text files alone. Always returns 0:
|
|
539
|
+
# adaptation is an optimisation, never a reason to fail an install.
|
|
540
|
+
_compat_adapt_path() {
|
|
541
|
+
local path="$1" real
|
|
506
542
|
[[ -n "$path" ]] || return 0
|
|
507
543
|
real="$(readlink -f -- "$path" 2>/dev/null || printf '%s' "$path")"
|
|
508
544
|
[[ -f "$real" && -x "$real" ]] || return 0
|
|
@@ -522,6 +558,57 @@ compat_adapt_installed() {
|
|
|
522
558
|
return 0
|
|
523
559
|
}
|
|
524
560
|
|
|
561
|
+
# Adapts whatever a tool has just put on PATH.
|
|
562
|
+
compat_adapt_installed() {
|
|
563
|
+
local tool="$1" path
|
|
564
|
+
path="$(command -v -- "$tool" 2>/dev/null)" || return 0
|
|
565
|
+
[[ -n "$path" ]] || return 0
|
|
566
|
+
_compat_adapt_path "$path"
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
# Records every direct entry of the writable parts of PATH as
|
|
570
|
+
# "path|size|mtime", printed to a temporary file. Taken before a command runs
|
|
571
|
+
# so the layer can afterwards adapt exactly what that command touched.
|
|
572
|
+
#
|
|
573
|
+
# Sweeping the whole prefix instead would need readelf on every ELF, which
|
|
574
|
+
# measures 60+ seconds over a Termux bin directory of 800 entries; diffing a
|
|
575
|
+
# snapshot adapts only the handful of files that actually changed. It also
|
|
576
|
+
# means no installer has to declare its binary names: a tool that installs a
|
|
577
|
+
# command its registry does not mention is still covered, and a file restored
|
|
578
|
+
# from an archive with its original mtime is caught by being a new path.
|
|
579
|
+
compat_path_snapshot() {
|
|
580
|
+
local dir tmp
|
|
581
|
+
tmp="$(mktemp "${TMPDIR:-${KARNEL_CACHE:-/tmp}}/karnel-compat-snap.XXXXXX" 2>/dev/null)" || return 1
|
|
582
|
+
local IFS=':'
|
|
583
|
+
for dir in $PATH; do
|
|
584
|
+
[[ -n "$dir" && -d "$dir" && -w "$dir" ]] || continue
|
|
585
|
+
find "$dir" -maxdepth 1 \( -type f -o -type l \) -printf '%p|%s|%T@\n' 2>/dev/null || true
|
|
586
|
+
done | sort >"$tmp" || true
|
|
587
|
+
printf '%s\n' "$tmp"
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
# Adapts every PATH entry that appeared or changed since a snapshot, then
|
|
591
|
+
# removes both files. Returns 0 unconditionally.
|
|
592
|
+
compat_adapt_since() {
|
|
593
|
+
local snap="$1" tmp dir path
|
|
594
|
+
[[ -f "$snap" ]] || return 0
|
|
595
|
+
tmp="$(mktemp "${TMPDIR:-${KARNEL_CACHE:-/tmp}}/karnel-compat-now.XXXXXX" 2>/dev/null)" || {
|
|
596
|
+
rm -f -- "$snap"
|
|
597
|
+
return 0
|
|
598
|
+
}
|
|
599
|
+
local IFS=':'
|
|
600
|
+
for dir in $PATH; do
|
|
601
|
+
[[ -n "$dir" && -d "$dir" && -w "$dir" ]] || continue
|
|
602
|
+
find "$dir" -maxdepth 1 \( -type f -o -type l \) -printf '%p|%s|%T@\n' 2>/dev/null || true
|
|
603
|
+
done | sort >"$tmp" || true
|
|
604
|
+
while IFS= read -r path; do
|
|
605
|
+
[[ -n "$path" ]] || continue
|
|
606
|
+
_compat_adapt_path "$path" || true
|
|
607
|
+
done < <(comm -13 -- "$snap" "$tmp" 2>/dev/null | cut -d'|' -f1)
|
|
608
|
+
rm -f -- "$snap" "$tmp"
|
|
609
|
+
return 0
|
|
610
|
+
}
|
|
611
|
+
|
|
525
612
|
# Human-readable one-liner for doctor output.
|
|
526
613
|
compat_describe() {
|
|
527
614
|
local file="$1" kind interp real
|
package/package.json
CHANGED