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:
parent
edb560de66
commit
7a1e63a553
8 changed files with 274 additions and 136 deletions
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue