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
|
|
@ -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])))
|
||||
|
|
|
|||
Loading…
Reference in a new issue