vfs: names up to 255 bytes and paths up to 4096, like Linux

Every path component was copied into a DIRSIZ buffer and every path
into a MAXPATH one, so names were cut at 29 bytes and paths at 127
without an error. Two names sharing their first 29 bytes opened the
same file, on SchFS and FAT alike, though both hold 255.

VFS_NAME_MAX (255) and VFS_PATH_MAX (4096) are the limits now, and
anything longer is ENAMETOOLONG. namex_from() takes its path buffers
from kalloc(), a page each, rather than putting two of them on an 8 KB
kernel stack. The v1 format still stores DIRSIZ bytes, and refuses a
longer name instead of matching on its first 30.

Checked: 255-byte names on SchFS and FAT (fsck.fat clean, and the FAT
name answers to its uppercase form), 256 refused, a 200-byte path and a
link to it, and no pages lost over 700 lookups through links.

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 11:17:28 +02:00
commit 67db42b460
7 changed files with 121 additions and 38 deletions

View file

@ -1345,6 +1345,10 @@ v1_dirlookup(struct inode *dp, char *name, uint *poff)
return 0;
}
// The format stores DIRSIZ bytes of a name. A longer one cannot be here,
// and comparing only its first DIRSIZ would find a different file.
if (strlen(name) > DIRSIZ)
return 0;
tree_search(dp, name, poff, &de, 0);
if (de.inum)
return iget(dp->dev, de.inum);
@ -1358,6 +1362,8 @@ v1_dirlink(struct inode *dp, char *name, uint64 inum)
struct dirent de;
struct inode *ip;
if (strlen(name) > DIRSIZ)
return -1; // see v1_dirlookup
if ((ip = dirlookup(dp, name, 0)) != 0) {
iput(ip);
return -1; // already exists
@ -1537,8 +1543,11 @@ build_dir_tree(struct inode *dp, uint64 pinum)
// Path lookup
// ===========================================================================
// Copy the next component of `path` into `name` (VFS_NAME_MAX + 1 bytes) and
// return what follows it, or 0 at the end. A component longer than
// VFS_NAME_MAX sets *toolong and ends the walk.
static char *
skipelem(char *path, char *name)
skipelem(char *path, char *name, int *toolong)
{
char *s;
int len;
@ -1551,12 +1560,12 @@ skipelem(char *path, char *name)
while (*path != '/' && *path != 0)
path++;
len = path - s;
if (len >= DIRSIZ)
memmove(name, s, DIRSIZ);
else {
memmove(name, s, len);
name[len] = 0;
if (len > VFS_NAME_MAX) {
*toolong = 1;
return 0;
}
memmove(name, s, len);
name[len] = 0;
while (*path == '/')
path++;
return path;
@ -1764,14 +1773,27 @@ namex_from(struct inode *start, char *path, int nameiparent, char *name,
int follow, int *err)
{
struct inode *ip, *next;
int links = 0;
int links = 0, toolong = 0;
// A private copy, because following a link rewrites what is left of it.
char safe[MAXPATH];
safestrcpy(safe, path, MAXPATH);
char *spath = safe;
if (err)
*err = ENOENT;
if (strnlen(path, VFS_PATH_MAX) >= VFS_PATH_MAX) {
if (err)
*err = ENAMETOOLONG;
return 0;
}
// A private copy, because following a link rewrites what is left of it.
// A page each rather than on the stack: PATH_MAX twice over is the whole of
// a kernel stack's slack, and a page is exactly PATH_MAX.
char *safe = kalloc(), *tgt = 0;
if (!safe) {
if (err)
*err = ENOMEM;
return 0;
}
safestrcpy(safe, path, VFS_PATH_MAX);
char *spath = safe;
// 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
@ -1780,7 +1802,7 @@ namex_from(struct inode *start, char *path, int nameiparent, char *name,
// The root itself can be a mount point, and a relative walk can start on one.
ip = mount_cross(ip);
while ((spath = skipelem(spath, name)) != 0) {
while ((spath = skipelem(spath, name, &toolong)) != 0) {
// ".." out of a mounted filesystem's root leaves it. The filesystem down
// there does not know it is mounted anywhere and would answer with its own
// root, which turns the mount into a subtree you can enter and never
@ -1800,15 +1822,15 @@ namex_from(struct inode *start, char *path, int nameiparent, char *name,
iunlockput(ip);
if (err)
*err = ENOTDIR;
return 0;
goto fail;
}
if (nameiparent && *spath == '\0') {
iunlock(ip);
return ip;
goto out;
}
if ((next = dirlookup(ip, name, 0)) == 0) {
iunlockput(ip);
return 0;
goto fail;
}
iunlock(ip);
next = mount_cross(next);
@ -1823,16 +1845,22 @@ namex_from(struct inode *start, char *path, int nameiparent, char *name,
// 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);
if (!tgt && !(tgt = kalloc())) {
iunlockput(next);
iput(ip);
if (err)
*err = ENOMEM;
goto fail;
}
int len = (++links > MAXSYMLINKS) ? -1
: readi(next, 0, (uint64)tgt, 0, VFS_PATH_MAX - 1);
iunlockput(next);
int rest = strlen(spath);
if (len <= 0 || len + 1 + rest >= MAXPATH) {
if (len <= 0 || len + 1 + rest >= VFS_PATH_MAX) {
iput(ip);
if (err)
*err = len < 0 ? ELOOP : len == 0 ? ENOENT : ENAMETOOLONG;
return 0;
goto fail;
}
tgt[len] = '\0';
if (rest) {
@ -1847,11 +1875,26 @@ namex_from(struct inode *start, char *path, int nameiparent, char *name,
}
}
if (toolong) {
iput(ip);
if (err)
*err = ENAMETOOLONG;
goto fail;
}
if (nameiparent) {
iput(ip);
return 0;
goto fail;
}
out:
kfree(safe);
if (tgt)
kfree(tgt);
return ip;
fail:
kfree(safe);
if (tgt)
kfree(tgt);
return 0;
}
static struct inode *
@ -1863,7 +1906,7 @@ namex(char *path, int nameiparent, char *name)
struct inode *
namei(char *path)
{
char name[DIRSIZ];
char name[VFS_NAME_MAX + 1];
return namex(path, 0, name);
}
@ -1878,7 +1921,7 @@ nameiparent(char *path, char *name)
struct inode *
nameifrom(struct inode *start, char *path)
{
char name[DIRSIZ];
char name[VFS_NAME_MAX + 1];
return namex_from(start, path, 0, name, 0, 0);
}
@ -1894,7 +1937,7 @@ nameiparentfrom(struct inode *start, char *path, char *name)
struct inode *
nameifollow(struct inode *start, char *path, int *err)
{
char name[DIRSIZ];
char name[VFS_NAME_MAX + 1];
return namex_from(start ? start : myproc()->cwd, path, 0, name, 1, err);
}