shithub: riscv

Download patch

ref: db971a6189e63802b4c7c0ee41bf00e9864f52a7
parent: e54b6c6cbd4d82d70ddb4932aeafb0b028cd71f5
author: cinap_lenrek <cinap_lenrek@felloff.net>
date: Sat Oct 23 09:40:06 EDT 2021

kernel: fix stat bugs

In a few places, we where using a fixed buffer of sizeof(Dir)+100
size for stat. This is not correct and fails if the name returned
in stat is long.

This results in being unable to seek to the end of file with a
long filename.

The kernel should do the same thing as dirfstat() from libc;
handling the conversion and buffer allocation and returning a
freeable Dir* pointer.

For this, a new dirchanstat() function was added.

The fstat syscall was not rewriting the name to the last path
element; fix it.

In addition, gracefully handle the mountfix case, reallocating
the buffer to accomidate the required stat length plus
size of the new name so dirsetname() does not fail.

--- a/sys/src/9/port/chan.c
+++ b/sys/src/9/port/chan.c
@@ -1720,3 +1720,42 @@
 		return;
 	error(Enotdir);
 }
+
+
+enum
+{
+	DIRSIZE	= STATFIXLEN + 16 * 4		/* enough for encoded stat buf + some reasonable strings */
+};
+
+Dir*
+dirchanstat(Chan *c)
+{
+	Dir *d;
+	uchar *buf;
+	int n, nd, i;
+
+	nd = DIRSIZE;
+	for(i=0; i<2; i++){	/* should work by the second try */
+		d = smalloc(sizeof(Dir) + BIT16SZ + nd);
+		if(waserror()){
+			free(d);
+			nexterror();
+		}
+		buf = (uchar*)&d[1];
+		n = devtab[c->type]->stat(c, buf, BIT16SZ+nd);
+		if(n < BIT16SZ)
+			error(Eshortstat);
+		nd = GBIT16(buf);	/* upper bound on size of Dir + strings */
+		if(nd <= n){
+			if(convM2D(buf, n, d, (char*)&d[1]) == 0)
+				error(Eshortstat);
+			poperror();
+			return d;
+		}
+		/* else sizeof(Dir)+BIT16SZ+nd is plenty */
+		free(d);
+		poperror();
+	}
+	error(Eshortstat);
+	return nil;
+}
--- a/sys/src/9/port/devfs.c
+++ b/sys/src/9/port/devfs.c
@@ -553,18 +553,6 @@
 	validname(*dev, 0);
 }
 
-static vlong
-getlen(Chan *c)
-{
-	uchar	buf[128];	/* old DIRLEN plus a little should be plenty */
-	Dir	d;
-	long	l;
-
-	l = devtab[c->type]->stat(c, buf, sizeof buf);
-	convM2D(buf, l, &d, nil);
-	return d.length;
-}
-
 /*
  * Process a single line of configuration,
  * often of the form "cmd newname idev0 idev1".
@@ -602,8 +590,6 @@
 	start = 0;
 	mp = nil;
 	cb = nil;
-	idev = nil;
-	ilen = nil;
 	keylen = 0;
 
 	if(waserror()){
@@ -686,6 +672,8 @@
 	 */
 	poperror();
 	rlock(&lck);
+	idev = smalloc(sizeof(Chan*) * Ndevs);
+	ilen = smalloc(sizeof(vlong) * Ndevs);
 	if(waserror()){
 		runlock(&lck);
 Fail:
@@ -699,11 +687,14 @@
 		free(cb);
 		nexterror();
 	}
-	idev = smalloc(sizeof(Chan*) * Ndevs);
-	ilen = smalloc(sizeof(vlong) * Ndevs);
 	for(i = 1; i < cb->nf; i++){
+		Dir *dir;
+
 		idev[i-1] = namec(cb->f[i], Aopen, ORDWR, 0);
-		ilen[i-1] = getlen(idev[i-1]);
+
+		dir = dirchanstat(idev[i-1]);
+		ilen[i-1] = dir->length;
+		free(dir);
 	}
 	poperror();
 	runlock(&lck);
