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
|
|
@ -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/<pid>/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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
120
kernel/fs/fs.c
120
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);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
|
|
|
|||
|
|
@ -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])))
|
||||
|
|
|
|||
|
|
@ -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