diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a52606..70b3c8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,31 @@ What changed in each version of Cortex Terminal, newest first. The release workf of a GitHub release from the section of its version here. Older versions are described on the [GitHub releases](https://github.com/ssfuisu/cortex/releases) page. +## 1.27.6 (2026-11-12) + +### Fixed + +- **Selecting text on the desktop replaced what had been copied on the phone.** The X server sent + Android not only what a program copied (the CLIPBOARD) but also every piece of text that was only + selected (the PRIMARY), so a drag of the mouse over a word in a terminal or an editor wiped the + phone's clipboard. Only what is copied goes to Android now; text copied on the phone still pastes + on the desktop with Ctrl+V and with the middle button. +- **`readlink` printed long link targets cut off.** A target that Cortex rewrites is longer as the + phone stores it (it starts with the app's own directory), and a program that read it into a small + buffer got it cut off before it was rewritten, shorter than the buffer, so it took that for the + whole target: coreutils' `readlink`, which starts with 64 bytes, printed + `/usr/share/postgresql/16/man/man7/ALTER_TEXT_SEA`. Into a buffer smaller than that the phone's own + path could even come out. The target is now read whole and rewritten before it is cut to the + buffer, as Linux does. + +### Changed + +- **lstat() of a link is faster.** The size of a link that Cortex rewrites (those of + /etc/alternatives, long absolute ones) was found by reading the link again at every lstat() and + fstatat(): ls -l, cp -a, tar and find paid that for each of them. The size is now kept while the + link stays the same. Measured on an x86 machine, lstat() of /etc/alternatives/awk went from about + 1,190 ns to 735 ns (530 ns without Cortex). The benchmark has a test for it: `lstat_alt_link`. + ## 1.27.5.1 (2026-11-11) ### Fixed diff --git a/README.md b/README.md index 1b9acc4..670b1dd 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ Measured on one 8-core Android phone (Linux 4.14, one big core, median of 9 inte | `write()`, `dup()` + `close()`, `getcwd()`, `sigprocmask()` | up to +21 ns | up to +21 ns | | Computation without system calls | 0 | 0 | -`lstat()` of a symbolic link stays expensive because the library reads the link to report its size the way Ubuntu does. +`lstat()` of a symbolic link stays the most expensive of these because the library reads the link to report its size the way Ubuntu does. Since 1.27.6 it reads each link once and keeps the size while the link stays the same (on an x86 machine, `lstat()` of `/etc/alternatives/awk` went from about 1,190 ns to 735 ns; 530 ns without the library). **Starting a program** (what a shell does for every command): the library made 19 system calls before the program started and now makes 7, no longer changes the permissions of `ld.so` each time, and lists the library directories that hold nearly every library first, so a program no longer pays two failed lookups per library (`apt-cache` made 34 of them, `bash` 4, now none). Starting a program through the library takes 769 µs instead of 903 µs; starting `ld.so` by hand without the library takes 491 µs. diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e62e6c0..96eff64 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -16,8 +16,8 @@ android { // from the app's writable data directory (filesDir). Android 10+ (targetSdk >= 29) // enforces SELinux W^X restrictions preventing execution from writable app data storage. targetSdk = 28 - versionCode = 12705 - versionName = "1.27.5.1" + versionCode = 12706 + versionName = "1.27.6" // UbuntuOnDeviceTest (app/src/androidTest), on Firebase Test Lab testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/assets/scripts/cortex-desktop.sh b/app/src/main/assets/scripts/cortex-desktop.sh index 9a5ca25..7d14d93 100644 --- a/app/src/main/assets/scripts/cortex-desktop.sh +++ b/app/src/main/assets/scripts/cortex-desktop.sh @@ -369,8 +369,10 @@ cmd_start() { # -rfbport -1: no TCP port, the Unix socket only (mode 0600: this app's user alone). setsid: a # session of its own, so that the end of the terminal that started it (the app runs this command # on a pseudo-terminal of its own) sends it no SIGHUP, which makes an X server reset. + # -SendPrimary=0: only what is copied (the CLIPBOARD) goes to Android's clipboard. Text that is + # only selected (the PRIMARY) replaced what had been copied on the phone, at every selection. setsid Xvnc ":$display" -geometry "$size" -depth 24 -rfbunixpath "$SOCKET" -rfbunixmode 0600 -rfbport -1 \ - -SecurityTypes None -nolisten tcp -AlwaysShared -AcceptSetDesktopSize -dpi 120 \ + -SecurityTypes None -nolisten tcp -AlwaysShared -AcceptSetDesktopSize -dpi 120 -SendPrimary=0 \ >"$LOG" 2>&1 9>&- & pid=$! echo "$pid" >"$STATE/xvnc.pid" diff --git a/app/src/main/cpp/hook/fileops.c b/app/src/main/cpp/hook/fileops.c index c416fa1..97e07b7 100644 --- a/app/src/main/cpp/hook/fileops.c +++ b/app/src/main/cpp/hook/fileops.c @@ -298,8 +298,25 @@ int renameat2(int olddirfd, const char *oldpath, int newdirfd, const char *newpa return ret; } +// A target the hook rewrites is longer as the kernel stores it (it starts with the rootfs path). Read +// straight into a small buffer, it was cut off before it was rewritten, and the shorter result told +// the caller it had all of it: coreutils' readlink, which starts with 64 bytes, printed +// "/usr/share/postgresql/16/man/man7/ALTER_TEXT_SEA". A buffer smaller than any path is filled from +// one that is not, and cut off afterwards, as the kernel does it (the whole size: the caller asks again). +static ssize_t readlink_into(char *buf, size_t bufsiz, const char *full, ssize_t len) { + if (len < 0) return len; + size_t n = (size_t)len < bufsiz ? (size_t)len : bufsiz; + memcpy(buf, full, n); + return (ssize_t)n; +} + // Hook readlink ssize_t readlink(const char *pathname, char *buf, size_t bufsiz) { + // as the kernel answers it (the target is read into a buffer of the hook's own below) + if (bufsiz == 0) { + errno = EINVAL; + return -1; + } if (is_proc_self_exe(pathname)) { ssize_t exe_len = handle_proc_self_exe(buf, bufsiz); if (exe_len >= 0) return exe_len; @@ -308,17 +325,26 @@ ssize_t readlink(const char *pathname, char *buf, size_t bufsiz) { if (!orig_readlink) orig_readlink = (ssize_t (*)(const char *, char *, size_t))dlsym(RTLD_NEXT, "readlink"); char pbuf[PATH_MAX]; const char *target = rewrite_path(pathname, pbuf, sizeof(pbuf)); - ssize_t ret = orig_readlink ? orig_readlink(target, buf, bufsiz) : -1; + char full[PATH_MAX]; + char *into = bufsiz < sizeof(full) ? full : buf; + size_t into_size = bufsiz < sizeof(full) ? sizeof(full) : bufsiz; + ssize_t ret = orig_readlink ? orig_readlink(target, into, into_size) : -1; if (ret < 0 && errno == ENOENT) { char alt[PATH_MAX]; const char *retry = usrmerge_retry_path(target, alt, sizeof(alt)); - if (retry) ret = orig_readlink(retry, buf, bufsiz); + if (retry) ret = orig_readlink(retry, into, into_size); } - return postprocess_readlink(pathname, buf, ret, bufsiz); + ret = postprocess_readlink(pathname, into, ret, into_size); + return into == buf ? ret : readlink_into(buf, bufsiz, full, ret); } // Hook readlinkat ssize_t readlinkat(int dirfd, const char *pathname, char *buf, size_t bufsiz) { + // as the kernel answers it (the target is read into a buffer of the hook's own below) + if (bufsiz == 0) { + errno = EINVAL; + return -1; + } if (is_proc_self_exe(pathname)) { ssize_t exe_len = handle_proc_self_exe(buf, bufsiz); if (exe_len >= 0) return exe_len; @@ -327,13 +353,17 @@ ssize_t readlinkat(int dirfd, const char *pathname, char *buf, size_t bufsiz) { if (!orig_readlinkat) orig_readlinkat = (ssize_t (*)(int, const char *, char *, size_t))dlsym(RTLD_NEXT, "readlinkat"); char pbuf[PATH_MAX]; const char *target = (pathname && pathname[0] == '/') ? rewrite_path(pathname, pbuf, sizeof(pbuf)) : pathname; - ssize_t ret = orig_readlinkat ? orig_readlinkat(dirfd, target, buf, bufsiz) : -1; + char full[PATH_MAX]; + char *into = bufsiz < sizeof(full) ? full : buf; + size_t into_size = bufsiz < sizeof(full) ? sizeof(full) : bufsiz; + ssize_t ret = orig_readlinkat ? orig_readlinkat(dirfd, target, into, into_size) : -1; if (ret < 0 && errno == ENOENT && pathname && pathname[0] == '/') { char alt[PATH_MAX]; const char *retry = usrmerge_retry_path(target, alt, sizeof(alt)); - if (retry) ret = orig_readlinkat(dirfd, retry, buf, bufsiz); + if (retry) ret = orig_readlinkat(dirfd, retry, into, into_size); } - return postprocess_readlink(pathname, buf, ret, bufsiz); + ret = postprocess_readlink(pathname, into, ret, into_size); + return into == buf ? ret : readlink_into(buf, bufsiz, full, ret); } // Fortified readlink variants used by dpkg / update-alternatives diff --git a/app/src/main/cpp/hook/files.c b/app/src/main/cpp/hook/files.c index 1b65838..e47f4b4 100644 --- a/app/src/main/cpp/hook/files.c +++ b/app/src/main/cpp/hook/files.c @@ -493,6 +493,108 @@ int stat(const char *pathname, struct stat *statbuf) { return ret; } +// ---- the size lstat() reports for a link --------------------------------------------------- +// The size of a link is the length of its target, and the hook reports it as readlink() returns +// the target: rewritten. Finding that out read the link a second time, on every lstat() of a link +// that can be rewritten (those of /etc/alternatives, long absolute ones): ls -l, cp -a, tar and +// find did that for each of them. A link's target cannot change while the link exists (a new +// target is a new link), so the size is kept, by the link's inode, the time it was made, its own +// size and the path it was asked for by. No lock: each place has a sequence number that is odd +// while it is written, and a reader that sees it change takes nothing (a fork can happen at any +// moment, and a child must never wait for a lock that another thread held). +#define LINK_SIZE_SLOTS 128 + +static struct link_size_slot { + uint32_t seq; + uint64_t dev, ino, path_hash; + int64_t ctime_sec; + int64_t ctime_nsec; + long long raw, size; +} g_link_sizes[LINK_SIZE_SLOTS]; + +static uint64_t link_path_hash(int dirfd, const char *path) { + uint64_t h = 1469598103934665603ULL ^ (uint64_t)(int64_t)dirfd; // FNV-1a + for (const unsigned char *p = (const unsigned char *)path; p && *p; p++) h = (h ^ *p) * 1099511628211ULL; + return h; +} + +static struct link_size_slot *link_size_slot_for(const struct stat *st, uint64_t path_hash) { + uint64_t mix = (uint64_t)st->st_ino * 0x9E3779B97F4A7C15ULL ^ path_hash; + return &g_link_sizes[(mix >> 32) % LINK_SIZE_SLOTS]; +} + +#define LS_LOAD(field) __atomic_load_n(&slot->field, __ATOMIC_RELAXED) +#define LS_STORE(field, value) __atomic_store_n(&slot->field, (value), __ATOMIC_RELAXED) + +// The size kept for this link, or -1 +static long long link_size_cached(const struct stat *st, uint64_t path_hash) { + struct link_size_slot *slot = link_size_slot_for(st, path_hash); + uint32_t before = __atomic_load_n(&slot->seq, __ATOMIC_ACQUIRE); + if (before == 0 || (before & 1)) return -1; + int match = LS_LOAD(dev) == (uint64_t)st->st_dev && LS_LOAD(ino) == (uint64_t)st->st_ino && + LS_LOAD(path_hash) == path_hash && LS_LOAD(ctime_sec) == (int64_t)st->st_ctim.tv_sec && + LS_LOAD(ctime_nsec) == (int64_t)st->st_ctim.tv_nsec && LS_LOAD(raw) == (long long)st->st_size; + long long size = LS_LOAD(size); + __atomic_thread_fence(__ATOMIC_ACQUIRE); + if (__atomic_load_n(&slot->seq, __ATOMIC_RELAXED) != before) return -1; + return match ? size : -1; +} + +static void link_size_keep(const struct stat *st, uint64_t path_hash, long long size) { + struct link_size_slot *slot = link_size_slot_for(st, path_hash); + uint32_t seq = __atomic_load_n(&slot->seq, __ATOMIC_RELAXED); + // another thread writes it: this one is not kept + if ((seq & 1) || !__atomic_compare_exchange_n(&slot->seq, &seq, seq + 1, 0, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)) return; + __atomic_thread_fence(__ATOMIC_RELEASE); + LS_STORE(dev, (uint64_t)st->st_dev); + LS_STORE(ino, (uint64_t)st->st_ino); + LS_STORE(path_hash, path_hash); + LS_STORE(ctime_sec, (int64_t)st->st_ctim.tv_sec); + LS_STORE(ctime_nsec, (int64_t)st->st_ctim.tv_nsec); + LS_STORE(raw, (long long)st->st_size); + LS_STORE(size, size); + __atomic_store_n(&slot->seq, seq + 2, __ATOMIC_RELEASE); +} + +// Whether the link is one of /proc's (fd/N, cwd, exe...): those point elsewhere while they stay the +// same inode (dup2() gives fd/N another file), so their size is never kept. Also when it was asked +// for by a path relative to a directory of /proc: by the device, which is /proc's. +static int link_is_procs(const struct stat *st, const char *target) { + if (target && strncmp(target, "/proc/", 6) == 0) return 1; + static uint64_t proc_dev; // device of /proc plus one, 0: not known yet + uint64_t dev = __atomic_load_n(&proc_dev, __ATOMIC_RELAXED); + if (dev == 0) { + static int (*orig_lstat)(const char *, struct stat *) = NULL; + if (!orig_lstat) orig_lstat = (int (*)(const char *, struct stat *))dlsym(RTLD_NEXT, "lstat"); + struct stat p; + if (!orig_lstat || orig_lstat("/proc/self", &p) != 0) return 1; // not known: not kept + dev = (uint64_t)p.st_dev + 1; + __atomic_store_n(&proc_dev, dev, __ATOMIC_RELAXED); + } + return (uint64_t)st->st_dev + 1 == dev; +} + +// statbuf (of a link at pathname, which dirfd and target reach) with the size of the rewritten target +static void link_size_as_read(int dirfd, const char *pathname, const char *target, struct stat *statbuf) { + int keep = !link_is_procs(statbuf, target); + uint64_t hash = link_path_hash(dirfd, pathname); + long long kept = keep ? link_size_cached(statbuf, hash) : -1; + if (kept >= 0) { + statbuf->st_size = (off_t)kept; + return; + } + static ssize_t (*orig_readlinkat)(int, const char *, char *, size_t) = NULL; + if (!orig_readlinkat) orig_readlinkat = (ssize_t (*)(int, const char *, char *, size_t))dlsym(RTLD_NEXT, "readlinkat"); + char link_buf[PATH_MAX]; + ssize_t llen = orig_readlinkat ? orig_readlinkat(dirfd, target, link_buf, sizeof(link_buf) - 1) : -1; + if (llen <= 0) return; + ssize_t post_len = postprocess_readlink(pathname, link_buf, llen, sizeof(link_buf)); + if (post_len > 0) { + if (keep) link_size_keep(statbuf, hash, (long long)post_len); + statbuf->st_size = post_len; + } +} + // Hook lstat int lstat(const char *pathname, struct stat *statbuf) { static int (*orig_lstat)(const char *, struct stat *) = NULL; @@ -510,16 +612,7 @@ int lstat(const char *pathname, struct stat *statbuf) { } if (ret == 0 && statbuf && S_ISLNK(statbuf->st_mode) && g_cortex_root[0] != '\0' && target && readlink_result_may_change(pathname, (long long)statbuf->st_size)) { - char link_buf[PATH_MAX]; - static ssize_t (*orig_readlink)(const char *, char *, size_t) = NULL; - if (!orig_readlink) orig_readlink = (ssize_t (*)(const char *, char *, size_t))dlsym(RTLD_NEXT, "readlink"); - ssize_t llen = orig_readlink ? orig_readlink(target, link_buf, sizeof(link_buf) - 1) : -1; - if (llen > 0) { - ssize_t post_len = postprocess_readlink(pathname, link_buf, llen, sizeof(link_buf)); - if (post_len > 0) { - statbuf->st_size = post_len; - } - } + link_size_as_read(AT_FDCWD, pathname, target, statbuf); } if (ret == 0) apply_stat_emulation(statbuf); return ret; @@ -542,16 +635,7 @@ int fstatat(int dirfd, const char *pathname, struct stat *statbuf, int flags) { } if (ret == 0 && statbuf && (flags & AT_SYMLINK_NOFOLLOW) && S_ISLNK(statbuf->st_mode) && g_cortex_root[0] != '\0' && target && readlink_result_may_change(pathname, (long long)statbuf->st_size)) { - char link_buf[PATH_MAX]; - static ssize_t (*orig_readlinkat)(int, const char *, char *, size_t) = NULL; - if (!orig_readlinkat) orig_readlinkat = (ssize_t (*)(int, const char *, char *, size_t))dlsym(RTLD_NEXT, "readlinkat"); - ssize_t llen = orig_readlinkat ? orig_readlinkat(dirfd, target, link_buf, sizeof(link_buf) - 1) : -1; - if (llen > 0) { - ssize_t post_len = postprocess_readlink(pathname, link_buf, llen, sizeof(link_buf)); - if (post_len > 0) { - statbuf->st_size = post_len; - } - } + link_size_as_read(dirfd, pathname, target, statbuf); } if (ret == 0) apply_stat_emulation(statbuf); return ret; diff --git a/tests/bench/micro.c b/tests/bench/micro.c index 1745029..cb4d8b3 100644 --- a/tests/bench/micro.c +++ b/tests/bench/micro.c @@ -90,6 +90,15 @@ static void t_lstat_file(long n, const char *root) { for (long i = 0; i < n; i++) { if (lstat(p, &st) != 0) die("lstat", p); sink += (unsigned long long)st.st_size; } } +static void t_lstat_alt_link(long n, const char *root) { + char p[PATH_MAX]; path_of(p, root, "/etc/alternatives/awk"); /* -> /usr/bin/mawk: a link the hook rewrites */ + struct stat st; + for (long i = 0; i < n; i++) { + if (lstat(p, &st) != 0 || !S_ISLNK(st.st_mode)) die("lstat(alternatives link)", p); + sink += (unsigned long long)st.st_size; + } +} + static void t_lstat_link(long n, const char *root) { char p[PATH_MAX]; path_of(p, root, "/bin"); /* a symlink to usr/bin on Ubuntu 24.04 */ struct stat st; @@ -275,7 +284,7 @@ static void t_spawn_host(long n, const char *root) { static const struct { const char *name; test_fn fn; } TESTS[] = { { "compute", t_compute }, { "stat", t_stat }, { "stat_deep", t_stat_deep }, { "stat_miss", t_stat_miss }, - { "stat_miss_usrbin", t_stat_miss_usrbin }, { "lstat_file", t_lstat_file }, { "lstat_link", t_lstat_link }, + { "stat_miss_usrbin", t_stat_miss_usrbin }, { "lstat_file", t_lstat_file }, { "lstat_link", t_lstat_link }, { "lstat_alt_link", t_lstat_alt_link }, { "access", t_access }, { "open", t_open }, { "openat_rel", t_openat_rel }, { "fopen", t_fopen }, { "readlink", t_readlink }, { "readdir", t_readdir }, { "realpath", t_realpath }, { "getcwd", t_getcwd }, { "write_null", t_write_null }, { "dup_close", t_dup_close }, { "sigprocmask", t_sigprocmask }, diff --git a/tests/scripts/test_hook_qemu.sh b/tests/scripts/test_hook_qemu.sh index 044cfa0..cce6423 100755 --- a/tests/scripts/test_hook_qemu.sh +++ b/tests/scripts/test_hook_qemu.sh @@ -703,6 +703,94 @@ check "SCM_CREDENTIALS are sent with the real ids" sh -c 'echo "$1" | grep -q "s check "the peer of a Unix socket has the ids the program sees for itself" sh -c 'echo "$1" | grep -q "peer ids 0 0, mine 0 0" && echo "$1" | grep -q "received ids 0 0"' sh "$out" echo "$out" | grep -q "received ids 0 0" || echo "$out" | sed 's/^/ /' +# lstat() of a link reports the length of the target readlink() gives (rewritten); the hook keeps that +# length instead of reading the link at every lstat(), and a link replaced by another is read again +mkdir -p "$work/root/etc/alternatives" +rm -f "$work/root/etc/alternatives/lnk-test" +ln -s "$work/root/usr/bin/lnk-target" "$work/root/etc/alternatives/lnk-test" +prog link_sizes <<'EOC' +#include +#include +#include +#include +#include +#include +int main(int argc, char **argv) { + const char *p = "/etc/alternatives/lnk-test"; + char buf[4096]; + struct stat a, b, c; + for (int i = 0; i < 3; i++) lstat(p, &a); + fstatat(AT_FDCWD, p, &b, AT_SYMLINK_NOFOLLOW); + ssize_t r = readlink(p, buf, sizeof(buf)); + printf("first: lstat %lld fstatat %lld readlink %zd\n", (long long)a.st_size, (long long)b.st_size, r); + // replaced by a link to another place, as update-alternatives does + char real[4096]; + snprintf(real, sizeof(real), "%s%s", argv[1], p); + if (unlink(real) != 0 || symlink("/usr/lib/a/much/longer/target/name", real) != 0) return 2; + lstat(p, &c); + r = readlink(p, buf, sizeof(buf)); + printf("replaced: lstat %lld readlink %zd\n", (long long)c.st_size, r); + // a buffer too small for the target as the kernel stores it (with the rootfs path in front) but + // large enough for the rewritten one: the whole of it, as coreutils' readlink asks (64 bytes) + snprintf(real, sizeof(real), "%s/usr/share/doc/a-name-that-makes-the-real-target-long", argv[1]); + unlink(real); + if (symlink(real, "/tmp/long-link") != 0 && symlink(real, "/tmp/long-link") != 0) return 3; + char small[64]; + r = readlink("/tmp/long-link", small, sizeof(small)); + printf("small buffer: %zd %.*s\n", r, (int)(r > 0 ? r : 0), small); + r = readlinkat(AT_FDCWD, "/tmp/long-link", small, 20); + printf("smaller than the target: %zd %.*s\n", r, (int)(r > 0 ? r : 0), small); + errno = 0; + r = readlink("/tmp/long-link", small, 0); + int err = errno; + printf("no buffer: %zd %s\n", r, err == EINVAL ? "EINVAL" : "other"); + return 0; +} +EOC +mkdir -p "$work/root/tmp" +rm -f "$work/root/tmp/long-link" +out=$(run "$work/link_sizes" "$work/root") +want="first: lstat 19 fstatat 19 readlink 19 +replaced: lstat 34 readlink 34 +small buffer: 53 /usr/share/doc/a-name-that-makes-the-real-target-long +smaller than the target: 20 /usr/share/doc/a-nam +no buffer: -1 EINVAL" +check "lstat() and readlink() report the rewritten target: again after the link is replaced, whole in a small buffer" test "$out" = "$want" +[ "$out" = "$want" ] || echo "$out" | sed 's/^/ /' + +# A link of /proc points elsewhere while it stays the same inode (dup2() gives fd/N another file): its +# size is never kept. Its raw size is 64, which the hook reads only with a rootfs path shorter than +# that, as on a phone: the root is given by a short name here. +prog fd_link_size <<'EOC' +#include +#include +#include +#include +int main(void) { + char fdlink[64], buf[4096]; + int one = open("/etc/alternatives", O_RDONLY | O_DIRECTORY); + int two = open("/usr/share/doc", O_RDONLY | O_DIRECTORY); + if (one < 0 || two < 0) return 2; + snprintf(fdlink, sizeof(fdlink), "/proc/self/fd/%d", one); + struct stat st; + lstat(fdlink, &st); + lstat(fdlink, &st); + dup2(two, one); + lstat(fdlink, &st); + ssize_t r = readlink(fdlink, buf, sizeof(buf)); + printf("after dup2: lstat %s readlink\n", (ssize_t)st.st_size == r ? "agrees with" : "differs from"); + return 0; +} +EOC +mkdir -p "$work/root/usr/share/doc" +short_root="/tmp/cxr$$" +ln -sfn "$work/root" "$short_root" +out=$( (cd "$work" && env -i CORTEX_ROOT="$short_root" HOME="$work" timeout 60 qemu-aarch64 "$cross/ld-linux-aarch64.so.1" \ + --preload "$work/hook.so" --library-path "$cross" "$work/fd_link_size" 2>&1 | grep -v "cannot be preloaded") ) +rm -f "$short_root" +check "lstat() of a /proc fd link follows dup2() (its size is not kept)" test "$out" = "after dup2: lstat agrees with readlink" +[ "$out" = "after dup2: lstat agrees with readlink" ] || echo " got: $out" + # A library loaded with RTLD_DEEPBIND (every PHP extension) looks for libc's functions in libc # before the hook: its calls were not seen, so it opened Android's files instead of Ubuntu's echo marker > "$work/root/etc/deepbind-marker" diff --git a/tests/scripts/test_shell_scripts.sh b/tests/scripts/test_shell_scripts.sh index fcb094d..352b8cd 100644 --- a/tests/scripts/test_shell_scripts.sh +++ b/tests/scripts/test_shell_scripts.sh @@ -867,8 +867,8 @@ else fi XARGS="$(cat "$DR/tmp/xvnc-args" 2>/dev/null)" if [[ "$XARGS" == *"-rfbunixmode 0600"* && "$XARGS" == *"-rfbport -1"* && "$XARGS" == *"-nolisten tcp"* && \ - "$XARGS" == *"-rfbunixpath $DR/tmp/.cortex-desktop/vnc.sock"* ]]; then - pass "the X server listens on no network port, only on a socket of the app's user" + "$XARGS" == *"-rfbunixpath $DR/tmp/.cortex-desktop/vnc.sock"* && "$XARGS" == *"-SendPrimary=0"* ]]; then + pass "the X server listens on no network port, only on a socket of the app's user; selected text stays on the desktop" else fail "Xvnc arguments: $XARGS" fi