mirror of https://github.com/trapexit/mergerfs.git
2 changed files with 234 additions and 5 deletions
@ -0,0 +1,219 @@ |
|||||
|
#!/usr/bin/env python3 |
||||
|
|
||||
|
import ctypes |
||||
|
import errno |
||||
|
import os |
||||
|
import sys |
||||
|
import tempfile |
||||
|
|
||||
|
|
||||
|
libc = ctypes.CDLL(None, use_errno=True) |
||||
|
libc.readlink.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_size_t] |
||||
|
libc.readlink.restype = ctypes.c_ssize_t |
||||
|
|
||||
|
|
||||
|
def readlink_raw(path: str, bufsiz: int): |
||||
|
buf = ctypes.create_string_buffer(bufsiz) |
||||
|
ctypes.set_errno(0) |
||||
|
rv = libc.readlink(path.encode(), buf, bufsiz) |
||||
|
err = ctypes.get_errno() |
||||
|
|
||||
|
return rv, err, bytes(buf) |
||||
|
|
||||
|
|
||||
|
def compare_case(name: str, |
||||
|
merge_path: str, |
||||
|
native_path: str, |
||||
|
bufsiz: int, |
||||
|
expect_errno: int = None): |
||||
|
m_rv, m_errno, m_buf = readlink_raw(merge_path, bufsiz) |
||||
|
n_rv, n_errno, n_buf = readlink_raw(native_path, bufsiz) |
||||
|
|
||||
|
if m_rv != n_rv: |
||||
|
return f"{name}: return mismatch mergerfs={m_rv} native={n_rv}" |
||||
|
if m_errno != n_errno: |
||||
|
return f"{name}: errno mismatch mergerfs={m_errno} native={n_errno}" |
||||
|
if m_rv >= 0 and m_buf != n_buf: |
||||
|
return f"{name}: buffer mismatch mergerfs={m_buf!r} native={n_buf!r}" |
||||
|
if expect_errno is not None and n_errno != expect_errno: |
||||
|
return f"{name}: expected errno={expect_errno}, got errno={n_errno}" |
||||
|
|
||||
|
return None |
||||
|
|
||||
|
|
||||
|
def main(): |
||||
|
if len(sys.argv) != 2: |
||||
|
print("usage: TEST_readlink_semantics <mountpoint>", file=sys.stderr) |
||||
|
return 1 |
||||
|
|
||||
|
mount = sys.argv[1] |
||||
|
target = "target-abcdefghijklmnopqrstuvwxyz" |
||||
|
|
||||
|
with tempfile.TemporaryDirectory() as native_dir: |
||||
|
paths = { |
||||
|
"merge_link": os.path.join(mount, "readlink-semantics-link"), |
||||
|
"native_link": os.path.join(native_dir, "readlink-semantics-link"), |
||||
|
"merge_regular": os.path.join(mount, "readlink-semantics-regular"), |
||||
|
"native_regular": os.path.join(native_dir, "readlink-semantics-regular"), |
||||
|
"merge_notdir": os.path.join(mount, "readlink-semantics-notdir"), |
||||
|
"native_notdir": os.path.join(native_dir, "readlink-semantics-notdir"), |
||||
|
"merge_loop": os.path.join(mount, "readlink-semantics-loop"), |
||||
|
"native_loop": os.path.join(native_dir, "readlink-semantics-loop"), |
||||
|
"merge_private_dir": os.path.join(mount, "readlink-semantics-private"), |
||||
|
"native_private_dir": os.path.join(native_dir, "readlink-semantics-private"), |
||||
|
} |
||||
|
paths["merge_private_link"] = os.path.join(paths["merge_private_dir"], "link") |
||||
|
paths["native_private_link"] = os.path.join(paths["native_private_dir"], "link") |
||||
|
|
||||
|
for p in ( |
||||
|
paths["merge_private_link"], |
||||
|
paths["merge_link"], |
||||
|
paths["merge_regular"], |
||||
|
paths["merge_notdir"], |
||||
|
paths["merge_loop"], |
||||
|
): |
||||
|
try: |
||||
|
os.unlink(p) |
||||
|
except FileNotFoundError: |
||||
|
pass |
||||
|
|
||||
|
try: |
||||
|
os.rmdir(paths["merge_private_dir"]) |
||||
|
except FileNotFoundError: |
||||
|
pass |
||||
|
except OSError: |
||||
|
pass |
||||
|
|
||||
|
os.symlink(target, paths["merge_link"]) |
||||
|
os.symlink(target, paths["native_link"]) |
||||
|
|
||||
|
with open(paths["merge_regular"], "w", encoding="ascii"): |
||||
|
pass |
||||
|
with open(paths["native_regular"], "w", encoding="ascii"): |
||||
|
pass |
||||
|
|
||||
|
with open(paths["merge_notdir"], "w", encoding="ascii"): |
||||
|
pass |
||||
|
with open(paths["native_notdir"], "w", encoding="ascii"): |
||||
|
pass |
||||
|
|
||||
|
os.symlink("readlink-semantics-loop", paths["merge_loop"]) |
||||
|
os.symlink("readlink-semantics-loop", paths["native_loop"]) |
||||
|
|
||||
|
os.makedirs(paths["merge_private_dir"], exist_ok=True) |
||||
|
os.makedirs(paths["native_private_dir"], exist_ok=True) |
||||
|
os.symlink(target, paths["merge_private_link"]) |
||||
|
os.symlink(target, paths["native_private_link"]) |
||||
|
|
||||
|
try: |
||||
|
cases = [] |
||||
|
|
||||
|
for bufsiz in (0, 1, 2, 5, 8, len(target), len(target) + 1): |
||||
|
cases.append(( |
||||
|
f"success/truncation bufsiz={bufsiz}", |
||||
|
paths["merge_link"], |
||||
|
paths["native_link"], |
||||
|
bufsiz, |
||||
|
errno.EINVAL if bufsiz == 0 else None, |
||||
|
)) |
||||
|
|
||||
|
cases.extend([ |
||||
|
( |
||||
|
"EINVAL non-symlink", |
||||
|
paths["merge_regular"], |
||||
|
paths["native_regular"], |
||||
|
128, |
||||
|
errno.EINVAL, |
||||
|
), |
||||
|
( |
||||
|
"ENOENT missing path", |
||||
|
os.path.join(mount, "readlink-semantics-missing"), |
||||
|
os.path.join(native_dir, "readlink-semantics-missing"), |
||||
|
128, |
||||
|
errno.ENOENT, |
||||
|
), |
||||
|
( |
||||
|
"ENOTDIR non-directory prefix", |
||||
|
os.path.join(paths["merge_notdir"], "child"), |
||||
|
os.path.join(paths["native_notdir"], "child"), |
||||
|
128, |
||||
|
errno.ENOTDIR, |
||||
|
), |
||||
|
( |
||||
|
"ELOOP prefix symlink loop", |
||||
|
os.path.join(paths["merge_loop"], "child"), |
||||
|
os.path.join(paths["native_loop"], "child"), |
||||
|
128, |
||||
|
errno.ELOOP, |
||||
|
), |
||||
|
( |
||||
|
"ENAMETOOLONG long pathname", |
||||
|
os.path.join(mount, "a" * 8192), |
||||
|
os.path.join(native_dir, "a" * 8192), |
||||
|
128, |
||||
|
errno.ENAMETOOLONG, |
||||
|
), |
||||
|
]) |
||||
|
|
||||
|
os.chmod(paths["merge_private_dir"], 0o600) |
||||
|
os.chmod(paths["native_private_dir"], 0o600) |
||||
|
cases.append(( |
||||
|
"EACCES unreadable path prefix", |
||||
|
paths["merge_private_link"], |
||||
|
paths["native_private_link"], |
||||
|
128, |
||||
|
errno.EACCES if os.geteuid() != 0 else None, |
||||
|
)) |
||||
|
|
||||
|
for case in cases: |
||||
|
err = compare_case(*case) |
||||
|
if err is not None: |
||||
|
print(err, end="") |
||||
|
return 1 |
||||
|
|
||||
|
# Explicitly guard the prior regression. |
||||
|
rv, err, _ = readlink_raw(paths["merge_link"], 1) |
||||
|
if rv != 1 or err != 0: |
||||
|
print(f"bufsiz=1 expected rv=1 errno=0, got rv={rv} errno={err}", end="") |
||||
|
return 1 |
||||
|
|
||||
|
return 0 |
||||
|
finally: |
||||
|
try: |
||||
|
os.chmod(paths["merge_private_dir"], 0o700) |
||||
|
except (FileNotFoundError, PermissionError): |
||||
|
pass |
||||
|
|
||||
|
try: |
||||
|
os.chmod(paths["native_private_dir"], 0o700) |
||||
|
except (FileNotFoundError, PermissionError): |
||||
|
pass |
||||
|
|
||||
|
for p in ( |
||||
|
paths["merge_private_link"], |
||||
|
paths["native_private_link"], |
||||
|
paths["merge_link"], |
||||
|
paths["native_link"], |
||||
|
paths["merge_regular"], |
||||
|
paths["native_regular"], |
||||
|
paths["merge_notdir"], |
||||
|
paths["native_notdir"], |
||||
|
paths["merge_loop"], |
||||
|
paths["native_loop"], |
||||
|
): |
||||
|
try: |
||||
|
os.unlink(p) |
||||
|
except FileNotFoundError: |
||||
|
pass |
||||
|
|
||||
|
for p in (paths["merge_private_dir"], paths["native_private_dir"]): |
||||
|
try: |
||||
|
os.rmdir(p) |
||||
|
except FileNotFoundError: |
||||
|
pass |
||||
|
except OSError: |
||||
|
pass |
||||
|
|
||||
|
|
||||
|
if __name__ == "__main__": |
||||
|
raise SystemExit(main()) |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue