mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-12 19:41:10 +00:00
fix: permissions set by Windows programs are kept on SD cards (#2504)
* fix: keep file permissions set by Windows programs on volumes without extended attributes Wine's server stores a file's security descriptor in an extended attribute. Volumes that cannot hold one dropped every descriptor, so a program that checks and repairs its install folder's permissions repeated the repair on every start. The descriptor is now kept inside the prefix when the volume refuses it, and follows the file when the server renames or deletes it. * fix: a path deleted with remove() drops its kept descriptor too
This commit is contained in:
1 parent
3b6d13a8d7
commit
b3c61cf0bf
5 files changed
+580
-7
No files matched your search
@@ -0,0 +1,291 @@
|
||||
#define _GNU_SOURCE
|
||||
#include <dlfcn.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define DESCRIPTOR "user.wine.sd"
|
||||
#define STORE "/.droiddeck-descriptors"
|
||||
#define ENTRY_MAX (PATH_MAX * 2)
|
||||
|
||||
int bl_is_wineserver(void) __attribute__((visibility("hidden")));
|
||||
|
||||
static int store_seen;
|
||||
|
||||
static int kept(const char *name) {
|
||||
return name != NULL && strcmp(name, DESCRIPTOR) == 0 && bl_is_wineserver();
|
||||
}
|
||||
|
||||
static int store_root(char *out, size_t size) {
|
||||
const char *prefix = getenv("WINEPREFIX");
|
||||
int n;
|
||||
|
||||
if (prefix == NULL || prefix[0] != '/') return -1;
|
||||
n = snprintf(out, size, "%s" STORE, prefix);
|
||||
return n > 0 && (size_t)n < size ? n : -1;
|
||||
}
|
||||
|
||||
static int store_exists(void) {
|
||||
char root[PATH_MAX];
|
||||
|
||||
if (store_seen) return 1;
|
||||
if (store_root(root, sizeof(root)) < 0) return 0;
|
||||
store_seen = access(root, F_OK) == 0;
|
||||
return store_seen;
|
||||
}
|
||||
|
||||
static int entry_for(const char *path, char *out, size_t size, const char *suffix, int create) {
|
||||
int used = store_root(out, size);
|
||||
const char *part = path, *end;
|
||||
|
||||
if (used < 0 || path[0] != '/') return -1;
|
||||
if (create && mkdir(out, 0700) != 0 && errno != EEXIST) return -1;
|
||||
while (*part == '/') part++;
|
||||
if (*part == '\0') return -1;
|
||||
for (;;) {
|
||||
size_t length;
|
||||
const char *tail;
|
||||
|
||||
end = strchr(part, '/');
|
||||
length = end ? (size_t)(end - part) : strlen(part);
|
||||
tail = end ? ".d" : suffix;
|
||||
if (length + strlen(tail) > NAME_MAX || used + length + strlen(tail) + 2 > size) return -1;
|
||||
out[used++] = '/';
|
||||
memcpy(out + used, part, length);
|
||||
used += (int)length;
|
||||
strcpy(out + used, tail);
|
||||
used += (int)strlen(tail);
|
||||
if (!end) break;
|
||||
if (create && mkdir(out, 0700) != 0 && errno != EEXIST) return -1;
|
||||
part = end + 1;
|
||||
}
|
||||
if (create) store_seen = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int path_of(int fd, char *out, size_t size) {
|
||||
char link[40];
|
||||
ssize_t n;
|
||||
|
||||
snprintf(link, sizeof(link), "/proc/self/fd/%d", fd);
|
||||
n = readlink(link, out, size - 1);
|
||||
if (n <= 0 || out[0] != '/') return -1;
|
||||
out[n] = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int resolved(const char *path, char *out, size_t size) {
|
||||
int fd, result;
|
||||
|
||||
if (path == NULL) return -1;
|
||||
fd = open(path, O_PATH | O_NOFOLLOW | O_CLOEXEC);
|
||||
if (fd < 0) return -1;
|
||||
result = path_of(fd, out, size);
|
||||
close(fd);
|
||||
return result;
|
||||
}
|
||||
|
||||
static int entry_of(int fd, char *out, size_t size, int create) {
|
||||
char path[PATH_MAX];
|
||||
|
||||
if (path_of(fd, path, sizeof(path)) != 0) return -1;
|
||||
return entry_for(path, out, size, ".sd", create);
|
||||
}
|
||||
|
||||
static ssize_t stored_read(int fd, void *value, size_t size) {
|
||||
char entry[ENTRY_MAX];
|
||||
struct stat st;
|
||||
ssize_t got = 0;
|
||||
int file;
|
||||
|
||||
if (entry_of(fd, entry, sizeof(entry), 0) != 0) return -2;
|
||||
file = open(entry, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
|
||||
if (file < 0) {
|
||||
if (errno != ENOENT) return -2;
|
||||
errno = ENODATA;
|
||||
return -1;
|
||||
}
|
||||
if (fstat(file, &st) != 0) {
|
||||
close(file);
|
||||
return -2;
|
||||
}
|
||||
if (size == 0) {
|
||||
close(file);
|
||||
return (ssize_t)st.st_size;
|
||||
}
|
||||
if ((size_t)st.st_size > size) {
|
||||
close(file);
|
||||
errno = ERANGE;
|
||||
return -1;
|
||||
}
|
||||
while (got < st.st_size) {
|
||||
ssize_t n = read(file, (char *)value + got, (size_t)(st.st_size - got));
|
||||
if (n <= 0) {
|
||||
close(file);
|
||||
return -2;
|
||||
}
|
||||
got += n;
|
||||
}
|
||||
close(file);
|
||||
return got;
|
||||
}
|
||||
|
||||
static int stored_write(int fd, const void *value, size_t size) {
|
||||
char entry[ENTRY_MAX], fresh[ENTRY_MAX + 8];
|
||||
size_t put = 0;
|
||||
int file;
|
||||
|
||||
if (entry_of(fd, entry, sizeof(entry), 1) != 0) return -1;
|
||||
snprintf(fresh, sizeof(fresh), "%s.new", entry);
|
||||
file = open(fresh, O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW | O_CLOEXEC, 0600);
|
||||
if (file < 0) return -1;
|
||||
while (put < size) {
|
||||
ssize_t n = write(file, (const char *)value + put, size - put);
|
||||
if (n <= 0) {
|
||||
close(file);
|
||||
unlinkat(AT_FDCWD, fresh, 0);
|
||||
return -1;
|
||||
}
|
||||
put += (size_t)n;
|
||||
}
|
||||
if (close(file) != 0 || renameat(AT_FDCWD, fresh, AT_FDCWD, entry) != 0) {
|
||||
unlinkat(AT_FDCWD, fresh, 0);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
ssize_t fgetxattr(int fd, const char *name, void *value, size_t size) {
|
||||
static ssize_t (*next)(int, const char *, void *, size_t);
|
||||
ssize_t result;
|
||||
|
||||
if (next == NULL) next = dlsym(RTLD_NEXT, "fgetxattr");
|
||||
if (next == NULL) {
|
||||
errno = ENOSYS;
|
||||
return -1;
|
||||
}
|
||||
result = next(fd, name, value, size);
|
||||
if (result >= 0 || errno != ENOTSUP || !kept(name)) return result;
|
||||
result = stored_read(fd, value, size);
|
||||
if (result == -2) {
|
||||
errno = ENOTSUP;
|
||||
return -1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int fsetxattr(int fd, const char *name, const void *value, size_t size, int flags) {
|
||||
static int (*next)(int, const char *, const void *, size_t, int);
|
||||
int saved = errno, result;
|
||||
|
||||
if (next == NULL) next = dlsym(RTLD_NEXT, "fsetxattr");
|
||||
if (next == NULL) {
|
||||
errno = ENOSYS;
|
||||
return -1;
|
||||
}
|
||||
result = next(fd, name, value, size, flags);
|
||||
if (result == 0 || errno != ENOTSUP || !kept(name)) return result;
|
||||
if (stored_write(fd, value, size) != 0) {
|
||||
errno = ENOTSUP;
|
||||
return -1;
|
||||
}
|
||||
errno = saved;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fremovexattr(int fd, const char *name) {
|
||||
static int (*next)(int, const char *);
|
||||
char entry[ENTRY_MAX];
|
||||
int saved = errno, result;
|
||||
|
||||
if (next == NULL) next = dlsym(RTLD_NEXT, "fremovexattr");
|
||||
if (next == NULL) {
|
||||
errno = ENOSYS;
|
||||
return -1;
|
||||
}
|
||||
result = next(fd, name);
|
||||
if (result == 0 || errno != ENOTSUP || !kept(name)) return result;
|
||||
if (entry_of(fd, entry, sizeof(entry), 0) != 0) {
|
||||
errno = ENOTSUP;
|
||||
return -1;
|
||||
}
|
||||
if (unlinkat(AT_FDCWD, entry, 0) != 0) {
|
||||
errno = errno == ENOENT ? ENODATA : ENOTSUP;
|
||||
return -1;
|
||||
}
|
||||
errno = saved;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tracked(const char *path, char *out, size_t size) {
|
||||
int saved = errno, found;
|
||||
|
||||
found = bl_is_wineserver() && store_exists() && resolved(path, out, size) == 0;
|
||||
errno = saved;
|
||||
return found;
|
||||
}
|
||||
|
||||
static void forget(const char *path, int folder) {
|
||||
char entry[ENTRY_MAX];
|
||||
int saved = errno;
|
||||
|
||||
if (entry_for(path, entry, sizeof(entry), ".sd", 0) == 0) unlinkat(AT_FDCWD, entry, 0);
|
||||
if (folder && entry_for(path, entry, sizeof(entry), ".d", 0) == 0) unlinkat(AT_FDCWD, entry, AT_REMOVEDIR);
|
||||
errno = saved;
|
||||
}
|
||||
|
||||
static void follow(const char *from, const char *to) {
|
||||
static const char *const kinds[] = {".sd", ".d"};
|
||||
char old[ENTRY_MAX], new[ENTRY_MAX];
|
||||
int saved = errno;
|
||||
|
||||
for (size_t i = 0; i < 2; i++) {
|
||||
if (entry_for(from, old, sizeof(old), kinds[i], 0) != 0) continue;
|
||||
if (access(old, F_OK) != 0) {
|
||||
if (i == 0 && entry_for(to, new, sizeof(new), kinds[i], 0) == 0) unlinkat(AT_FDCWD, new, 0);
|
||||
continue;
|
||||
}
|
||||
if (entry_for(to, new, sizeof(new), kinds[i], 1) != 0 || renameat(AT_FDCWD, old, AT_FDCWD, new) != 0) {
|
||||
if (i == 0) unlinkat(AT_FDCWD, old, 0);
|
||||
}
|
||||
}
|
||||
errno = saved;
|
||||
}
|
||||
|
||||
__attribute__((visibility("hidden")))
|
||||
int bl_descriptor_unlink(const char *path, int (*real)(const char *)) {
|
||||
char target[PATH_MAX];
|
||||
int known = tracked(path, target, sizeof(target)), result = real(path);
|
||||
|
||||
if (result == 0 && known) forget(target, 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
__attribute__((visibility("hidden")))
|
||||
int bl_descriptor_rmdir(const char *path, int (*real)(const char *)) {
|
||||
char target[PATH_MAX];
|
||||
int known = tracked(path, target, sizeof(target)), result = real(path);
|
||||
|
||||
if (result == 0 && known) forget(target, 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
__attribute__((visibility("hidden")))
|
||||
int bl_descriptor_remove(const char *path, int (*real)(const char *)) {
|
||||
return bl_descriptor_rmdir(path, real);
|
||||
}
|
||||
|
||||
__attribute__((visibility("hidden")))
|
||||
int bl_descriptor_rename(const char *from, const char *to, int (*real)(const char *, const char *)) {
|
||||
char old[PATH_MAX], new[PATH_MAX];
|
||||
int known = tracked(from, old, sizeof(old)), result = real(from, to);
|
||||
|
||||
if (result == 0 && known && tracked(to, new, sizeof(new))) follow(old, new);
|
||||
return result;
|
||||
}
|
||||
@@ -846,9 +846,17 @@ int bl_dircache_create(int dirfd, const char *path, int flags, mode_t mode,
|
||||
}
|
||||
|
||||
ONE_PATH(mkdir, ADDED, IN_CREATE, next_mkdir(path, mode), mode_t mode)
|
||||
ONE_PATH(rmdir, REMOVED, IN_DELETE, next_rmdir(path))
|
||||
ONE_PATH(unlink, REMOVED, IN_DELETE, next_unlink(path))
|
||||
ONE_PATH(remove, REMOVED, IN_DELETE, next_remove(path))
|
||||
int bl_descriptor_unlink(const char *path, int (*real)(const char *)) __attribute__((visibility("hidden")));
|
||||
int bl_descriptor_rmdir(const char *path, int (*real)(const char *)) __attribute__((visibility("hidden")));
|
||||
int bl_descriptor_remove(const char *path, int (*real)(const char *)) __attribute__((visibility("hidden")));
|
||||
int bl_descriptor_rename(const char *from, const char *to, int (*real)(const char *, const char *))
|
||||
__attribute__((visibility("hidden")));
|
||||
|
||||
static int moved(const char *from, const char *to) { return bl_descriptor_rename(from, to, next_rename); }
|
||||
|
||||
ONE_PATH(rmdir, REMOVED, IN_DELETE, bl_descriptor_rmdir(path, next_rmdir))
|
||||
ONE_PATH(unlink, REMOVED, IN_DELETE, bl_descriptor_unlink(path, next_unlink))
|
||||
ONE_PATH(remove, REMOVED, IN_DELETE, bl_descriptor_remove(path, next_remove))
|
||||
|
||||
#define TWO_PATHS(name, gone, event, call) \
|
||||
int name(const char *from, const char *to) { \
|
||||
@@ -881,7 +889,7 @@ ONE_PATH(remove, REMOVED, IN_DELETE, next_remove(path))
|
||||
int bl_link_lock(const char *oldpath, const char *newpath, int result) __attribute__((visibility("hidden")));
|
||||
static int link_or_lock(const char *from, const char *to) { return bl_link_lock(from, to, next_link(from, to)); }
|
||||
|
||||
TWO_PATHS(rename, 1, IN_MOVED_TO, next_rename)
|
||||
TWO_PATHS(rename, 1, IN_MOVED_TO, moved)
|
||||
TWO_PATHS(link, 0, IN_CREATE, link_or_lock)
|
||||
TWO_PATHS(symlink, 0, IN_CREATE, next_symlink)
|
||||
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
#define FAT_MAGIC 0x4d44
|
||||
#define EXFAT_MAGIC 0x2011bab0
|
||||
|
||||
static int is_wineserver(void) {
|
||||
__attribute__((visibility("hidden")))
|
||||
int bl_is_wineserver(void) {
|
||||
static int cached = -1;
|
||||
char exe[PATH_MAX];
|
||||
const char *base;
|
||||
@@ -43,7 +44,7 @@ void bl_storage_owner(int fd, struct stat *st) {
|
||||
int saved = errno;
|
||||
uid_t uid;
|
||||
|
||||
if (st != NULL && is_wineserver()) {
|
||||
if (st != NULL && bl_is_wineserver()) {
|
||||
uid = getuid();
|
||||
if (st->st_uid != uid && has_no_unix_owner(fd)) st->st_uid = uid;
|
||||
}
|
||||
@@ -60,7 +61,7 @@ int fchmod(int fd, mode_t mode) {
|
||||
return -1;
|
||||
}
|
||||
result = next_fchmod(fd, mode);
|
||||
if (result != 0 && errno == EPERM && is_wineserver()) {
|
||||
if (result != 0 && errno == EPERM && bl_is_wineserver()) {
|
||||
if (has_no_unix_owner(fd)) {
|
||||
errno = saved;
|
||||
return 0;
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
PRELOAD = Path(__file__).resolve().parents[1] / "linuxfs/preload"
|
||||
|
||||
FAKE = r"""
|
||||
#define _GNU_SOURCE
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static int answer(int fd) {
|
||||
char link[32], path[4096];
|
||||
snprintf(link, sizeof(link), "/proc/self/fd/%d", fd);
|
||||
long n = syscall(SYS_readlinkat, AT_FDCWD, link, path, sizeof(path) - 1);
|
||||
path[n > 0 ? n : 0] = '\0';
|
||||
if (strstr(path, "/card/")) return ENOTSUP;
|
||||
if (strstr(path, "/denied/")) return EACCES;
|
||||
return ENODATA;
|
||||
}
|
||||
|
||||
ssize_t fgetxattr(int fd, const char *name, void *value, size_t size) {
|
||||
(void)name; (void)value; (void)size;
|
||||
errno = answer(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int fsetxattr(int fd, const char *name, const void *value, size_t size, int flags) {
|
||||
(void)name; (void)value; (void)size; (void)flags;
|
||||
int error = answer(fd);
|
||||
if (error == ENODATA) return 0;
|
||||
errno = error;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int fremovexattr(int fd, const char *name) {
|
||||
(void)name;
|
||||
errno = answer(fd);
|
||||
return -1;
|
||||
}
|
||||
"""
|
||||
|
||||
PROGRAM = r"""
|
||||
#define _GNU_SOURCE
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/xattr.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static const char *error(void) {
|
||||
return errno == ENOTSUP ? "unsupported" : errno == ENODATA ? "none" : errno == ERANGE ? "range" :
|
||||
errno == EACCES ? "denied" : "other";
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
for (int i = 1; i < argc; i++) {
|
||||
const char *op = argv[i];
|
||||
if (!strcmp(op, "unlink") || !strcmp(op, "rmdir")) {
|
||||
const char *path = argv[++i];
|
||||
printf("%s\n", (op[0] == 'u' ? unlink(path) : rmdir(path)) == 0 ? "ok" : "failed");
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(op, "remove-path")) {
|
||||
printf("%s\n", remove(argv[++i]) == 0 ? "ok" : "failed");
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(op, "rename")) {
|
||||
const char *from = argv[++i], *to = argv[++i];
|
||||
printf("%s\n", rename(from, to) == 0 ? "ok" : "failed");
|
||||
continue;
|
||||
}
|
||||
int fd = open(argv[++i], O_RDONLY);
|
||||
char value[64];
|
||||
if (!strcmp(op, "set")) {
|
||||
const char *text = argv[++i];
|
||||
errno = 1234;
|
||||
int result = fsetxattr(fd, "user.wine.sd", text, strlen(text), 0);
|
||||
printf("%s\n", result == 0 ? errno == 1234 ? "ok" : "errno" : error());
|
||||
} else if (!strcmp(op, "get")) {
|
||||
ssize_t n = fgetxattr(fd, "user.wine.sd", value, sizeof(value));
|
||||
if (n < 0) printf("%s\n", error());
|
||||
else printf("=%.*s\n", (int)n, value);
|
||||
} else if (!strcmp(op, "size")) {
|
||||
ssize_t n = fgetxattr(fd, "user.wine.sd", NULL, 0);
|
||||
if (n < 0) printf("%s\n", error());
|
||||
else printf("%d\n", (int)n);
|
||||
} else if (!strcmp(op, "short")) {
|
||||
ssize_t n = fgetxattr(fd, "user.wine.sd", value, 2);
|
||||
printf("%s\n", n < 0 ? error() : "read");
|
||||
} else if (!strcmp(op, "remove")) {
|
||||
errno = 1234;
|
||||
int result = fremovexattr(fd, "user.wine.sd");
|
||||
printf("%s\n", result == 0 ? errno == 1234 ? "ok" : "errno" : error());
|
||||
} else if (!strcmp(op, "other")) {
|
||||
ssize_t n = fgetxattr(fd, "user.other", value, sizeof(value));
|
||||
printf("%s\n", n < 0 ? error() : "read");
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
@unittest.skipUnless(shutil.which("cc"), "needs a C compiler")
|
||||
class DescriptorPreloadTest(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.build_dir = tempfile.TemporaryDirectory()
|
||||
build = Path(cls.build_dir.name)
|
||||
(build / "fake.c").write_text(FAKE)
|
||||
(build / "program.c").write_text(PROGRAM)
|
||||
cls.preload = build / "libblsession.so"
|
||||
cls.fake = build / "fake.so"
|
||||
subprocess.run(["cc", "-shared", "-fPIC", "-O2", "-Wall", "-pthread", "-o", str(cls.preload),
|
||||
*map(str, sorted(PRELOAD.glob("*.c"))), "-ldl"], check=True)
|
||||
subprocess.run(["cc", "-shared", "-fPIC", "-O2", "-o", str(cls.fake), str(build / "fake.c")], check=True)
|
||||
cls.wineserver = build / "wineserver"
|
||||
subprocess.run(["cc", "-O2", "-o", str(cls.wineserver), str(build / "program.c")], check=True)
|
||||
cls.other = build / "wine64-preloader"
|
||||
shutil.copy(cls.wineserver, cls.other)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.build_dir.cleanup()
|
||||
|
||||
def setUp(self):
|
||||
self.temp = tempfile.TemporaryDirectory()
|
||||
self.dir = Path(os.path.realpath(self.temp.name))
|
||||
self.prefix = self.dir / "prefix"
|
||||
self.prefix.mkdir()
|
||||
self.store = self.prefix / ".droiddeck-descriptors"
|
||||
for kind in ("card", "disk", "denied"):
|
||||
(self.dir / kind / "game").mkdir(parents=True)
|
||||
(self.dir / kind / "game" / "file").write_text("x")
|
||||
|
||||
def tearDown(self):
|
||||
self.temp.cleanup()
|
||||
|
||||
def path(self, *parts):
|
||||
return str(self.dir.joinpath(*parts))
|
||||
|
||||
def run_as(self, program, *args, prefix=True):
|
||||
env = dict(os.environ, LD_PRELOAD="%s:%s" % (self.preload, self.fake))
|
||||
env.pop("WINEPREFIX", None)
|
||||
if prefix:
|
||||
env["WINEPREFIX"] = str(self.prefix)
|
||||
result = subprocess.run([str(program), *args], env=env, capture_output=True, text=True, check=True)
|
||||
return result.stdout.split()
|
||||
|
||||
def server(self, *args, **kwargs):
|
||||
return self.run_as(self.wineserver, *args, **kwargs)
|
||||
|
||||
def test_a_descriptor_is_kept_where_the_volume_cannot_store_it(self):
|
||||
file, folder = self.path("card", "game", "file"), self.path("card", "game")
|
||||
self.assertEqual(["none", "ok", "ok"], self.server("get", file, "set", file, "one", "set", folder, "two"))
|
||||
self.assertEqual(["=one", "3", "=two"], self.server("get", file, "size", file, "get", folder))
|
||||
|
||||
def test_a_descriptor_is_replaced_and_removed(self):
|
||||
file = self.path("card", "game", "file")
|
||||
self.assertEqual(["ok", "ok", "=longer"], self.server("set", file, "one", "set", file, "longer", "get", file))
|
||||
self.assertEqual(["range", "ok", "none", "none"], self.server("short", file, "remove", file, "get", file, "remove", file))
|
||||
|
||||
def test_a_volume_that_stores_descriptors_is_left_alone(self):
|
||||
file = self.path("disk", "game", "file")
|
||||
self.assertEqual(["ok", "none", "none"], self.server("set", file, "one", "get", file, "remove", file))
|
||||
self.assertFalse(self.store.exists())
|
||||
|
||||
def test_other_failures_keep_their_error(self):
|
||||
file = self.path("denied", "game", "file")
|
||||
self.assertEqual(["denied", "denied", "denied"], self.server("set", file, "one", "get", file, "remove", file))
|
||||
self.assertFalse(self.store.exists())
|
||||
|
||||
def test_other_programs_and_other_attributes_are_unchanged(self):
|
||||
file = self.path("card", "game", "file")
|
||||
self.assertEqual(["unsupported", "unsupported", "unsupported"],
|
||||
self.run_as(self.other, "set", file, "one", "get", file, "remove", file))
|
||||
self.assertEqual(["ok", "unsupported"], self.server("set", file, "one", "other", file))
|
||||
self.assertEqual(["unsupported"], self.run_as(self.other, "get", file))
|
||||
|
||||
def test_nothing_is_kept_without_a_prefix(self):
|
||||
file = self.path("card", "game", "file")
|
||||
self.assertEqual(["unsupported", "unsupported", "unsupported"],
|
||||
self.server("set", file, "one", "get", file, "remove", file, prefix=False))
|
||||
|
||||
def test_a_deleted_file_loses_its_descriptor(self):
|
||||
file, folder = self.path("card", "game", "file"), self.path("card", "game")
|
||||
self.assertEqual(["ok", "ok", "ok"], self.server("set", file, "one", "set", folder, "two", "unlink", file))
|
||||
Path(file).write_text("x")
|
||||
self.assertEqual(["none", "ok", "ok"], self.server("get", file, "unlink", file, "rmdir", folder))
|
||||
Path(folder).mkdir()
|
||||
self.assertEqual(["none"], self.server("get", folder))
|
||||
|
||||
def test_a_removed_file_or_folder_loses_its_descriptor(self):
|
||||
file, folder = self.path("card", "game", "file"), self.path("card", "game")
|
||||
self.assertEqual(["ok", "ok", "ok", "ok"],
|
||||
self.server("set", file, "one", "set", folder, "two", "remove-path", file, "remove-path", folder))
|
||||
Path(folder).mkdir()
|
||||
Path(file).write_text("x")
|
||||
self.assertEqual(["none", "none"], self.server("get", folder, "get", file))
|
||||
|
||||
def test_a_file_deleted_through_a_link_loses_its_descriptor(self):
|
||||
file = self.path("card", "game", "file")
|
||||
os.symlink(self.path("card"), self.path("drive"))
|
||||
self.assertEqual(["ok", "ok"], self.server("set", file, "one", "unlink", self.path("drive", "game", "file")))
|
||||
Path(file).write_text("x")
|
||||
self.assertEqual(["none"], self.server("get", file))
|
||||
|
||||
def test_a_descriptor_follows_a_rename(self):
|
||||
file, moved = self.path("card", "game", "file"), self.path("card", "game", "moved")
|
||||
self.assertEqual(["ok", "ok", "=one"], self.server("set", file, "one", "rename", file, moved, "get", moved))
|
||||
Path(file).write_text("x")
|
||||
self.assertEqual(["none", "ok", "none"], self.server("get", file, "rename", file, moved, "get", moved))
|
||||
|
||||
def test_descriptors_follow_a_renamed_folder(self):
|
||||
file, folder, moved = self.path("card", "game", "file"), self.path("card", "game"), self.path("card", "moved")
|
||||
self.assertEqual(["ok", "ok", "ok"], self.server("set", file, "one", "set", folder, "two", "rename", folder, moved))
|
||||
self.assertEqual(["=two", "=one"], self.server("get", moved, "get", self.path("card", "moved", "file")))
|
||||
Path(folder).mkdir()
|
||||
(Path(folder) / "file").write_text("x")
|
||||
self.assertEqual(["none", "none"], self.server("get", folder, "get", file))
|
||||
|
||||
def test_deletes_outside_the_store_cost_nothing_before_it_exists(self):
|
||||
file = self.path("disk", "game", "file")
|
||||
self.assertEqual(["ok"], self.server("unlink", file))
|
||||
self.assertFalse(self.store.exists())
|
||||
|
||||
def test_a_name_too_long_to_keep_reports_no_support(self):
|
||||
long_file = self.path("card", "game", "n" * 254)
|
||||
Path(long_file).write_text("x")
|
||||
self.assertEqual(["unsupported", "unsupported"], self.server("set", long_file, "one", "get", long_file))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -13,8 +13,18 @@ PROGRAM = r"""
|
||||
#include "FASTPATH"
|
||||
#include <sys/mount.h>
|
||||
|
||||
static const char *race_from, *race_to;
|
||||
static dev_t race_dev;
|
||||
static ino_t race_ino;
|
||||
|
||||
static long host_sys(long nr, long a, long b, long c, long d, long e, long f) {
|
||||
long r = syscall(nr, a, b, c, d, e, f);
|
||||
struct stat fs;
|
||||
if (r >= 0 && nr == SYS_fcntl && b == F_DUPFD_CLOEXEC && c == DIR_FD_FLOOR && race_from
|
||||
&& syscall(SYS_fstat, a, &fs) == 0 && fs.st_dev == race_dev && fs.st_ino == race_ino) {
|
||||
syscall(SYS_renameat, AT_FDCWD, race_from, AT_FDCWD, race_to);
|
||||
race_from = NULL;
|
||||
}
|
||||
return r < 0 ? -errno : r;
|
||||
}
|
||||
|
||||
@@ -72,6 +82,14 @@ int main(int argc, char **argv) {
|
||||
} else if (!strcmp(op, "host-mv")) {
|
||||
show(host_sys(SYS_renameat, AT_FDCWD, (long)argv[i + 1], AT_FDCWD, (long)argv[i + 2], 0, 0));
|
||||
i += 2;
|
||||
} else if (!strcmp(op, "race-mv")) {
|
||||
struct stat rs;
|
||||
race_from = argv[i + 1];
|
||||
race_to = argv[i + 2];
|
||||
i += 2;
|
||||
show(host_sys(SYS_newfstatat, AT_FDCWD, (long)race_from, (long)&rs, 0, 0, 0));
|
||||
race_dev = rs.st_dev;
|
||||
race_ino = rs.st_ino;
|
||||
} else if (!strcmp(op, "host-rmdir")) {
|
||||
show(host_sys(SYS_unlinkat, AT_FDCWD, (long)argv[++i], AT_REMOVEDIR, 0, 0, 0));
|
||||
} else if (!strcmp(op, "host-mkdir")) {
|
||||
@@ -169,6 +187,16 @@ class FastPathFuseDirsTest(unittest.TestCase):
|
||||
"stat", "/mnt/card/Games/steamapps/common/Moved/Data/keep")
|
||||
self.assertEqual(["ok", "ok", "E2", "ok"], out)
|
||||
|
||||
def test_a_folder_renamed_while_it_is_opened_is_not_used_under_its_old_name(self):
|
||||
game = self.card / "Games/steamapps/common/Game"
|
||||
(game / "Data/keep").write_text("x")
|
||||
out = self.ask("fuse", "race-mv", str(game / "Data"), str(game / "Moved"),
|
||||
"stat", "/mnt/card/Games/steamapps/common/Game/Data/keep",
|
||||
"touch", "/mnt/card/Games/steamapps/common/Game/Data/new", "fds")
|
||||
self.assertEqual(["ok", "E2", "E2"], out[:3])
|
||||
self.assertEqual(["keep"], [p.name for p in (game / "Moved").iterdir()])
|
||||
self.assertLessEqual(int(out[-1]), 64)
|
||||
|
||||
def test_a_folder_deleted_and_made_again_is_looked_up_fresh(self):
|
||||
data = self.card / "Games/steamapps/common/Game/Data"
|
||||
out = self.ask("fuse", "touch", "/mnt/card/Games/steamapps/common/Game/Data/old",
|
||||
|
||||
Reference in new issue
Block a user