diff --git a/os/StarryOS/kernel/src/file/fs.rs b/os/StarryOS/kernel/src/file/fs.rs index 0aaae4261c..695f9a4ab9 100644 --- a/os/StarryOS/kernel/src/file/fs.rs +++ b/os/StarryOS/kernel/src/file/fs.rs @@ -63,7 +63,11 @@ pub fn resolve_at(dirfd: c_int, path: Option<&str>, flags: u32) -> AxResult() { - ResolveAtResult::File(file.inner().backend()?.location().clone()) + // Use location() directly: backend() rejects PATH-only fds + // (BadFileDescriptor) which would break fstat(O_PATH-fd). + // man "O_PATH": fstat(2) is in the allowed-operations list. + // Fixes bug-open-path-fstat-ebadf. + ResolveAtResult::File(file.inner().location().clone()) } else if let Some(dir) = f.downcast_ref::() { ResolveAtResult::File(dir.inner().clone()) } else { diff --git a/os/StarryOS/kernel/src/syscall/fs/fd_ops.rs b/os/StarryOS/kernel/src/syscall/fs/fd_ops.rs index 8d93aed7bb..fef70661a6 100644 --- a/os/StarryOS/kernel/src/syscall/fs/fd_ops.rs +++ b/os/StarryOS/kernel/src/syscall/fs/fd_ops.rs @@ -41,9 +41,6 @@ fn flags_to_options(flags: c_int, mode: __kernel_mode_t, (uid, gid): (u32, u32)) if flags & O_CREAT != 0 { options.create(true); } - if flags & O_PATH != 0 { - options.path(true); - } // O_EXCL only makes sense with O_CREAT (POSIX). Without O_CREAT, Linux // ignores O_EXCL for existing files — busybox blkdiscard opens block // devices with O_RDWR|O_EXCL (no O_CREAT). @@ -59,6 +56,21 @@ fn flags_to_options(flags: c_int, mode: __kernel_mode_t, (uid, gid): (u32, u32)) if flags & O_DIRECT != 0 { options.direct(true); } + // O_PATH must be applied LAST and override conflicting flags: man 2 open + // "When O_PATH is specified in flags, flag bits other than O_CLOEXEC, + // O_DIRECTORY, and O_NOFOLLOW are ignored." + // Fixes bug-open-path-honors-excl / -path-creat-creates / + // -path-dir-write-eisdir / -path-sym-write-enoent. + if flags & O_PATH != 0 { + options.path(true); + // O_PATH: drop access mode (no I/O), drop create/excl/trunc/append + options.read(true).write(false); + options + .create(false) + .create_new(false) + .truncate(false) + .append(false); + } options } @@ -140,6 +152,51 @@ pub fn sys_openat( let path = vm_load_string(path)?; debug!("sys_openat <= {dirfd} {path:?} {flags:#o} {mode:#o}"); + let uflags = flags as u32; + + // Pathname length check — man "ENAMETOOLONG — pathname was too long". + // starry path layer only checks per-component (255); add whole-path + // check (PATH_MAX = 4096). Fixes bug-open-pathmax-no-enametoolong. + if path.len() >= 4096 { + return Err(AxError::NameTooLong); + } + + // Empty pathname → ENOENT. openat() does not accept AT_EMPTY_PATH. + // Fixes bug-openat-empty-path-no-enoent. + if path.is_empty() { + return Err(AxError::NotFound); + } + + // O_CREAT|O_DIRECTORY is an invalid combination: open() cannot create + // a directory. man: "EINVAL — flags contains an invalid value." + // Fixes bug-open-creat-directory-einval. + // Exception: O_PATH ignores O_CREAT, so still allow PATH|CREAT|DIRECTORY. + if uflags & O_CREAT != 0 && uflags & O_DIRECTORY != 0 && uflags & O_PATH == 0 { + return Err(AxError::InvalidInput); + } + + // O_TMPFILE requires O_RDWR or O_WRONLY. man: "EINVAL — O_TMPFILE was + // specified in flags, but neither O_WRONLY nor O_RDWR was specified." + // Fixes bug-open-tmpfile-no-einval. + // + // Exception: O_PATH ignores all flags except O_CLOEXEC/O_DIRECTORY/O_NOFOLLOW + // (see flags_to_options PATH override below). On Linux, O_PATH|O_TMPFILE|RDONLY + // is accepted as an O_PATH handle (TMPFILE/access bits ignored), not EINVAL. + if uflags & O_TMPFILE == O_TMPFILE && uflags & 0b11 == O_RDONLY && uflags & O_PATH == 0 { + return Err(AxError::InvalidInput); + } + + // Absolute path: man "If pathname is absolute, then dirfd is ignored." + // starry with_fs() unconditionally calls Directory::from_fd(dirfd), + // so invalid dirfd would EBADF before the abs-path shortcut applies. + // Same pattern as resolve_at (PR #605) / sys_fchownat (PR #588). + // Fixes bug-openat-abs-path-honors-invalid-dirfd. + let dirfd = if path.starts_with('/') { + AT_FDCWD as _ + } else { + dirfd + }; + let mode = mode & !current().as_thread().proc_data.umask(); let cred = current().as_thread().cred(); diff --git a/os/arceos/modules/axfs-ng/src/highlevel/file.rs b/os/arceos/modules/axfs-ng/src/highlevel/file.rs index a24dfd3aea..7ac507bb07 100644 --- a/os/arceos/modules/axfs-ng/src/highlevel/file.rs +++ b/os/arceos/modules/axfs-ng/src/highlevel/file.rs @@ -200,6 +200,15 @@ impl OpenOptions { fn _open(&self, loc: Location) -> VfsResult { let flags = self.to_flags()?; + // O_CREAT on an existing directory → EISDIR (Linux behavior; + // CREAT carries an implicit "create regular file" intent that + // conflicts with an existing directory regardless of access mode). + // Fixes bug-open-creat-on-existing-dir-no-eisdir. + // O_PATH path bypasses this — it doesn't actually open / mutate. + if self.create && loc.is_dir() && !self.path { + return Err(VfsError::IsADirectory); + } + if self.directory { loc.check_is_dir()?; } @@ -246,6 +255,15 @@ impl OpenOptions { return Err(VfsError::InvalidInput); } + // Empty pathname → NotFound. man "ENOENT — O_CREAT is not set and + // the named file does not exist." resolve_parent("") would otherwise + // return cwd itself which lets open() succeed — wrong per POSIX. + // openat() does not accept AT_EMPTY_PATH; only specific *at calls do. + // Fixes bug-openat-empty-path-no-enoent. + if path.as_ref().as_str().is_empty() { + return Err(VfsError::NotFound); + } + let loc = match context.resolve_parent(path.as_ref()) { Ok((parent, name)) => { let mut loc = parent.open_file( @@ -262,6 +280,13 @@ impl OpenOptions { loc = context .with_current_dir(parent)? .try_resolve_symlink(loc, &mut 0)?; + } else if loc.node_type() == NodeType::Symlink && !self.path { + // O_NOFOLLOW + basename is a symlink + not O_PATH: + // man "If the trailing component (i.e., basename) of + // pathname is a symbolic link, then the open fails, + // with the error ELOOP." + // Fixes bug-open-nofollow-sym. + return Err(VfsError::FilesystemLoop); } loc } @@ -275,12 +300,18 @@ impl OpenOptions { } pub(crate) fn to_flags(&self) -> VfsResult { + // Linux semantic: O_APPEND only adds APPEND bit; it does NOT promote + // read-only fd to read/write. (Previous code merged (true,_,true) → + // READ|WRITE|APPEND which silently upgraded RDONLY|APPEND to RW — + // see bug-open-rdonly-append-promotes-rw.) Ok(match (self.read, self.write, self.append) { (true, false, false) => FileFlags::READ, (false, true, false) => FileFlags::WRITE, (true, true, false) => FileFlags::READ | FileFlags::WRITE, - (false, _, true) => FileFlags::WRITE | FileFlags::APPEND, - (true, _, true) => FileFlags::READ | FileFlags::WRITE | FileFlags::APPEND, + (true, false, true) => FileFlags::READ | FileFlags::APPEND, + (false, true, true) => FileFlags::WRITE | FileFlags::APPEND, + (true, true, true) => FileFlags::READ | FileFlags::WRITE | FileFlags::APPEND, + (false, false, true) => FileFlags::APPEND, // RDONLY-equivalent + APPEND: pure status (false, false, false) => return Err(VfsError::InvalidInput), } | if self.path { FileFlags::PATH @@ -293,19 +324,12 @@ impl OpenOptions { if !self.read && !self.write && !self.append { return false; } - match (self.write, self.append) { - (true, false) => {} - (false, false) => { - if self.truncate { - return false; - } - } - (_, true) => { - if self.truncate && !self.create_new { - return false; - } - } - } + // Linux multi-fs: RDONLY|TRUNC truncates the file (POSIX VERSIONS + // says effect is "unspecified", but most fs do truncate). Don't + // reject. Fixes bug-open-rdonly-trunc-einval. + // RDWR|APPEND|TRUNC is also explicitly allowed by Linux; the prior + // restriction "(_,true) && truncate && !create_new → false" was too + // strict. Fixes bug-open-append-trunc-einval. true } } @@ -903,15 +927,18 @@ pub struct File { impl File { /// Creates a new [`File`] from a [`FileBackend`] and access flags. pub fn new(inner: FileBackend, flags: FileFlags) -> Self { + // man 2 open: "The file offset is set to the beginning of the file" + // — initial position is always 0, regardless of O_APPEND. + // O_APPEND only relocates the offset BEFORE EACH WRITE (handled in + // `write()` via the `access(FileFlags::APPEND)` branch). Setting + // initial position to EOF would break read() on RDONLY|APPEND + // (read sees EOF immediately) — see bug-open-rdonly-append-promotes-rw. let position = if inner.location().flags().contains(NodeFlags::STREAM) { None } else { - Some(Mutex::new(if flags.contains(FileFlags::APPEND) { - inner.location().len().unwrap_or_default() - } else { - 0 - })) + Some(Mutex::new(0)) }; + let _ = flags; Self { inner, flags: AtomicU8::new(flags.bits()), @@ -1028,6 +1055,11 @@ impl File { { self.access_flags.fetch_or(3, Ordering::AcqRel); } + // WRITE bit is mandatory for any write path, regardless of whether + // APPEND is set. Otherwise O_RDONLY|O_APPEND fd would silently + // succeed writes (since access(APPEND) only checks the APPEND bit). + // Fixes bug-open-rdonly-append-promotes-rw (the part inside axfs). + self.access(FileFlags::WRITE)?; if let Some(pos) = self.position.as_ref() { let mut pos = pos.lock(); if let Ok(f) = self.access(FileFlags::APPEND) { diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-append-trunc-einval/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-append-trunc-einval/c/CMakeLists.txt new file mode 100644 index 0000000000..f10bfb358f --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-append-trunc-einval/c/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.20) +project(bug-open-append-trunc-einval C) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS ON) +add_executable(bug-open-append-trunc-einval src/main.c) +target_compile_options(bug-open-append-trunc-einval PRIVATE -Wall -Wextra -Werror) +install(TARGETS bug-open-append-trunc-einval RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-append-trunc-einval/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-append-trunc-einval/c/src/main.c new file mode 100644 index 0000000000..b708868550 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-append-trunc-einval/c/src/main.c @@ -0,0 +1,47 @@ +/* + * bug-open-append-trunc-einval: O_RDWR|O_APPEND|O_TRUNC on existing file + * must succeed (truncate then append). + * + * Linux behavior: O_RDWR + O_APPEND + O_TRUNC is a valid combination. open + * succeeds; the file is truncated to 0; subsequent writes append. + * StarryOS bug: axfs-ng/highlevel/file.rs:OpenOptions::is_valid() rejects + * `(_, true) => if self.truncate && !self.create_new { return false; }` + * when O_APPEND is set with O_TRUNC and CREAT_NEW isn't — returns -1 EINVAL. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +int main(void) +{ + const char *file = "/tmp/bug_append_trunc"; + unlink(file); + int fd0 = open(file, O_CREAT | O_WRONLY, 0644); + if (fd0 < 0) { perror("setup"); return 1; } + write(fd0, "hello", 5); + close(fd0); + + errno = 0; + int fd = open(file, O_RDWR | O_APPEND | O_TRUNC); + int ok = 0; + if (fd >= 0) { + struct stat st; + if (stat(file, &st) == 0 && st.st_size == 0) { + printf("PASS: open(O_RDWR|O_APPEND|O_TRUNC) -> fd=%d, size=0\n", fd); + ok = 1; + } else { + printf("FAIL: opened ok but size=%ld (expected 0)\n", (long)st.st_size); + } + close(fd); + } else { + printf("FAIL: open(O_RDWR|O_APPEND|O_TRUNC) -> -1 errno=%d (%s); expected fd>=0\n", + errno, strerror(errno)); + } + + unlink(file); + return ok ? 0 : 1; +} diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-creat-directory-einval/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-creat-directory-einval/c/CMakeLists.txt new file mode 100644 index 0000000000..d25d9afe57 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-creat-directory-einval/c/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.20) +project(bug-open-creat-directory-einval C) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS ON) +add_executable(bug-open-creat-directory-einval src/main.c) +target_compile_options(bug-open-creat-directory-einval PRIVATE -Wall -Wextra -Werror) +install(TARGETS bug-open-creat-directory-einval RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-creat-directory-einval/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-creat-directory-einval/c/src/main.c new file mode 100644 index 0000000000..04c0080184 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-creat-directory-einval/c/src/main.c @@ -0,0 +1,64 @@ +/* + * bug-open-creat-directory-einval: O_CREAT|O_DIRECTORY must fail with EINVAL. + * + * man 2 open implies (and Linux enforces): you cannot create a directory with + * open(); O_CREAT|O_DIRECTORY is rejected up front with EINVAL. + * + * Linux behavior: open(, O_CREAT|O_DIRECTORY) → -1 EINVAL + * StarryOS bug: doesn't reject the combo; either returns a valid fd (for + * existing directories) or lets path-resolution errors fire (ENOTDIR for + * regular files, EEXIST when O_EXCL also set on existing path). + * + * Root cause: starry's flags_to_options (kernel/src/syscall/fs/fd_ops.rs:26-63) + * doesn't validate that CREAT and DIRECTORY are mutually exclusive. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +static int probe(const char *path, const char *desc) +{ + errno = 0; + int fd = open(path, O_RDONLY | O_CREAT | O_DIRECTORY, 0644); + int ok = (fd == -1 && errno == EINVAL); + if (ok) { + printf("PASS: %s -> -1 EINVAL\n", desc); + } else { + printf("FAIL: %s -> fd=%d errno=%d (%s); expected -1 EINVAL\n", + desc, fd, errno, strerror(errno)); + } + if (fd >= 0) close(fd); + return ok; +} + +int main(void) +{ + int ok = 1; + + /* against existing regular file */ + const char *file = "/tmp/bug_creat_dir_file"; + unlink(file); + int fd = open(file, O_CREAT | O_WRONLY, 0644); + if (fd >= 0) close(fd); + ok &= probe(file, "existing regular file + O_CREAT|O_DIRECTORY"); + unlink(file); + + /* against existing directory */ + const char *dir = "/tmp/bug_creat_dir_dir"; + rmdir(dir); + mkdir(dir, 0755); + ok &= probe(dir, "existing directory + O_CREAT|O_DIRECTORY"); + rmdir(dir); + + /* against absent path */ + const char *absent = "/tmp/bug_creat_dir_absent_xyz"; + unlink(absent); + ok &= probe(absent, "absent path + O_CREAT|O_DIRECTORY"); + unlink(absent); + + return ok ? 0 : 1; +} diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-creat-on-existing-dir-no-eisdir/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-creat-on-existing-dir-no-eisdir/c/CMakeLists.txt new file mode 100644 index 0000000000..79208e5e96 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-creat-on-existing-dir-no-eisdir/c/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.20) +project(bug-open-creat-on-existing-dir-no-eisdir C) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS ON) +add_executable(bug-open-creat-on-existing-dir-no-eisdir src/main.c) +target_compile_options(bug-open-creat-on-existing-dir-no-eisdir PRIVATE -Wall -Wextra -Werror) +install(TARGETS bug-open-creat-on-existing-dir-no-eisdir RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-creat-on-existing-dir-no-eisdir/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-creat-on-existing-dir-no-eisdir/c/src/main.c new file mode 100644 index 0000000000..c494f769dd --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-creat-on-existing-dir-no-eisdir/c/src/main.c @@ -0,0 +1,41 @@ +/* + * bug-open-creat-on-existing-dir-no-eisdir: open(existing_dir, O_CREAT|O_RDONLY) + * must fail with EISDIR. + * + * Linux behavior: O_CREAT on a path that already names a directory is rejected + * with EISDIR (regardless of access mode), because open(2) cannot create + * directories — and an existing directory cannot be re-opened with CREAT + * intent. + * + * StarryOS bug: returns a valid fd to the directory, ignoring the O_CREAT-vs- + * directory conflict. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +int main(void) +{ + const char *dir = "/tmp/bug_creat_existing_dir"; + rmdir(dir); + if (mkdir(dir, 0755) != 0) { perror("setup mkdir"); return 1; } + + errno = 0; + int fd = open(dir, O_CREAT | O_RDONLY, 0644); + int ok = (fd == -1 && errno == EISDIR); + + if (ok) { + printf("PASS: open(existing_dir, O_CREAT|O_RDONLY) -> -1 EISDIR\n"); + } else { + printf("FAIL: expected -1 EISDIR, got fd=%d errno=%d (%s)\n", + fd, errno, strerror(errno)); + } + if (fd >= 0) close(fd); + + rmdir(dir); + return ok ? 0 : 1; +} diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-nofollow-sym/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-nofollow-sym/c/CMakeLists.txt new file mode 100644 index 0000000000..4951b74a12 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-nofollow-sym/c/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.20) +project(bug-open-nofollow-sym C) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS ON) +add_executable(bug-open-nofollow-sym src/main.c) +target_compile_options(bug-open-nofollow-sym PRIVATE -Wall -Wextra -Werror) +install(TARGETS bug-open-nofollow-sym RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-nofollow-sym/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-nofollow-sym/c/src/main.c new file mode 100644 index 0000000000..cab0d1539f --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-nofollow-sym/c/src/main.c @@ -0,0 +1,57 @@ +/* + * bug-open-nofollow-sym: O_NOFOLLOW + symlink basename should fail with ELOOP. + * + * man 2 open §"O_NOFOLLOW": + * "If the trailing component (i.e., basename) of pathname is a symbolic link, + * then the open fails, with the error ELOOP." + * + * Linux behavior: open(symlink_to_file, O_RDONLY|O_NOFOLLOW) returns -1, errno=ELOOP + * StarryOS bug: returns a valid fd to the symlink target. + * + * Root cause (from source reading): + * axfs-ng/highlevel/file.rs:OpenOptions::open() at `if !self.no_follow` + * only resolves symlinks when no_follow is false. When no_follow=true, it + * skips try_resolve_symlink and returns the raw location — but never + * explicitly checks "if loc is symlink → ELOOP". So basename symlinks slip + * through and get opened as the symlink (which to the file layer looks like + * a regular file, since the kernel reads the link target's data). + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +int main(void) +{ + const char *file = "/tmp/bug_open_nofollow_target"; + const char *sym = "/tmp/bug_open_nofollow_sym"; + + /* setup: regular file + symlink pointing at it */ + unlink(sym); + unlink(file); + int fd0 = open(file, O_CREAT | O_WRONLY, 0644); + if (fd0 < 0) { perror("setup open"); return 1; } + write(fd0, "x", 1); + close(fd0); + if (symlink(file, sym) != 0) { perror("setup symlink"); return 1; } + + /* the actual probe */ + errno = 0; + int fd = open(sym, O_RDONLY | O_NOFOLLOW); + + int ok = (fd == -1 && errno == ELOOP); + if (ok) { + printf("PASS: open(symlink, O_RDONLY|O_NOFOLLOW) -> -1 ELOOP as expected\n"); + } else { + printf("FAIL: expected -1 ELOOP, got fd=%d errno=%d (%s)\n", + fd, errno, strerror(errno)); + } + if (fd >= 0) close(fd); + + unlink(sym); + unlink(file); + return ok ? 0 : 1; +} diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-path-creat-creates/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-path-creat-creates/c/CMakeLists.txt new file mode 100644 index 0000000000..be356f5922 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-path-creat-creates/c/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.20) +project(bug-open-path-creat-creates C) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS ON) +add_executable(bug-open-path-creat-creates src/main.c) +target_compile_options(bug-open-path-creat-creates PRIVATE -Wall -Wextra -Werror) +install(TARGETS bug-open-path-creat-creates RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-path-creat-creates/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-path-creat-creates/c/src/main.c new file mode 100644 index 0000000000..40326fa041 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-path-creat-creates/c/src/main.c @@ -0,0 +1,46 @@ +/* + * bug-open-path-creat-creates: O_PATH does not create files (O_CREAT ignored). + * + * man 2 open §"O_PATH": + * "When O_PATH is specified in flags, flag bits other than O_CLOEXEC, + * O_DIRECTORY, and O_NOFOLLOW are ignored." + * + * Therefore O_PATH|O_CREAT on an absent path should NOT create the file — + * since O_CREAT is one of the ignored flags. Linux: returns -1 ENOENT + * (path doesn't exist, can't open). + * + * StarryOS bug: returns a valid fd AND creates a real file at the path. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +int main(void) +{ + const char *path = "/tmp/bug_path_creat_creates_xyz"; + unlink(path); + + errno = 0; + int fd = open(path, O_PATH | O_CREAT, 0644); + + int ok; + struct stat st; + int file_was_created = (stat(path, &st) == 0); + + if (fd == -1 && errno == ENOENT && !file_was_created) { + printf("PASS: open(absent, O_PATH|O_CREAT) -> -1 ENOENT, no file created\n"); + ok = 1; + } else { + printf("FAIL: expected -1 ENOENT (no creation), got fd=%d errno=%d (%s); file_present_after=%d\n", + fd, errno, strerror(errno), file_was_created); + ok = 0; + } + + if (fd >= 0) close(fd); + unlink(path); + return ok ? 0 : 1; +} diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-path-dir-write-eisdir/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-path-dir-write-eisdir/c/CMakeLists.txt new file mode 100644 index 0000000000..4cfb548a2e --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-path-dir-write-eisdir/c/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.20) +project(bug-open-path-dir-write-eisdir C) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS ON) +add_executable(bug-open-path-dir-write-eisdir src/main.c) +target_compile_options(bug-open-path-dir-write-eisdir PRIVATE -Wall -Wextra -Werror) +install(TARGETS bug-open-path-dir-write-eisdir RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-path-dir-write-eisdir/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-path-dir-write-eisdir/c/src/main.c new file mode 100644 index 0000000000..9c2f9fea67 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-path-dir-write-eisdir/c/src/main.c @@ -0,0 +1,53 @@ +/* + * bug-open-path-dir-write-eisdir: O_PATH on a directory must succeed regardless + * of access mode (O_PATH ignores access mode). + * + * man 2 open §"O_PATH": + * "Opening a file or directory with the O_PATH flag requires no permissions + * on the object itself" — and the access mode is ignored for O_PATH fds + * (only the path-level operations matter). + * + * Linux behavior: open(dir, O_PATH|O_WRONLY) → fd>=0 + * StarryOS bug: returns -1 EISDIR — starry's "writing-a-dir" check fires + * before honoring O_PATH semantics. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +int main(void) +{ + const char *dir = "/tmp/bug_path_dir_write"; + rmdir(dir); + if (mkdir(dir, 0755) != 0) { perror("setup mkdir"); return 1; } + + int ok = 1; + int fd; + + fd = open(dir, O_PATH | O_WRONLY); + if (fd >= 0) { + printf("PASS: open(dir, O_PATH|O_WRONLY) -> fd=%d\n", fd); + close(fd); + } else { + printf("FAIL: open(dir, O_PATH|O_WRONLY) -> -1 errno=%d (%s); expected fd>=0\n", + errno, strerror(errno)); + ok = 0; + } + + fd = open(dir, O_PATH | O_RDWR); + if (fd >= 0) { + printf("PASS: open(dir, O_PATH|O_RDWR) -> fd=%d\n", fd); + close(fd); + } else { + printf("FAIL: open(dir, O_PATH|O_RDWR) -> -1 errno=%d (%s); expected fd>=0\n", + errno, strerror(errno)); + ok = 0; + } + + rmdir(dir); + return ok ? 0 : 1; +} diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-path-fstat-ebadf/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-path-fstat-ebadf/c/CMakeLists.txt new file mode 100644 index 0000000000..bcba7e9765 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-path-fstat-ebadf/c/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.20) +project(bug-open-path-fstat-ebadf C) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS ON) +add_executable(bug-open-path-fstat-ebadf src/main.c) +target_compile_options(bug-open-path-fstat-ebadf PRIVATE -Wall -Wextra -Werror) +install(TARGETS bug-open-path-fstat-ebadf RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-path-fstat-ebadf/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-path-fstat-ebadf/c/src/main.c new file mode 100644 index 0000000000..4110efaa0a --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-path-fstat-ebadf/c/src/main.c @@ -0,0 +1,51 @@ +/* + * bug-open-path-fstat-ebadf: fstat on an O_PATH fd must succeed. + * + * man 2 open §"O_PATH" lists fstat(2) (since Linux 3.6) as one of the + * operations explicitly allowed on O_PATH file descriptors. + * + * Linux behavior: fstat on O_PATH fd returns 0, st_size populated. + * StarryOS bug: returns -1 EBADF — over-zealous "no I/O" rejection in + * File::stat / fd_ops Path-handle wrapper. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +int main(void) +{ + const char *file = "/tmp/bug_path_fstat"; + unlink(file); + int fd0 = open(file, O_CREAT | O_WRONLY, 0644); + if (fd0 < 0) { perror("setup"); return 1; } + write(fd0, "12345", 5); + close(fd0); + + int fd = open(file, O_PATH); + if (fd < 0) { + printf("FAIL: open(file, O_PATH) -> -1 errno=%d (%s)\n", errno, strerror(errno)); + unlink(file); + return 1; + } + + struct stat st; + errno = 0; + int rc = fstat(fd, &st); + int ok; + if (rc == 0 && st.st_size == 5) { + printf("PASS: fstat(O_PATH fd) -> 0 with st_size=5\n"); + ok = 1; + } else { + printf("FAIL: fstat returned %d errno=%d (%s) st_size=%ld; expected 0 with size=5\n", + rc, errno, strerror(errno), (long)st.st_size); + ok = 0; + } + + close(fd); + unlink(file); + return ok ? 0 : 1; +} diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-path-honors-excl/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-path-honors-excl/c/CMakeLists.txt new file mode 100644 index 0000000000..6d6642eed1 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-path-honors-excl/c/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.20) +project(bug-open-path-honors-excl C) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS ON) +add_executable(bug-open-path-honors-excl src/main.c) +target_compile_options(bug-open-path-honors-excl PRIVATE -Wall -Wextra -Werror) +install(TARGETS bug-open-path-honors-excl RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-path-honors-excl/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-path-honors-excl/c/src/main.c new file mode 100644 index 0000000000..e29b9a9f31 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-path-honors-excl/c/src/main.c @@ -0,0 +1,45 @@ +/* + * bug-open-path-honors-excl: O_PATH should ignore O_CREAT/O_EXCL. + * + * man 2 open §"O_PATH": + * "When O_PATH is specified in flags, flag bits other than O_CLOEXEC, + * O_DIRECTORY, and O_NOFOLLOW are ignored." + * + * Linux behavior: open(existing_file, O_PATH|O_CREAT|O_EXCL) returns valid fd. + * StarryOS bug: returns -1 EEXIST (CREAT|EXCL still honored even when PATH set). + * + * Root cause: flags_to_options (fd_ops.rs:26-63) sets create/create_new from + * O_CREAT/O_EXCL bits unconditionally; the O_PATH branch is only set later + * via options.path(true) but doesn't clear/override the create flags. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +int main(void) +{ + const char *file = "/tmp/bug_path_excl_file"; + unlink(file); + int fd0 = open(file, O_CREAT | O_WRONLY, 0644); + if (fd0 < 0) { perror("setup"); return 1; } + write(fd0, "x", 1); + close(fd0); + + errno = 0; + int fd = open(file, O_PATH | O_CREAT | O_EXCL, 0644); + int ok = (fd >= 0); + if (ok) { + printf("PASS: open(existing, O_PATH|O_CREAT|O_EXCL) -> fd=%d (PATH ignored CREAT/EXCL)\n", fd); + close(fd); + } else { + printf("FAIL: expected fd>=0 (PATH ignores CREAT/EXCL), got fd=-1 errno=%d (%s)\n", + errno, strerror(errno)); + } + + unlink(file); + return ok ? 0 : 1; +} diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-path-sym-write-enoent/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-path-sym-write-enoent/c/CMakeLists.txt new file mode 100644 index 0000000000..2b18e7c80b --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-path-sym-write-enoent/c/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.20) +project(bug-open-path-sym-write-enoent C) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS ON) +add_executable(bug-open-path-sym-write-enoent src/main.c) +target_compile_options(bug-open-path-sym-write-enoent PRIVATE -Wall -Wextra -Werror) +install(TARGETS bug-open-path-sym-write-enoent RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-path-sym-write-enoent/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-path-sym-write-enoent/c/src/main.c new file mode 100644 index 0000000000..3c80c5fa64 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-path-sym-write-enoent/c/src/main.c @@ -0,0 +1,50 @@ +/* + * bug-open-path-sym-write-enoent: O_PATH on a symlink-to-file should follow + * the symlink and open the target (PATH ignores access mode). + * + * man 2 open §"O_PATH": + * "[O_PATH] Obtain a file descriptor that can be used for two purposes... + * flag bits other than O_CLOEXEC, O_DIRECTORY, and O_NOFOLLOW are ignored." + * + * Linux behavior: open(symlink_to_existing_file, O_PATH|O_WRONLY) → fd>=0, + * pointing at the target file (symlink followed; access mode ignored). + * StarryOS bug: returns -1 ENOENT — starry's path/access handling rejects the + * combo before honoring O_PATH. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +int main(void) +{ + const char *file = "/tmp/bug_path_sym_target"; + const char *sym = "/tmp/bug_path_sym"; + unlink(sym); unlink(file); + int fd0 = open(file, O_CREAT | O_WRONLY, 0644); + if (fd0 < 0) { perror("setup file"); return 1; } + write(fd0, "x", 1); + close(fd0); + if (symlink(file, sym) != 0) { perror("setup symlink"); return 1; } + + errno = 0; + int fd = open(sym, O_PATH | O_WRONLY); + + int ok; + if (fd >= 0) { + printf("PASS: open(sym_to_file, O_PATH|O_WRONLY) -> fd=%d\n", fd); + close(fd); + ok = 1; + } else { + printf("FAIL: expected fd>=0, got -1 errno=%d (%s)\n", + errno, strerror(errno)); + ok = 0; + } + + unlink(sym); + unlink(file); + return ok ? 0 : 1; +} diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-pathmax-no-enametoolong/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-pathmax-no-enametoolong/c/CMakeLists.txt new file mode 100644 index 0000000000..546ad557ea --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-pathmax-no-enametoolong/c/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.20) +project(bug-open-pathmax-no-enametoolong C) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS ON) +add_executable(bug-open-pathmax-no-enametoolong src/main.c) +target_compile_options(bug-open-pathmax-no-enametoolong PRIVATE -Wall -Wextra -Werror) +install(TARGETS bug-open-pathmax-no-enametoolong RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-pathmax-no-enametoolong/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-pathmax-no-enametoolong/c/src/main.c new file mode 100644 index 0000000000..c374fcc6d5 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-pathmax-no-enametoolong/c/src/main.c @@ -0,0 +1,44 @@ +/* + * bug-open-pathmax-no-enametoolong: pathname > PATH_MAX must ENAMETOOLONG. + * + * man 2 open §"ENAMETOOLONG": "pathname was too long." + * Linux: open() rejects path lengths > PATH_MAX (4096) with -1 ENAMETOOLONG. + * + * StarryOS bug: only checks per-component length (NAME_MAX 255 in axfs-ng-vfs + * path.rs). Multi-segment paths totaling > PATH_MAX walk through fine, + * eventually hitting "component doesn't exist" → ENOENT instead. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +int main(void) +{ + /* 拼出 ~4500 字符的多段路径(每段 50 字符 'b')*/ + char path[PATH_MAX + 256]; + snprintf(path, sizeof(path), "/tmp"); + while (strlen(path) < PATH_MAX + 100) { + size_t len = strlen(path); + if (len + 51 >= sizeof(path)) break; + path[len] = '/'; + memset(path + len + 1, 'b', 50); + path[len + 51] = '\0'; + } + + errno = 0; + int fd = open(path, O_RDONLY); + int ok = (fd == -1 && errno == ENAMETOOLONG); + if (ok) { + printf("PASS: open(path > PATH_MAX) -> -1 ENAMETOOLONG (len=%zu)\n", strlen(path)); + } else { + printf("FAIL: expected -1 ENAMETOOLONG (len=%zu), got fd=%d errno=%d (%s)\n", + strlen(path), fd, errno, strerror(errno)); + } + if (fd >= 0) close(fd); + return ok ? 0 : 1; +} diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-rdonly-append-promotes-rw/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-rdonly-append-promotes-rw/c/CMakeLists.txt new file mode 100644 index 0000000000..504da5c742 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-rdonly-append-promotes-rw/c/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.20) +project(bug-open-rdonly-append-promotes-rw C) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS ON) +add_executable(bug-open-rdonly-append-promotes-rw src/main.c) +target_compile_options(bug-open-rdonly-append-promotes-rw PRIVATE -Wall -Wextra -Werror) +install(TARGETS bug-open-rdonly-append-promotes-rw RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-rdonly-append-promotes-rw/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-rdonly-append-promotes-rw/c/src/main.c new file mode 100644 index 0000000000..580b51d15e --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-rdonly-append-promotes-rw/c/src/main.c @@ -0,0 +1,63 @@ +/* + * bug-open-rdonly-append-promotes-rw: O_RDONLY|O_APPEND must NOT make the fd + * writable. APPEND is a status flag and only affects WRITE behavior; it + * doesn't grant write access. + * + * man 2 open §"O_APPEND": + * "The file is opened in append mode. Before each write(2), the file offset + * is positioned at the end of the file..." + * — APPEND is purely about write-time offset; combined with RDONLY it's + * effectively a no-op (no writes will happen). + * + * Linux behavior: open(file, O_RDONLY|O_APPEND); write(fd,...) → -1 EBADF + * StarryOS bug: axfs-ng/highlevel/file.rs:to_flags() promotes + * (read=true, write=false, append=true) → READ|WRITE|APPEND, so the fd + * actually becomes writable. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +int main(void) +{ + const char *file = "/tmp/bug_rdonly_append"; + unlink(file); + int fd0 = open(file, O_CREAT | O_WRONLY, 0644); + if (fd0 < 0) { perror("setup"); return 1; } + write(fd0, "hi", 2); + close(fd0); + + int fd = open(file, O_RDONLY | O_APPEND); + if (fd < 0) { + printf("FAIL: open(file, O_RDONLY|O_APPEND) -> -1 errno=%d (%s); expected fd>=0\n", + errno, strerror(errno)); + unlink(file); + return 1; + } + + /* read should succeed (RDONLY) */ + char buf[8] = {0}; + ssize_t r = read(fd, buf, sizeof(buf) - 1); + int read_ok = (r == 2 && memcmp(buf, "hi", 2) == 0); + + /* write MUST fail with EBADF (RDONLY) */ + errno = 0; + ssize_t w = write(fd, "X", 1); + int write_ok = (w == -1 && errno == EBADF); + + int ok = read_ok && write_ok; + if (ok) { + printf("PASS: O_RDONLY|O_APPEND fd is read-only (read works, write -> EBADF)\n"); + } else { + printf("FAIL: read=%zd (want 2 + content 'hi') write=%zd errno=%d (%s) (want -1 EBADF)\n", + r, w, errno, strerror(errno)); + } + + close(fd); + unlink(file); + return ok ? 0 : 1; +} diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-rdonly-trunc-einval/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-rdonly-trunc-einval/c/CMakeLists.txt new file mode 100644 index 0000000000..f29491ee2f --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-rdonly-trunc-einval/c/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.20) +project(bug-open-rdonly-trunc-einval C) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS ON) +add_executable(bug-open-rdonly-trunc-einval src/main.c) +target_compile_options(bug-open-rdonly-trunc-einval PRIVATE -Wall -Wextra -Werror) +install(TARGETS bug-open-rdonly-trunc-einval RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-rdonly-trunc-einval/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-rdonly-trunc-einval/c/src/main.c new file mode 100644 index 0000000000..d99a7d7444 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-rdonly-trunc-einval/c/src/main.c @@ -0,0 +1,55 @@ +/* + * bug-open-rdonly-trunc-einval: O_RDONLY|O_TRUNC must not return EINVAL. + * + * man 2 open §"VERSIONS": + * "O_RDONLY | O_TRUNC — The (undefined) effect of O_RDONLY | O_TRUNC varies + * among implementations. On many systems the file is actually truncated." + * + * Linux multi-fs default: file IS truncated (open succeeds with fd, file becomes empty). + * StarryOS bug: OpenOptions::is_valid() (axfs-ng/highlevel/file.rs:289-307) + * rejects the combination outright → returns -1 EINVAL. + * + * This test asserts Linux behavior (truncate on success). On starry, EINVAL + * appears instead. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +int main(void) +{ + const char *file = "/tmp/bug_rdonly_trunc"; + unlink(file); + int fd0 = open(file, O_CREAT | O_WRONLY, 0644); + if (fd0 < 0) { perror("setup"); return 1; } + write(fd0, "hello", 5); + close(fd0); + + errno = 0; + int fd = open(file, O_RDONLY | O_TRUNC); + + int ok; + if (fd >= 0) { + struct stat st; + if (stat(file, &st) == 0 && st.st_size == 0) { + printf("PASS: O_RDONLY|O_TRUNC truncated file (size 0)\n"); + ok = 1; + } else { + printf("FAIL: O_RDONLY|O_TRUNC opened ok but file size=%ld (expected 0)\n", + (long)st.st_size); + ok = 0; + } + close(fd); + } else { + printf("FAIL: O_RDONLY|O_TRUNC -> -1 errno=%d (%s); expected truncate to size 0\n", + errno, strerror(errno)); + ok = 0; + } + + unlink(file); + return ok ? 0 : 1; +} diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-tmpfile-no-einval/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-tmpfile-no-einval/c/CMakeLists.txt new file mode 100644 index 0000000000..5fd33635c2 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-tmpfile-no-einval/c/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.20) +project(bug-open-tmpfile-no-einval C) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS ON) +add_executable(bug-open-tmpfile-no-einval src/main.c) +target_compile_options(bug-open-tmpfile-no-einval PRIVATE -Wall -Wextra -Werror) +install(TARGETS bug-open-tmpfile-no-einval RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-tmpfile-no-einval/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-tmpfile-no-einval/c/src/main.c new file mode 100644 index 0000000000..9b59c8ed2f --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-open-tmpfile-no-einval/c/src/main.c @@ -0,0 +1,44 @@ +/* + * bug-open-tmpfile-no-einval: O_TMPFILE without O_RDWR/O_WRONLY must EINVAL. + * + * man 2 open §"O_TMPFILE": + * "O_TMPFILE must be specified with one of O_RDWR or O_WRONLY..." + * Implication: O_TMPFILE | O_RDONLY → EINVAL. + * + * StarryOS bug: kernel `flags_to_options` does not recognize O_TMPFILE bits at + * all (silently ignored). The call falls through to opening the directory + * as RDONLY → returns valid fd. No EINVAL. + * + * Linux behavior: -1 EINVAL. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +#ifndef O_TMPFILE +#define O_TMPFILE 020000000 +#endif + +int main(void) +{ + const char *dir = "/tmp/bug_tmpfile_einval_dir"; + rmdir(dir); + if (mkdir(dir, 0755) != 0) { perror("setup"); return 1; } + + errno = 0; + int fd = open(dir, O_TMPFILE | O_RDONLY, 0644); + int ok = (fd == -1 && errno == EINVAL); + if (ok) { + printf("PASS: open(dir, O_TMPFILE|O_RDONLY) -> -1 EINVAL\n"); + } else { + printf("FAIL: expected -1 EINVAL, got fd=%d errno=%d (%s)\n", + fd, errno, strerror(errno)); + } + if (fd >= 0) close(fd); + rmdir(dir); + return ok ? 0 : 1; +} diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/bug-openat-abs-path-honors-invalid-dirfd/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-openat-abs-path-honors-invalid-dirfd/c/CMakeLists.txt new file mode 100644 index 0000000000..8866bb86b7 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-openat-abs-path-honors-invalid-dirfd/c/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.20) +project(bug-openat-abs-path-honors-invalid-dirfd C) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS ON) +add_executable(bug-openat-abs-path-honors-invalid-dirfd src/main.c) +target_compile_options(bug-openat-abs-path-honors-invalid-dirfd PRIVATE -Wall -Wextra -Werror) +install(TARGETS bug-openat-abs-path-honors-invalid-dirfd RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/bug-openat-abs-path-honors-invalid-dirfd/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-openat-abs-path-honors-invalid-dirfd/c/src/main.c new file mode 100644 index 0000000000..603422f85c --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-openat-abs-path-honors-invalid-dirfd/c/src/main.c @@ -0,0 +1,62 @@ +/* + * bug-openat-abs-path-honors-invalid-dirfd: openat with an ABSOLUTE pathname + * must ignore dirfd entirely (per man), even when dirfd is invalid. + * + * man 2 open §"openat()": + * "If pathname is absolute, then dirfd is ignored." + * + * Linux behavior: openat(-1, "/tmp/anything", O_RDONLY) → fd>=0 (dirfd ignored). + * StarryOS bug: kernel/src/file/fs.rs::with_fs unconditionally calls + * Directory::from_fd(dirfd) when dirfd != AT_FDCWD, before path is even + * inspected — so invalid dirfd fails with EBADF before the absolute-path + * shortcut can take effect. Fix template = sys_fchownat (ctl.rs:413-418, + * PR #588) or resolve_at (file/fs.rs:70-75, PR #605): + * `let dirfd = if path.starts_with('/') { AT_FDCWD } else { dirfd };` + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +int main(void) +{ + /* setup: absolute target file */ + const char *file = "/tmp/bug_openat_abs_dirfd"; + unlink(file); + int fd0 = open(file, O_CREAT | O_WRONLY, 0644); + if (fd0 < 0) { perror("setup"); return 1; } + write(fd0, "x", 1); + close(fd0); + + int ok = 1; + + /* probe 1: invalid dirfd -1 + absolute path */ + errno = 0; + int fd = openat(-1, file, O_RDONLY); + if (fd >= 0) { + printf("PASS: openat(-1, /abs/path) -> fd=%d (dirfd ignored)\n", fd); + close(fd); + } else { + printf("FAIL: openat(-1, /abs/path) -> -1 errno=%d (%s); expected fd>=0 (dirfd should be ignored)\n", + errno, strerror(errno)); + ok = 0; + } + + /* probe 2: non-existent dirfd 9999 + absolute path */ + errno = 0; + fd = openat(9999, file, O_RDONLY); + if (fd >= 0) { + printf("PASS: openat(9999, /abs/path) -> fd=%d (dirfd ignored)\n", fd); + close(fd); + } else { + printf("FAIL: openat(9999, /abs/path) -> -1 errno=%d (%s); expected fd>=0\n", + errno, strerror(errno)); + ok = 0; + } + + unlink(file); + return ok ? 0 : 1; +} diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/bug-openat-empty-path-no-enoent/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-openat-empty-path-no-enoent/c/CMakeLists.txt new file mode 100644 index 0000000000..ab7478a137 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-openat-empty-path-no-enoent/c/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.20) +project(bug-openat-empty-path-no-enoent C) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS ON) +add_executable(bug-openat-empty-path-no-enoent src/main.c) +target_compile_options(bug-openat-empty-path-no-enoent PRIVATE -Wall -Wextra -Werror) +install(TARGETS bug-openat-empty-path-no-enoent RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/bug-openat-empty-path-no-enoent/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-openat-empty-path-no-enoent/c/src/main.c new file mode 100644 index 0000000000..a4f7e46400 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-openat-empty-path-no-enoent/c/src/main.c @@ -0,0 +1,35 @@ +/* + * bug-openat-empty-path-no-enoent: openat with empty path (no AT_EMPTY_PATH) + * must ENOENT. + * + * man 2 open §ENOENT (1st variant): "O_CREAT not set and the named file + * does not exist." — empty path qualifies as "no such file." + * AT_EMPTY_PATH is not defined for openat (only some *at functions like + * fstatat). + * + * Linux behavior: openat(AT_FDCWD, "", O_RDONLY) → -1 ENOENT. + * StarryOS bug: OpenOptions::open via resolve_parent("") returns the CWD + * itself; opens it as RDONLY → returns valid fd, no ENOENT. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +int main(void) +{ + errno = 0; + int fd = openat(AT_FDCWD, "", O_RDONLY); + int ok = (fd == -1 && errno == ENOENT); + if (ok) { + printf("PASS: openat(AT_FDCWD, \"\", O_RDONLY) -> -1 ENOENT\n"); + } else { + printf("FAIL: expected -1 ENOENT, got fd=%d errno=%d (%s)\n", + fd, errno, strerror(errno)); + } + if (fd >= 0) close(fd); + return ok ? 0 : 1; +} diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-aarch64.toml b/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-aarch64.toml index 62690b41f3..7071465781 100644 --- a/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-aarch64.toml +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-aarch64.toml @@ -65,6 +65,21 @@ test_commands = [ "/usr/bin/bug-tmpfs-hardlink-cache", "/usr/bin/bug-zombie-syscalls", "/usr/bin/bug-starry-setuid-setgid-no-uidvalid", + "/usr/bin/bug-open-nofollow-sym", + "/usr/bin/bug-open-creat-directory-einval", + "/usr/bin/bug-open-path-honors-excl", + "/usr/bin/bug-open-path-dir-write-eisdir", + "/usr/bin/bug-open-path-creat-creates", + "/usr/bin/bug-open-path-sym-write-enoent", + "/usr/bin/bug-open-rdonly-trunc-einval", + "/usr/bin/bug-open-rdonly-append-promotes-rw", + "/usr/bin/bug-open-creat-on-existing-dir-no-eisdir", + "/usr/bin/bug-open-append-trunc-einval", + "/usr/bin/bug-open-tmpfile-no-einval", + "/usr/bin/bug-open-pathmax-no-enametoolong", + "/usr/bin/bug-openat-empty-path-no-enoent", + "/usr/bin/bug-openat-abs-path-honors-invalid-dirfd", + "/usr/bin/bug-open-path-fstat-ebadf", ] success_regex = ["(?m)^STARRY_GROUPED_TESTS_PASSED\\s*$"] fail_regex = ['(?i)\bpanic(?:ked)?\b', '(?m)^STARRY_GROUPED_TEST_FAILED:'] diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-loongarch64.toml b/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-loongarch64.toml index d4922c1313..558e971207 100644 --- a/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-loongarch64.toml +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-loongarch64.toml @@ -66,6 +66,21 @@ test_commands = [ "/usr/bin/bug-tmpfs-hardlink-cache", "/usr/bin/bug-zombie-syscalls", "/usr/bin/bug-starry-setuid-setgid-no-uidvalid", + "/usr/bin/bug-open-nofollow-sym", + "/usr/bin/bug-open-creat-directory-einval", + "/usr/bin/bug-open-path-honors-excl", + "/usr/bin/bug-open-path-dir-write-eisdir", + "/usr/bin/bug-open-path-creat-creates", + "/usr/bin/bug-open-path-sym-write-enoent", + "/usr/bin/bug-open-rdonly-trunc-einval", + "/usr/bin/bug-open-rdonly-append-promotes-rw", + "/usr/bin/bug-open-creat-on-existing-dir-no-eisdir", + "/usr/bin/bug-open-append-trunc-einval", + "/usr/bin/bug-open-tmpfile-no-einval", + "/usr/bin/bug-open-pathmax-no-enametoolong", + "/usr/bin/bug-openat-empty-path-no-enoent", + "/usr/bin/bug-openat-abs-path-honors-invalid-dirfd", + "/usr/bin/bug-open-path-fstat-ebadf", ] success_regex = ["(?m)^STARRY_GROUPED_TESTS_PASSED\\s*$"] fail_regex = ['(?i)\bpanic(?:ked)?\b', '(?m)^STARRY_GROUPED_TEST_FAILED:'] diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-riscv64.toml b/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-riscv64.toml index 51e9721675..ecc692df2a 100644 --- a/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-riscv64.toml +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-riscv64.toml @@ -73,6 +73,21 @@ test_commands = [ "/usr/bin/bug-tmpfs-hardlink-cache", "/usr/bin/bug-zombie-syscalls", "/usr/bin/bug-starry-setuid-setgid-no-uidvalid", + "/usr/bin/bug-open-nofollow-sym", + "/usr/bin/bug-open-creat-directory-einval", + "/usr/bin/bug-open-path-honors-excl", + "/usr/bin/bug-open-path-dir-write-eisdir", + "/usr/bin/bug-open-path-creat-creates", + "/usr/bin/bug-open-path-sym-write-enoent", + "/usr/bin/bug-open-rdonly-trunc-einval", + "/usr/bin/bug-open-rdonly-append-promotes-rw", + "/usr/bin/bug-open-creat-on-existing-dir-no-eisdir", + "/usr/bin/bug-open-append-trunc-einval", + "/usr/bin/bug-open-tmpfile-no-einval", + "/usr/bin/bug-open-pathmax-no-enametoolong", + "/usr/bin/bug-openat-empty-path-no-enoent", + "/usr/bin/bug-openat-abs-path-honors-invalid-dirfd", + "/usr/bin/bug-open-path-fstat-ebadf", ] success_regex = ["(?m)^STARRY_GROUPED_TESTS_PASSED\\s*$"] fail_regex = ['(?i)\bpanic(?:ked)?\b', '(?m)^STARRY_GROUPED_TEST_FAILED:'] diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-x86_64.toml b/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-x86_64.toml index e2765b1c14..4682949360 100644 --- a/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-x86_64.toml +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-x86_64.toml @@ -70,6 +70,21 @@ test_commands = [ "/usr/bin/bug-tmpfs-hardlink-cache", "/usr/bin/bug-zombie-syscalls", "/usr/bin/bug-starry-setuid-setgid-no-uidvalid", + "/usr/bin/bug-open-nofollow-sym", + "/usr/bin/bug-open-creat-directory-einval", + "/usr/bin/bug-open-path-honors-excl", + "/usr/bin/bug-open-path-dir-write-eisdir", + "/usr/bin/bug-open-path-creat-creates", + "/usr/bin/bug-open-path-sym-write-enoent", + "/usr/bin/bug-open-rdonly-trunc-einval", + "/usr/bin/bug-open-rdonly-append-promotes-rw", + "/usr/bin/bug-open-creat-on-existing-dir-no-eisdir", + "/usr/bin/bug-open-append-trunc-einval", + "/usr/bin/bug-open-tmpfile-no-einval", + "/usr/bin/bug-open-pathmax-no-enametoolong", + "/usr/bin/bug-openat-empty-path-no-enoent", + "/usr/bin/bug-openat-abs-path-honors-invalid-dirfd", + "/usr/bin/bug-open-path-fstat-ebadf", ] success_regex = ["(?m)^STARRY_GROUPED_TESTS_PASSED\\s*$"] fail_regex = ['(?i)\bpanic(?:ked)?\b', '(?m)^STARRY_GROUPED_TEST_FAILED:']