fs: follow symlinks like Linux; Linux mknod; /proc/partitions

Symlinks are followed in namex_from() now, in the middle of a path as
well as at the end, and a relative target resolves from the directory
that holds the link rather than from the caller's cwd. `ln -s f d1/s;
cat d1/s` said "Symbolic link loop" because it looked for ./f. A loop is
ELOOP after 40 links, a dangling link ENOENT, and open(O_CREAT) through
a link to a file opens the file. divesymlink() is gone.

mknod from a Linux program passes (mode, dev) where the native call
passes (major, minor), and was read as the native one: `mknod n c 1 2`
made node 432,258, and mkfifo made a character device. Both are decoded
now; a FIFO is EPERM, and an existing name is EEXIST.

/proc/mounts listed devtmpfs twice: once from a fixed line and once
from the mount table. The fixed line is gone, and mount_add() refuses
a filesystem mounted on its own root, which is where a second devtmpfs
mount lands. A read-only FAT mount says ro.

/proc/partitions exists, in Linux's format, and /proc/filesystems lists
vfat.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
This commit is contained in:
Nexoniarz 2026-10-23 10:50:41 +02:00
commit 7a1e63a553
8 changed files with 274 additions and 136 deletions

View file

@ -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;

View file

@ -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;

View file

@ -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);

View file

@ -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);
}
// ===========================================================================

View file

@ -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])))

View file

@ -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;
}