--- a/sys/src/9/port/devswap.c
+++ b/sys/src/9/port/devswap.c
@@ -383,10 +383,6 @@
 static void
 setswapchan(Chan *c)
 {
-	uchar buf[sizeof(Dir)+100];
-	Dir d;
-	int n;
-
 	if(waserror()){
 		cclose(c);
 		nexterror();
@@ -403,16 +399,17 @@
 	 *  to be at most the size of the partition
 	 */
 	if(devtab[c->type]->dc != L'M'){
-		n = devtab[c->type]->stat(c, buf, sizeof buf);
-		if(n <= 0 || convM2D(buf, n, &d, nil) == 0)
-			error("stat failed in setswapchan");
-		if(d.length < (vlong)conf.nswppo*BY2PG)
+		Dir *d = dirchanstat(c);
+		if(d->length < (vlong)conf.nswppo*BY2PG){
+			free(d);
 			error("swap device too small");
-		if(d.length < (vlong)conf.nswap*BY2PG){
-			conf.nswap = d.length/BY2PG;
+		}
+		if(d->length < (vlong)conf.nswap*BY2PG){
+			conf.nswap = d->length/BY2PG;
 			swapalloc.top = &swapalloc.swmap[conf.nswap];
 			swapalloc.free = conf.nswap;
 		}
+		free(d);
 	}
 	c->flag &= ~CCACHE;
 	cclunk(c);
--- a/sys/src/9/port/portfns.h
+++ b/sys/src/9/port/portfns.h
@@ -79,6 +79,7 @@
 int		devstat(Chan*, uchar*, int, Dirtab*, int, Devgen*);
 Walkqid*	devwalk(Chan*, Chan*, char**, int, Dirtab*, int, Devgen*);
 int		devwstat(Chan*, uchar*, int);
+Dir*		dirchanstat(Chan *);
 void		drawactive(int);
 void		drawcmap(void);
 void		dumpaproc(Proc*);
--- a/sys/src/9/port/sdloop.c
+++ b/sys/src/9/port/sdloop.c
@@ -47,21 +47,14 @@
 static void
 identify(Ctlr *c, SDunit *u)
 {
-	int n;
 	uvlong s, osectors;
-	uchar buf[sizeof(Dir) + 100];
-	Dir dir;
+	Dir *dir;
 
-	if(waserror()){
-		iprint("sdloop: identify: %s\n", up->errstr);
-		nexterror();
-	}
 	osectors = c->sectors;
-	n = devtab[c->c->type]->stat(c->c, buf, sizeof buf);
-	if(convM2D(buf, n, &dir, nil) == 0)
-		error("internal error: stat error in seek");
-	s = dir.length / c->sectsize;
-	poperror();
+
+	dir = dirchanstat(c->c);
+	s = dir->length / c->sectsize;
+	free(dir);
 
 	memset(u->inquiry, 0, sizeof u->inquiry);
 	u->inquiry[2] = 2;
--- a/sys/src/9/port/sysfile.c
+++ b/sys/src/9/port/sysfile.c
@@ -592,22 +592,26 @@
 			}
 			runlock(&mh->lock);
 
+			if(waserror())
+				goto Norewrite;
 			name = dirname(p, &nname);
-			/*
-			 * Do the stat but fix the name.  If it fails, leave old entry.
-			 * BUG: If it fails because there isn't room for the entry,
-			 * what can we do?  Nothing, really.  Might as well skip it.
-			 */
 			if(buf == nil){
 				nbuf = 4096;
 				buf = smalloc(nbuf);
 			}
-			if(waserror())
-				goto Norewrite;
 			l = devtab[nc->type]->stat(nc, buf, nbuf);
+			if(l < BIT16SZ)
+				error(Eshortstat);
+			rest = BIT16SZ + GBIT16(buf) + nname;
+			if(rest > nbuf){
+				free(buf);
+				nbuf = rest;
+				buf = smalloc(nbuf);
+				l = devtab[nc->type]->stat(nc, buf, nbuf);
+			}
 			l = dirsetname(name, nname, buf, l, nbuf);
-			if(l == BIT16SZ)
-				error("dirsetname");
+			if(l <= BIT16SZ)
+				error(Eshortstat);
 			poperror();
 
 			/*
@@ -831,10 +835,8 @@
 static vlong
 sseek(int fd, vlong o, int type)
 {
+	Dir *d;
 	Chan *c;
-	uchar buf[sizeof(Dir)+100];
-	Dir dir;
-	int n;
 	vlong off;
 
 	c = fdtochan(fd, -1, 1, 1);
@@ -872,10 +874,9 @@
 	case 2:
 		if(c->qid.type & QTDIR)
 			error(Eisdir);
-		n = devtab[c->type]->stat(c, buf, sizeof buf);
-		if(convM2D(buf, n, &dir, nil) == 0)
-			error("internal error: stat error in seek");
-		off = dir.length + o;
+		d = dirchanstat(c);
+		off = d->length + o;
+		free(d);
 		if(off < 0)
 			error(Enegoff);
 		c->offset = off;
@@ -967,25 +968,27 @@
 uintptr
 sysfstat(va_list list)
 {
+	char *name;
+	uchar *s;
 	Chan *c;
 	int fd;
-	uint l;
-	uchar *s;
+	uint l, r;
 
 	fd = va_arg(list, int);
 	s = va_arg(list, uchar*);
 	l = va_arg(list, uint);
 	validaddr((uintptr)s, l, 1);
-
 	c = fdtochan(fd, -1, 0, 1);
 	if(waserror()) {
 		cclose(c);
 		nexterror();
 	}
-	l = devtab[c->type]->stat(c, s, l);
+	r = devtab[c->type]->stat(c, s, l);
+	if((name = pathlast(c->path)) != nil)
+		r = dirsetname(name, strlen(name), s, r, l);
 	poperror();
 	cclose(c);
-	return l;
+	return r;
 }
 
 uintptr
@@ -992,9 +995,9 @@
 sysstat(va_list list)
 {
 	char *name;
+	uchar *s;
 	Chan *c;
 	uint l, r;
-	uchar *s;
 
 	name = va_arg(list, char*);
 	s = va_arg(list, uchar*);
@@ -1007,10 +1010,8 @@
 		nexterror();
 	}
 	r = devtab[c->type]->stat(c, s, l);
-	name = pathlast(c->path);
-	if(name != nil)
+	if((name = pathlast(c->path)) != nil)
 		r = dirsetname(name, strlen(name), s, r, l);
-
 	poperror();
 	cclose(c);
 	return r;
@@ -1330,12 +1331,10 @@
 uintptr
 sys_stat(va_list list)
 {
-	static char old[] = "old stat system call - recompile";
+	char *name;
+	uchar *s;
 	Chan *c;
-	uint l;
-	uchar *s, buf[128];	/* old DIRLEN plus a little should be plenty */
-	char strs[128], *name;
-	Dir d;
+	Dir *d;
 
 	name = va_arg(list, char*);
 	s = va_arg(list, uchar*);
@@ -1346,19 +1345,17 @@
 		cclose(c);
 		nexterror();
 	}
-	l = devtab[c->type]->stat(c, buf, sizeof buf);
-	/* buf contains a new stat buf; convert to old. yuck. */
-	if(l <= BIT16SZ)	/* buffer too small; time to face reality */
-		error(old);
-	name = pathlast(c->path);
-	if(name != nil)
-		l = dirsetname(name, strlen(name), buf, l, sizeof buf);
-	l = convM2D(buf, l, &d, strs);
-	if(l == 0)
-		error(old);
-	packoldstat(s, &d);
-	
+	d = dirchanstat(c);
+	if(waserror()){
+		free(d);
+		nexterror();
+	}
+	if((name = pathlast(c->path)) != nil)
+		d->name = name;
+	packoldstat(s, d);
 	poperror();
+	free(d);
+	poperror();
 	cclose(c);
 	return 0;
 }
@@ -1366,13 +1363,10 @@
 uintptr
 sys_fstat(va_list list)
 {
-	static char old[] = "old fstat system call - recompile";
-	Chan *c;
 	char *name;
-	uint l;
-	uchar *s, buf[128];	/* old DIRLEN plus a little should be plenty */
-	char strs[128];
-	Dir d;
+	uchar *s;
+	Chan *c;
+	Dir *d;
 	int fd;
 
 	fd = va_arg(list, int);
@@ -1383,18 +1377,16 @@
 		cclose(c);
 		nexterror();
 	}
-	l = devtab[c->type]->stat(c, buf, sizeof buf);
-	/* buf contains a new stat buf; convert to old. yuck. */
-	if(l <= BIT16SZ)	/* buffer too small; time to face reality */
-		error(old);
-	name = pathlast(c->path);
-	if(name != nil)
-		l = dirsetname(name, strlen(name), buf, l, sizeof buf);
-	l = convM2D(buf, l, &d, strs);
-	if(l == 0)
-		error(old);
-	packoldstat(s, &d);
-	
+	d = dirchanstat(c);
+	if(waserror()){
+		free(d);
+		nexterror();
+	}
+	if((name = pathlast(c->path)) != nil)
+		d->name = name;
+	packoldstat(s, d);
+	poperror();
+	free(d);
 	poperror();
 	cclose(c);
 	return 0;