diff --git a/docs/architecture/filesystem.md b/docs/architecture/filesystem.md index 029ae82..d58ac7c 100644 --- a/docs/architecture/filesystem.md +++ b/docs/architecture/filesystem.md @@ -153,6 +153,11 @@ Three things worth knowing: length until it is generated, and generating every one on every `stat()` to fill in a number almost nothing reads would be absurd. +`/proc/mounts`, `/proc/filesystems` and `/proc/partitions` are in Linux's +formats to the column, since what reads them (`df`, `mount`, `fdisk -l`, +installer scripts) parses them. Every disk and partition in `/proc/partitions` +has major 8 and its device number as the minor, matching `/dev`. + ## devtmpfs `/dev` is generated, not stored. `kernel/fs/devfs.c`, behind the same `fs_ops` @@ -283,6 +288,23 @@ held alive by an `nlink` bump. Renaming a *directory* is still refused with `EISDIR`, and so is replacing one: both need the `..` fixup and the ancestor-loop check that are not written. +## Symlinks + +Followed the way Linux follows them, in `namex_from()`. A link in the middle of +a path (`/a/link/b`) always is; the last component only for the calls that +follow (`open`, `stat`, `chdir`, `execve`, `access`, `truncate`, `statfs`, +`utimensat`), not for `lstat`, `readlink`, `unlink`, `rename`, `link` and the +create paths. A relative target resolves from the directory holding the link, +not from the caller's cwd, and a 41st link in one lookup is `ELOOP`. + +The walk never holds a directory lock while it reads a link, only a reference +to the directory it is in, so following one is restarting the walk from there +with the target spliced in front of what is left of the path. + +`open(O_CREAT)` through a link to an existing file opens the file, so +`echo hi > link` writes where the link points. A dangling link is not created +through. + ## What it does not do - **No page cache.** `mmap` of a file reads the whole thing in at map time, and @@ -290,20 +312,13 @@ both need the `..` fixup and the ancestor-loop check that are not written. single largest gap in the layer. - **No symlinks in procfs**, which is why `/proc//exe` is a regular file and `ps -o comm` prints `[init]`. -- **Symlinks resolve on the final path component only.** `open()`, `stat()` and - friends follow a link they are pointed at; a link in the *middle* of a path — - `/a/link/b` — is not resolved. That belongs in `namex()`, which would have to - re-enter path lookup while holding a directory lock, and getting that wrong is - a deadlock rather than a wrong answer. - **`rename()` does not move directories**, and will not replace one. `EISDIR` either way. -- **No named pipes.** `mkfifo()` does not fail — it *succeeds* and produces a - character device, because `mknod()` turns `S_IFIFO` into a `T_DEV` and keeps - the major/minor. `ls -l` shows `crw-rw-rw- 438, 0` where a `p` belongs, and - nothing written to it comes back out. Succeeding wrongly is worse than - refusing: the zsh port was configured with `zsh_cv_sys_fifo=yes` on the - strength of it, and process substitution failed at the point of use rather - than being reported as unavailable. +- **No named pipes.** `mkfifo()` fails with `EPERM`, Linux's answer for a + filesystem that cannot hold one. It used to *succeed* and make a character + device, because `mknod()` read a Linux program's `(mode, dev)` as the native + `(major, minor)`: `ls -l` showed `crw-rw-rw- 438, 0` where a `p` belongs. The + zsh port was configured with `zsh_cv_sys_fifo=yes` on the strength of that. - **No `/dev/fd`, and no `/proc/self`.** A process cannot reach its own open descriptors by path. `<(cmd)` needs one or the other — zsh now knows it has neither and says so. diff --git a/include/file.h b/include/file.h index d2e25c9..d4f213d 100644 --- a/include/file.h +++ b/include/file.h @@ -175,6 +175,9 @@ struct inode { int iheld(uint dev, uint64 inum); // 1 if any inode on dev but `except` is referenced (0: no exception). In fs.c. int ibusy(uint dev, uint64 except); +// Path lookup that follows a final symlink too; 0 with *err set on failure. +// `start` 0 means the cwd. In fs.c. +struct inode *nameifollow(struct inode *start, char *path, int *err); // table mapping major device number to // device functions diff --git a/kernel/dev/blkdev.c b/kernel/dev/blkdev.c index cb40129..8f6b47e 100644 --- a/kernel/dev/blkdev.c +++ b/kernel/dev/blkdev.c @@ -372,23 +372,35 @@ putu(char *buf, int cap, int off, uint64 v) return off; } +// putu(), right-aligned in `w` columns, the way printf("%*llu") would. +static int +putw(char *buf, int cap, int off, uint64 v, int w) +{ + int n = 1; + for (uint64 t = v; t >= 10; t /= 10) + n++; + for (; w > n; w--) + off = put(buf, cap, off, " "); + return putu(buf, cap, off, v); +} + +// /proc/partitions, in Linux's format to the column, because that is what +// reads it: lsblk's fallback, fdisk -l, every installer and rescue script. +// Sizes are in 1 KB blocks. The major is 8 for all of them, the one devtmpfs +// gives every disk here, and the minor is the device number, as in /dev. int blkdev_info(char *buf, int cap) { - int off = put(buf, cap, 0, "dev name MB type\n"); + int off = put(buf, cap, 0, "major minor #blocks name\n\n"); for (int s = 0; s < NBLKDEV; s++) { struct blkdev *d = &devs[s]; if (!d->used) continue; - off = putu(buf, cap, off, BLKDEV_FIRST + s); - off = put(buf, cap, off, " "); + off = putw(buf, cap, off, 8, 4); + off = putw(buf, cap, off, BLKDEV_FIRST + s, 8); + off = putw(buf, cap, off, (d->nsectors * d->secsize) >> 10, 11); + off = put(buf, cap, off, " "); off = put(buf, cap, off, d->name); - for (int pad = 12 - strlen(d->name); pad > 0; pad--) - off = put(buf, cap, off, " "); - off = putu(buf, cap, off, (d->nsectors * d->secsize) >> 20); - off = put(buf, cap, off, d->parent < 0 ? " disk" : - d->schfs > 0 ? " schfs" : - d->schfs < 0 ? " fat" : " part"); off = put(buf, cap, off, "\n"); } return off; diff --git a/kernel/dev/procfs.c b/kernel/dev/procfs.c index 6e4ce6f..d3e2fd3 100644 --- a/kernel/dev/procfs.c +++ b/kernel/dev/procfs.c @@ -38,6 +38,8 @@ #include "sbuf.h" #include "kmem.h" #include "dbg.h" +#include "blkdev.h" +#include "fat.h" #define PROC_BUFSZ 2048 @@ -241,9 +243,9 @@ gen_mounts(struct sbuf *sb) char path[MAXPATH], source[MAXPATH], type[16]; uint fsdev; - sb_puts(sb, "devtmpfs /dev devtmpfs rw 0 0\n"); - - for (int i = 0; i < 8; i++) { + // Every mount is in the table, /dev included. A fixed devtmpfs line here + // listed it twice once init mounted it the ordinary way. + for (int i = 0; i < 16; i++) { if (mount_get(i, path, source, type, &fsdev) != 0) continue; // "/" is emitted last, below, so skip it if it somehow appears here. @@ -256,7 +258,8 @@ gen_mounts(struct sbuf *sb) sb_puts(sb, type[0] ? type : "schfs"); // A mounted snapshot is genuinely read-only, and saying so is what stops // anything trying to write to one and reporting a confusing EROFS later. - sb_puts(sb, SCHFS_IS_SNAP_DEV(fsdev) ? " ro 0 0\n" : " rw 0 0\n"); + int ro = SCHFS_IS_SNAP_DEV(fsdev) || (FAT_IS_DEV(fsdev) && fat_readonly(fsdev)); + sb_puts(sb, ro ? " ro 0 0\n" : " rw 0 0\n"); } // Root must be last: df keeps the last mount point that prefixes the path @@ -323,6 +326,7 @@ gen_filesystems(struct sbuf *sb) sb_puts(sb, "\tschfs\n"); sb_puts(sb, "nodev\tproc\n"); sb_puts(sb, "nodev\tdevtmpfs\n"); + sb_puts(sb, "\tvfat\n"); } // --- dispatch -------------------------------------------------------------- @@ -347,6 +351,7 @@ gen_filesystems(struct sbuf *sb) #define PROC_BOOTMODE 19 #define PROC_BOOTDEBUG 30 #define PROC_SLAB 20 +#define PROC_PARTITIONS 21 // Render global file `id` (a PROC_* constant) into `buf`. Returns its length. // @@ -401,6 +406,8 @@ proc_gen_global(int id, char *buf, int cap) sb_puts(&sb, boot_debug ? "on\n" : "off\n"); break; case PROC_DEVICES: gen_devices(&sb); break; + // The block layer renders its own table, like PCI and SchFS above. + case PROC_PARTITIONS: sb.len = blkdev_info(buf, PROC_BUFSZ); break; default: return 0; } return sb.len; diff --git a/kernel/fs/exec.c b/kernel/fs/exec.c index 3d12453..dfdece5 100644 --- a/kernel/fs/exec.c +++ b/kernel/fs/exec.c @@ -11,6 +11,10 @@ #include "errno.h" #include "x86.h" #include "fsstat.h" +#include "spinlock.h" +#include "sleeplock.h" +#include "fs.h" +#include "file.h" // Binaries with entry points at 0x100000+ are musl/Linux-ABI. // SchOS-native binaries (init) are linked at 0x4000. @@ -56,11 +60,12 @@ exec(char *path, char **argv, char **envp) begin_op(); - if((ip = namei(path)) == 0){ + int err; + if((ip = nameifollow(0, path, &err)) == 0){ end_op(); // Must be ENOENT, not a bare -1: execvp() relies on this exact errno to // know it should try the next $PATH component rather than give up. - return -ENOENT; + return -err; } ilock(ip); diff --git a/kernel/fs/fs.c b/kernel/fs/fs.c index 702e32a..c75207b 100644 --- a/kernel/fs/fs.c +++ b/kernel/fs/fs.c @@ -44,6 +44,7 @@ #include "fsstat.h" #include "file.h" #include "fat.h" +#include "errno.h" extern uint uptime(void); @@ -1616,6 +1617,11 @@ int mount_add(uint dev, uint64 inum, uint fsdev, uint64 root, const char *path, const char *source, const char *type) { + // The lookup that found the mount point already crossed any mount on it, + // so mounting devtmpfs on /dev a second time lands on devtmpfs's own root. + // That stacked it on itself and listed it twice in /proc/mounts. + if (dev == fsdev && inum == root) + return -1; acquire(&mtable.lock); for (int i = 0; i < NMOUNT; i++) if (mtable.m[i].used && mtable.m[i].dev == dev && @@ -1742,24 +1748,30 @@ mount_cross(struct inode *ip) // ownership of its own reference) rather than always `myproc()->cwd`. This is // what makes a real (non-AT_FDCWD) dirfd argument to the *at() syscalls // actually work: an absolute path still resolves from root either way. +// +// Symlinks are followed the way Linux follows them. One in the middle of the +// path always is; the last component only when `follow` says so, since +// lstat(), readlink(), unlink() and friends want the link itself. A relative +// target is resolved from the directory that holds the link, not from the +// caller's cwd, and more than MAXSYMLINKS of them in one lookup is ELOOP. +// +// The link is read with no directory locked: the walk only ever holds a +// reference to the directory it is in, so re-entering it costs nothing. +#define MAXSYMLINKS 40 + static struct inode * -namex_from(struct inode *start, char *path, int nameiparent, char *name) +namex_from(struct inode *start, char *path, int nameiparent, char *name, + int follow, int *err) { struct inode *ip, *next; + int links = 0; - // Collapse consecutive slashes into a safe local copy. + // A private copy, because following a link rewrites what is left of it. char safe[MAXPATH]; - int i = 0, j = 0; - while (path[i] != '\0' && j < MAXPATH - 1) { - if (path[i] == '/') { - safe[j++] = '/'; - while (path[i] == '/') i++; - } else { - safe[j++] = path[i++]; - } - } - safe[j] = '\0'; + safestrcpy(safe, path, MAXPATH); char *spath = safe; + if (err) + *err = ENOENT; // Root is a SchFS object, not a v1 inode. ops_for(SCHFS_DEV) then routes // every operation on it through schfs_ops, which is the whole point: the v1 @@ -1786,6 +1798,8 @@ namex_from(struct inode *start, char *path, int nameiparent, char *name) ilock(ip); if (ip->type != T_DIR) { iunlockput(ip); + if (err) + *err = ENOTDIR; return 0; } if (nameiparent && *spath == '\0') { @@ -1796,8 +1810,41 @@ namex_from(struct inode *start, char *path, int nameiparent, char *name) iunlockput(ip); return 0; } - iunlockput(ip); - ip = mount_cross(next); + iunlock(ip); + next = mount_cross(next); + + ilock(next); + if (next->type != T_SYMLINK || (*spath == '\0' && (!follow || nameiparent))) { + iunlock(next); + iput(ip); + ip = next; + continue; + } + + // A link: put its target in front of what is left of the path and carry + // on from the directory it was found in, or from root. + char tgt[MAXPATH]; + int len = (++links > MAXSYMLINKS) ? -1 + : readi(next, 0, (uint64)tgt, 0, MAXPATH - 1); + iunlockput(next); + int rest = strlen(spath); + if (len <= 0 || len + 1 + rest >= MAXPATH) { + iput(ip); + if (err) + *err = len < 0 ? ELOOP : len == 0 ? ENOENT : ENAMETOOLONG; + return 0; + } + tgt[len] = '\0'; + if (rest) { + tgt[len] = '/'; + memmove(tgt + len + 1, spath, rest + 1); + } + memmove(safe, tgt, strlen(tgt) + 1); + spath = safe; + if (*spath == '/') { + iput(ip); + ip = mount_cross(iget(SCHFS_DEV, schfs_kroot(SCHFS_DEV))); + } } if (nameiparent) { @@ -1810,7 +1857,7 @@ namex_from(struct inode *start, char *path, int nameiparent, char *name) static struct inode * namex(char *path, int nameiparent, char *name) { - return namex_from(myproc()->cwd, path, nameiparent, name); + return namex_from(myproc()->cwd, path, nameiparent, name, 0, 0); } struct inode * @@ -1832,50 +1879,23 @@ struct inode * nameifrom(struct inode *start, char *path) { char name[DIRSIZ]; - return namex_from(start, path, 0, name); + return namex_from(start, path, 0, name, 0, 0); } struct inode * nameiparentfrom(struct inode *start, char *path, char *name) { - return namex_from(start, path, 1, name); + return namex_from(start, path, 1, name, 0, 0); } -// =========================================================================== -// Symlink traversal -// -// Fixed from original: -// - Buffer was char path[MAXPATH+1]. Original called readi(..., MAXPATH) -// then a second readi(... path+len, len, MAXPATH+1) to assert nothing -// more was there. The second read could write 1 byte past the buffer -// if len == MAXPATH. -// - Now: read at most MAXPATH-1 bytes, always NUL-terminate. -// =========================================================================== - +// The lookup open(), stat(), chdir() and exec() want: a final symlink is +// followed too, and on failure `*err` says why (ENOENT, ENOTDIR, ELOOP, +// ENAMETOOLONG), so a loop is not reported as a missing file. struct inode * -divesymlink(struct inode *isym) +nameifollow(struct inode *start, char *path, int *err) { - struct inode *ip = isym; - char path[MAXPATH]; - int deep = 0; - - do { - int len = readi(ip, 0, (uint64)path, 0, MAXPATH - 1); - if (len < 0) len = 0; - path[len] = '\0'; - - iunlockput(ip); - - if (++deep > 10) - return 0; // symlink loop - - if ((ip = namei(path)) == 0) - return 0; - - ilock(ip); - } while (ip->type == T_SYMLINK); - - return ip; + char name[DIRSIZ]; + return namex_from(start ? start : myproc()->cwd, path, 0, name, 1, err); } // =========================================================================== diff --git a/kernel/fs/procfs.c b/kernel/fs/procfs.c index c0d1bd3..ef3549b 100644 --- a/kernel/fs/procfs.c +++ b/kernel/fs/procfs.c @@ -103,6 +103,8 @@ static const struct global_file globals[] = { { "bootdebug", 30 }, // What the slab allocator is holding. See include/kmem.h. { "slab", 20 }, + // Disks and partitions, in Linux's format. See blkdev_info(). + { "partitions", 21 }, { "kmsg", 17 }, }; #define NGLOBALS ((int)(sizeof(globals) / sizeof(globals[0]))) diff --git a/kernel/sys/sys_fs.c b/kernel/sys/sys_fs.c index 0d0770b..eb5c04d 100644 --- a/kernel/sys/sys_fs.c +++ b/kernel/sys/sys_fs.c @@ -54,6 +54,9 @@ struct linux_stat { #define LINUX_S_IFREG 0100000 #define LINUX_S_IFLNK 0120000 #define LINUX_S_IFSOCK 0140000 +#define LINUX_S_IFMT 0170000 +#define LINUX_S_IFBLK 0060000 +#define LINUX_S_IFIFO 0010000 // Linux's dev_t: 12 bits of major and 8 of minor packed into the low 32, with // the overflow of each above that. Spelled out in full rather than as @@ -162,6 +165,17 @@ at_namei(int dirfd, char *path) return nameifrom(start, path); } +// at_namei() that follows a final symlink as well, and says why it failed. +static struct inode * +at_nameif(int dirfd, char *path, int *err) +{ + *err = ENOENT; + if (!path) return 0; + struct inode *start = (path[0] == '/') ? myproc()->cwd : at_dir(dirfd); + if (!start) { *err = EBADF; return 0; } + return nameifollow(start, path, err); +} + static struct inode * at_nameiparent(int dirfd, char *path, char *name) { @@ -206,7 +220,9 @@ createat_x(int dirfd, char *path, short type, short major, short minor, // `> /dev/null` did not work, despite /dev/null existing. // mkdir/mknod ask for T_DIR/T_DEV and still correctly fail here, // which is what lets them report EEXIST. - if (type == T_FILE && ip->type != T_DIR) + // A symlink found here dangles: open() already followed every link + // that leads somewhere. Writing into it would rewrite its target. + if (type == T_FILE && ip->type != T_DIR && ip->type != T_SYMLINK) return ip; iunlockput(ip); return 0; @@ -479,33 +495,33 @@ addr_t sys_pipe2(void) // Filesystem ops // ----------------------------------------------------------------------- -// Resolve a symlink opened as the final path component. -// -// divesymlink() has been in kernel/fs/fs.c since the fork and nothing ever -// called it, so a symlink behaved as a small file containing its own target: -// `cat link` printed the target's *name* rather than its contents, and every -// program that follows a symlink to find something followed it nowhere. -// -// Only the last component. A symlink in the middle of a path -- /a/link/b -- -// is still not resolved; that belongs in namex(), which would have to re-enter -// path lookup while holding a directory lock, and getting that wrong is a -// deadlock rather than a wrong answer. Final-component resolution is what -// open(), exec() and stat() need, which is nearly everything that matters. -// -// Takes a locked inode and returns a locked inode, or 0 with everything -// released -- the same contract divesymlink() already had. +// The lookup for opening something that exists. A final symlink is followed, +// from the directory it is in, unless O_NOFOLLOW, which makes one an ELOOP the +// way POSIX has it (this kernel has no O_PATH to open the link itself). +// Returns the inode unlocked, or 0 with *err set. static struct inode * -follow_final(struct inode *ip, int omode) +open_lookup(int dirfd, char *path, int omode, int *err) { - if (ip->type != T_SYMLINK) - return ip; - if (omode & O_NOFOLLOW) { - // POSIX: opening a symlink itself is an error unless O_PATH, which - // this kernel does not have. Saying so beats handing back the link. - iunlockput(ip); - return 0; - } - return divesymlink(ip); + if (!(omode & O_NOFOLLOW)) + return at_nameif(dirfd, path, err); + struct inode *ip = at_namei(dirfd, path); + *err = ENOENT; + if (!ip) return 0; + ilock(ip); + if (ip->type == T_SYMLINK) { iunlockput(ip); *err = ELOOP; return 0; } + iunlock(ip); + return ip; +} + +// open(O_CREAT) on a name that exists through a symlink opens the target, so +// `echo hi > link` writes where the link points. 0 if there is nothing there +// (or O_EXCL), and the caller creates. +static struct inode * +open_existing(int dirfd, char *path, int omode) +{ + int err; + if (omode & (O_EXCL | O_NOFOLLOW)) return 0; + return at_nameif(dirfd, path, &err); } addr_t sys_open(void) @@ -523,8 +539,12 @@ addr_t sys_open(void) if (ex) { iput(ex); end_op(); return -EEXIST; } } int cerr = 0; - ip = createat_x(AT_FDCWD, path, - (omode & O_EXTENT) ? T_EXTENT : T_FILE, 0, 0, &cerr); + if ((ip = open_existing(AT_FDCWD, path, omode)) != 0) { + ilock(ip); + if (ip->type == T_DIR) { iunlockput(ip); end_op(); return -EISDIR; } + } else + ip = createat_x(AT_FDCWD, path, + (omode & O_EXTENT) ? T_EXTENT : T_FILE, 0, 0, &cerr); // A refusal because the filesystem is read-only is not "no such file". // A shell prints the same sentence for every create failure, so // returning ENOENT here made writing to a snapshot look like a missing @@ -533,9 +553,9 @@ addr_t sys_open(void) if ((omode & O_TRUNC) && (ip->type == T_FILE || ip->type == T_EXTENT)) itrunc(ip); } else { - if (!(ip = namei(path))) { end_op(); return -ENOENT; } + int err; + if (!(ip = open_lookup(AT_FDCWD, path, omode, &err))) { end_op(); return -err; } ilock(ip); - if (!(ip = follow_final(ip, omode))) { end_op(); return -ELOOP; } // Directories may only be opened for reading, but callers (e.g. // opendir()) legitimately OR in flags like O_DIRECTORY/O_CLOEXEC/ // O_NONBLOCK alongside O_RDONLY — only the access-mode bits matter. @@ -577,14 +597,17 @@ addr_t sys_openat(void) if (ex) { iput(ex); end_op(); return -EEXIST; } } int cerr = 0; - if (!(ip = createat_x(dirfd, path, T_FILE, 0, 0, &cerr))) { + if ((ip = open_existing(dirfd, path, omode)) != 0) { + ilock(ip); + if (ip->type == T_DIR) { iunlockput(ip); end_op(); return -EISDIR; } + } else if (!(ip = createat_x(dirfd, path, T_FILE, 0, 0, &cerr))) { end_op(); return cerr ? -cerr : -ENOENT; } } else { - if (!(ip = at_namei(dirfd, path))) { end_op(); return -ENOENT; } + int err; + if (!(ip = open_lookup(dirfd, path, omode, &err))) { end_op(); return -err; } ilock(ip); - if (!(ip = follow_final(ip, omode))) { end_op(); return -ELOOP; } // Directories may only be opened for reading, but callers (e.g. // opendir()) legitimately OR in flags like O_DIRECTORY/O_CLOEXEC/ // O_NONBLOCK alongside O_RDONLY — only the access-mode bits matter. @@ -656,13 +679,13 @@ do_stat(int dirfd, char *path, char *st, int follow) { struct inode *ip; + int err = ENOENT; begin_op(); - if (!(ip = at_namei(dirfd, path))) { end_op(); return -ENOENT; } - ilock(ip); - if (follow && ip->type == T_SYMLINK && !(ip = divesymlink(ip))) { + if (!(ip = follow ? at_nameif(dirfd, path, &err) : at_namei(dirfd, path))) { end_op(); - return -ELOOP; + return -err; } + ilock(ip); if (myproc()->syscall_map) stati_linux(ip, (struct linux_stat*)st); else stati(ip, (struct stat*)st); iunlockput(ip); @@ -692,9 +715,10 @@ addr_t sys_access(void) { char *path; if (argstr(0, &path) < 0) return -1; + int err; begin_op(); - struct inode *ip = namei(path); - if (!ip) { end_op(); return -ENOENT; } + struct inode *ip = at_nameif(AT_FDCWD, path, &err); + if (!ip) { end_op(); return -err; } iput(ip); end_op(); return 0; } @@ -705,7 +729,8 @@ addr_t sys_chdir(void) struct inode *ip; begin_op(); if (argstr(0, &path) < 0) { end_op(); return -EFAULT; } - if (!(ip = namei(path))) { end_op(); return -ENOENT; } + int err; + if (!(ip = at_nameif(AT_FDCWD, path, &err))) { end_op(); return -err; } ilock(ip); // ENOTDIR, not a bare -1. `cd` into a file must fail, and it did, but the // shell reported "Operation not permitted" -- which reads like a @@ -775,14 +800,65 @@ addr_t sys_mkdir(void) return 0; } +// What a Linux mknod(mode, dev) asks for, as a type here. Character and block +// devices are both T_DEV (the major says which driver); a regular file is what +// mknod(S_IFREG) has always meant. Named pipes and sockets have no on-disk +// form here yet, and Linux's answer for a filesystem that cannot hold one is +// EPERM. Returns the type, or a negative errno. +static int +mknod_type(uint32 mode) +{ + switch (mode & LINUX_S_IFMT) { + case 0: + case LINUX_S_IFREG: return T_FILE; + case LINUX_S_IFCHR: + case LINUX_S_IFBLK: return T_DEV; + default: return -EPERM; + } +} + +static addr_t +do_mknod(int dirfd, char *path, uint32 mode, uint64 dev) +{ + int type = mknod_type(mode); + if (type < 0) return type; + // Linux's dev_t: 12 bits of major in 8..19 and the rest from bit 32, the + // minor's low byte in 0..7 and the rest from bit 20. The other half of + // linux_makedev(). + int major = (int)(((dev >> 8) & 0xfff) | ((dev >> 32) & ~0xfffull)); + int minor = (int)((dev & 0xff) | ((dev >> 12) & ~0xffull)); + if (type == T_FILE) major = minor = 0; + begin_op(); + // createat_x() opens an existing file when asked for one, as open(O_CREAT) + // wants. mknod() must not. + struct inode *ex = at_namei(dirfd, path); + if (ex) { iput(ex); end_op(); return -EEXIST; } + int cerr = 0; + struct inode *ip = createat_x(dirfd, path, type, (short)major, + (short)minor, &cerr); + if (!ip) { end_op(); return cerr ? -cerr : -EEXIST; } + iunlockput(ip); end_op(); + return 0; +} + +// The native call is mknod(path, major, minor); a Linux program's is +// mknod(path, mode, dev), on the same slot through the syscall map. Reading +// the Linux one as the native one made `mknod n c 1 2` a node numbered from +// the mode bits: 432, 258. addr_t sys_mknod(void) { struct inode *ip; char *path; int major, minor; + if (argstr(0, &path) < 0 || argint(1, &major) < 0 || argint(2, &minor) < 0) + return -EFAULT; + if (myproc()->syscall_map) { + uint64 dev; + argaddr(2, &dev); + return do_mknod(AT_FDCWD, path, (uint32)major, dev); + } begin_op(); - if (argstr(0, &path) < 0 || argint(1, &major) < 0 || argint(2, &minor) < 0 || - !(ip = create(path, T_DEV, major, minor))) { end_op(); return -1; } + if (!(ip = create(path, T_DEV, major, minor))) { end_op(); return -1; } iunlockput(ip); end_op(); return 0; } @@ -953,8 +1029,9 @@ addr_t sys_truncate(void) struct inode *ip; if (argstr(0, &path) < 0 || argaddr(1, &length) < 0) return -1; if ((int64)length < 0) return -EINVAL; + int err; begin_op(); - if (!(ip = namei(path))) { end_op(); return -ENOENT; } + if (!(ip = at_nameif(AT_FDCWD, path, &err))) { end_op(); return -err; } ilock(ip); itrunc_to(ip, (uint64)length); iunlockput(ip); end_op(); @@ -1651,10 +1728,12 @@ addr_t sys_utimensat(void) } else { char *path; if (argstr(1, &path) < 0) return -EFAULT; + int err = ENOENT; begin_op(); - ip = at_namei(dirfd, path); + ip = (flags & AT_SYMLINK_NOFOLLOW) ? at_namei(dirfd, path) + : at_nameif(dirfd, path, &err); end_op(); - if (!ip) return -ENOENT; + if (!ip) return -err; } uint64 now = 0, us = 0; @@ -1747,9 +1826,10 @@ addr_t sys_statfs(void) // Resolve the path: which filesystem it lands on is the whole question, // and mount crossing happens during resolution. + int err; begin_op(); - struct inode *ip = namei(path); - if (!ip) { end_op(); return -ENOENT; } + struct inode *ip = at_nameif(AT_FDCWD, path, &err); + if (!ip) { end_op(); return -err; } uint dev = ip->dev; iput(ip); end_op(); @@ -1806,17 +1886,10 @@ addr_t sys_mknodat(void) uint64 dev; argint(0, &dirfd); if (argstr(1, &path) < 0) return -1; - argint(2, &(int){0}); // mode — ignored + int mode; + argint(2, &mode); argaddr(3, &dev); - int major = (dev >> 8) & 0xff; - int minor = dev & 0xff; - begin_op(); - int cerr = 0; - struct inode *ip = createat_x(dirfd, path, T_DEV, (short)major, - (short)minor, &cerr); - if (!ip) { end_op(); return cerr ? -cerr : -1; } - iunlockput(ip); end_op(); - return 0; + return do_mknod(dirfd, path, (uint32)mode, dev); } addr_t sys_unlinkat(void) @@ -1970,9 +2043,10 @@ addr_t sys_faccessat(void) if (argstr(1, &path) < 0) return -1; argint(2, &(int){0}); // amode — ignored // arg3 flags — ignored + int err; begin_op(); - struct inode *ip = at_namei(dirfd, path); - if (!ip) { end_op(); return -ENOENT; } + struct inode *ip = at_nameif(dirfd, path, &err); + if (!ip) { end_op(); return -err; } iput(ip); end_op(); return 0; }