From 2c56547272e43b483d560a61692f1e24926a82fb Mon Sep 17 00:00:00 2001 From: Robert Morris Date: Mon, 4 Aug 2014 13:06:48 -0400 Subject: [PATCH] every iput() and namei() must be inside a transaction --- exec.c | 10 ++++- fs.c | 3 ++ log.c | 6 +-- proc.c | 2 + sysfile.c | 35 ++++++++++++----- usertests.c | 106 +++++++++++++++++++++++++++++++++++++++++++++++++++- 6 files changed, 145 insertions(+), 17 deletions(-) diff --git a/exec.c b/exec.c index a85e203..7eaef5b 100644 --- a/exec.c +++ b/exec.c @@ -18,8 +18,11 @@ exec(char *path, char **argv) struct proghdr ph; pde_t *pgdir, *oldpgdir; - if((ip = namei(path)) == 0) + begin_trans(); + if((ip = namei(path)) == 0){ + commit_trans(); return -1; + } ilock(ip); pgdir = 0; @@ -47,6 +50,7 @@ exec(char *path, char **argv) goto bad; } iunlockput(ip); + commit_trans(); ip = 0; // Allocate two pages at the next page boundary. @@ -95,7 +99,9 @@ exec(char *path, char **argv) bad: if(pgdir) freevm(pgdir); - if(ip) + if(ip){ iunlockput(ip); + commit_trans(); + } return -1; } diff --git a/fs.c b/fs.c index 9d6dfd1..7621d08 100644 --- a/fs.c +++ b/fs.c @@ -314,6 +314,8 @@ iunlock(struct inode *ip) // be recycled. // If that was the last reference and the inode has no links // to it, free the inode (and its content) on disk. +// All calls to iput() must be inside a transaction in +// case it has to free the inode. void iput(struct inode *ip) { @@ -601,6 +603,7 @@ skipelem(char *path, char *name) // Look up and return the inode for a path name. // If parent != 0, return the inode for the parent and copy the final // path element into name, which must have room for DIRSIZ bytes. +// Must be called inside a transaction since it calls iput(). static struct inode* namex(char *path, int nameiparent, char *name) { diff --git a/log.c b/log.c index 1814edc..95cc4d5 100644 --- a/log.c +++ b/log.c @@ -5,7 +5,7 @@ #include "fs.h" #include "buf.h" -// Simple logging. Each system call that might write the file system +// Simple logging. Each file system system call // should be surrounded with begin_trans() and commit_trans() calls. // // The log holds at most one transaction at a time. Commit forces @@ -18,10 +18,6 @@ // one transaction reading a block that another one has modified, // for example an i-node block. // -// Read-only system calls don't need to use transactions, though -// this means that they may observe uncommitted data. I-node and -// buffer locks prevent read-only calls from seeing inconsistent data. -// // The log is a physical re-do log containing disk blocks. // The on-disk log format: // header block, containing sector #s for block A, B, C, ... diff --git a/proc.c b/proc.c index e19539c..db0e9c7 100644 --- a/proc.c +++ b/proc.c @@ -186,7 +186,9 @@ exit(void) } } + begin_trans(); iput(proc->cwd); + commit_trans(); proc->cwd = 0; acquire(&ptable.lock); diff --git a/sysfile.c b/sysfile.c index 64f2b33..095fca7 100644 --- a/sysfile.c +++ b/sysfile.c @@ -120,10 +120,12 @@ sys_link(void) if(argstr(0, &old) < 0 || argstr(1, &new) < 0) return -1; - if((ip = namei(old)) == 0) - return -1; begin_trans(); + if((ip = namei(old)) == 0){ + commit_trans(); + return -1; + } ilock(ip); if(ip->type == T_DIR){ @@ -186,10 +188,12 @@ sys_unlink(void) if(argstr(0, &path) < 0) return -1; - if((dp = nameiparent(path, name)) == 0) - return -1; begin_trans(); + if((dp = nameiparent(path, name)) == 0){ + commit_trans(); + return -1; + } ilock(dp); @@ -286,18 +290,24 @@ sys_open(void) if(argstr(0, &path) < 0 || argint(1, &omode) < 0) return -1; + + begin_trans(); + if(omode & O_CREATE){ - begin_trans(); ip = create(path, T_FILE, 0, 0); - commit_trans(); - if(ip == 0) + if(ip == 0){ + commit_trans(); return -1; + } } else { - if((ip = namei(path)) == 0) + if((ip = namei(path)) == 0){ + commit_trans(); return -1; + } ilock(ip); if(ip->type == T_DIR && omode != O_RDONLY){ iunlockput(ip); + commit_trans(); return -1; } } @@ -306,9 +316,11 @@ sys_open(void) if(f) fileclose(f); iunlockput(ip); + commit_trans(); return -1; } iunlock(ip); + commit_trans(); f->type = FD_INODE; f->ip = ip; @@ -361,15 +373,20 @@ sys_chdir(void) char *path; struct inode *ip; - if(argstr(0, &path) < 0 || (ip = namei(path)) == 0) + begin_trans(); + if(argstr(0, &path) < 0 || (ip = namei(path)) == 0){ + commit_trans(); return -1; + } ilock(ip); if(ip->type != T_DIR){ iunlockput(ip); + commit_trans(); return -1; } iunlock(ip); iput(proc->cwd); + commit_trans(); proc->cwd = ip; return 0; } diff --git a/usertests.c b/usertests.c index 92b9136..5a78c7c 100644 --- a/usertests.c +++ b/usertests.c @@ -13,6 +13,106 @@ char name[3]; char *echoargv[] = { "echo", "ALL", "TESTS", "PASSED", 0 }; int stdout = 1; +// does chdir() call iput(p->cwd) in a transaction? +void +iputtest(void) +{ + printf(stdout, "iput test\n"); + + if(mkdir("iputdir") < 0){ + printf(stdout, "mkdir failed\n"); + exit(); + } + if(chdir("iputdir") < 0){ + printf(stdout, "chdir iputdir failed\n"); + exit(); + } + if(unlink("../iputdir") < 0){ + printf(stdout, "unlink ../iputdir failed\n"); + exit(); + } + if(chdir("/") < 0){ + printf(stdout, "chdir / failed\n"); + exit(); + } + printf(stdout, "iput test ok\n"); +} + +// does exit() call iput(p->cwd) in a transaction? +void +exitiputtest(void) +{ + int pid; + + printf(stdout, "exitiput test\n"); + + pid = fork(); + if(pid < 0){ + printf(stdout, "fork failed\n"); + exit(); + } + if(pid == 0){ + if(mkdir("iputdir") < 0){ + printf(stdout, "mkdir failed\n"); + exit(); + } + if(chdir("iputdir") < 0){ + printf(stdout, "child chdir failed\n"); + exit(); + } + if(unlink("../iputdir") < 0){ + printf(stdout, "unlink ../iputdir failed\n"); + exit(); + } + exit(); + } + wait(); + printf(stdout, "exitiput test ok\n"); +} + +// does the error path in open() for attempt to write a +// directory call iput() in a transaction? +// needs a hacked kernel that pauses just after the namei() +// call in sys_open(): +// if((ip = namei(path)) == 0) +// return -1; +// { +// int i; +// for(i = 0; i < 10000; i++) +// yield(); +// } +void +openiputtest(void) +{ + int pid; + + printf(stdout, "openiput test\n"); + if(mkdir("oidir") < 0){ + printf(stdout, "mkdir oidir failed\n"); + exit(); + } + pid = fork(); + if(pid < 0){ + printf(stdout, "fork failed\n"); + exit(); + } + if(pid == 0){ + int fd = open("oidir", O_RDWR); + if(fd >= 0){ + printf(stdout, "open directory for write succeeded\n"); + exit(); + } + exit(); + } + sleep(1); + if(unlink("oidir") != 0){ + printf(stdout, "unlink failed\n"); + exit(); + } + wait(); + printf(stdout, "openiput test ok\n"); +} + // simple file system tests void @@ -187,7 +287,7 @@ void dirtest(void) printf(stdout, "unlink dir0 failed\n"); exit(); } - printf(stdout, "mkdir test\n"); + printf(stdout, "mkdir test ok\n"); } void @@ -1628,6 +1728,10 @@ main(int argc, char *argv[]) writetest1(); createtest(); + openiputtest(); + exitiputtest(); + iputtest(); + mem(); pipe1(); preempt();