X-Git-Url: http://git.megacz.com/?p=nestedvm.git;a=blobdiff_plain;f=src%2Forg%2Fibex%2Fnestedvm%2FUnixRuntime.java;h=051a50be063926a90b68c1f811487254a09ae4d2;hp=3befb91aeaf744fe096c22a9630b73c0d631af36;hb=c59b7cfc7a6b67574d38c5c8eb7732bad37236b0;hpb=c2b2704764af1ade923ba8f15d517b87f9d16189 diff --git a/src/org/ibex/nestedvm/UnixRuntime.java b/src/org/ibex/nestedvm/UnixRuntime.java index 3befb91..051a50b 100644 --- a/src/org/ibex/nestedvm/UnixRuntime.java +++ b/src/org/ibex/nestedvm/UnixRuntime.java @@ -4,60 +4,37 @@ import org.ibex.nestedvm.util.*; import java.io.*; import java.util.*; -public abstract class UnixRuntime extends Runtime { +// FEATURE: Remove System.{out,err}.printlns and throw Errors where applicable + +// FEATURE: BusyBox's ASH doesn't like \r\n at the end of lines +// is ash just broken or are many apps like this? if so workaround in nestedvm + +public abstract class UnixRuntime extends Runtime implements Cloneable { /** The pid of this "process" */ private int pid; - private int ppid; + private UnixRuntime parent; protected int getPid() { return pid; } - /** processes filesystem */ - private FS fs; - public FS getFS() { return fs; } - public void setFS(FS fs) { - if(state >= RUNNING) throw new IllegalStateException("Can't change fs while process is running"); - this.fs = fs; - } + private static final GlobalState defaultGD = new GlobalState(); + private GlobalState gd = defaultGD; - /** proceses current working directory */ + /** proceses' current working directory - absolute path WITHOUT leading slash + "" = root, "bin" = /bin "usr/bin" = /usr/bin */ private String cwd; - /* Static stuff */ - // FEATURE: Most of this is O(n) or worse - fix it - private final Object waitNotification = new Object(); - private final static int MAX_TASKS = 256; - private final static UnixRuntime[] tasks = new UnixRuntime[MAX_TASKS]; - private static int addTask(UnixRuntime rt) { - synchronized(tasks) { - for(int i=1;i= MAX_TASKS)) return -ECHILD; - for(;;) { - synchronized(tasks) { - UnixRuntime task = null; + if(pid == 0 || pid < -1) { + System.err.println("WARNING: waitpid called with a pid of " + pid); + return -ECHILD; + } + boolean blocking = (options&WNOHANG)==0; + + if(pid !=-1 && (pid <= 0 || pid >= gd.tasks.length)) return -ECHILD; + if(children == null) return blocking ? -ECHILD : 0; + + UnixRuntime done = null; + + synchronized(children) { + for(;;) { if(pid == -1) { - for(int i=0;i 0) done = (UnixRuntime)exitedChildren.remove(exitedChildren.size() - 1); + } else if(pid > 0) { + UnixRuntime t = gd.tasks[pid]; + if(t.parent != this) return -ECHILD; + if(t.state == EXITED) { + if(!exitedChildren.remove(t)) throw new Error("should never happen"); + done = t; } - } else if(tasks[pid] != null && tasks[pid].ppid == this.pid && tasks[pid].state == DONE) { - task = tasks[pid]; + } else { + // process group stuff, EINVAL returned above + throw new Error("should never happen"); } - - if(task != null) { - removeTask(task); - try { - if(statusAddr!=0) memWrite(statusAddr,task.exitStatus()<<8); - } catch(FaultException e) { - return -EFAULT; - } - - return task.pid; + if(done == null) { + if(!blocking) return 0; + try { children.wait(); } catch(InterruptedException e) {} + System.err.println("waitpid woke up: " + exitedChildren.size()); + } else { + gd.tasks[done.pid] = null; + break; } } - if((options&WNOHANG)!=0) return 0; - synchronized(waitNotification) { - try { waitNotification.wait(); } catch(InterruptedException e) { /* ignore */ } + } + if(statusAddr!=0) memWrite(statusAddr,done.exitStatus()<<8); + return done.pid; + } + + + protected void _exited() { + if(children != null) synchronized(children) { + for(Enumeration e = exitedChildren.elements(); e.hasMoreElements(); ) { + UnixRuntime child = (UnixRuntime) e.nextElement(); + gd.tasks[child.pid] = null; + } + exitedChildren.clear(); + for(Enumeration e = activeChildren.elements(); e.hasMoreElements(); ) { + UnixRuntime child = (UnixRuntime) e.nextElement(); + child.parent = null; + } + activeChildren.clear(); + } + + UnixRuntime _parent = parent; + if(_parent == null) { + gd.tasks[pid] = null; + } else { + synchronized(_parent.children) { + if(parent == null) { + gd.tasks[pid] = null; + } else { + parent.activeChildren.remove(this); + parent.exitedChildren.add(this); + parent.children.notify(); + } } } } - // Great ugliness lies within..... + protected Object clone() throws CloneNotSupportedException { + UnixRuntime r = (UnixRuntime) super.clone(); + r.pid = 0; + r.parent = null; + r.children = null; + r.activeChildren = r.exitedChildren = null; + return r; + } + private int sys_fork() { - CPUState state = getCPUState(); + CPUState state = new CPUState(); + getCPUState(state); int sp = state.r[SP]; final UnixRuntime r; + try { - r = (UnixRuntime) getClass().newInstance(); + r = (UnixRuntime) clone(); } catch(Exception e) { - System.err.println(e); + e.printStackTrace(); return -ENOMEM; } - int child_pid = addTask(r); - if(child_pid < 0) return -ENOMEM; - - r.ppid = pid; - r.brkAddr = brkAddr; - r.fds = new FD[OPEN_MAX]; - for(int i=0;i= sp-PAGE_SIZE*2) - System.arraycopy(writePages[i],0,r.writePages[i],0,PAGE_WORDS); - } else { - r.readPages[i] = r.readPages[i]; - } + + r.parent = this; + + try { + r._started(); + } catch(ProcessTableFullExn e) { + return -ENOMEM; + } + + System.err.println("fork " + pid + " -> " + r.pid + " tasks[" + r.pid + "] = " + gd.tasks[r.pid]); + if(children == null) { + children = new Object(); + activeChildren = new Vector(); + exitedChildren = new Vector(); } - state.r[V0] = 0; - state.pc += 4; + activeChildren.add(r); + + state.r[V0] = 0; // return 0 to child + state.pc += 4; // skip over syscall instruction r.setCPUState(state); r.state = PAUSED; - new Thread() { - public void run() { - try { - while(!r.execute()); - } catch(Exception e) { - System.err.println("Forked process threw exception: "); - e.printStackTrace(); - } + new ForkedProcess(r); + + return r.pid; + } + + public static final class ForkedProcess extends Thread { + private final UnixRuntime initial; + public ForkedProcess(UnixRuntime initial) { this.initial = initial; start(); } + public void run() { UnixRuntime.executeAndExec(initial); } + } + + public static int runAndExec(UnixRuntime r, String argv0, String[] rest) { return runAndExec(r,concatArgv(argv0,rest)); } + public static int runAndExec(UnixRuntime r, String[] argv) { r.start(argv); return executeAndExec(r); } + + public static int executeAndExec(UnixRuntime r) { + for(;;) { + for(;;) { + if(r.execute()) break; + System.err.println("WARNING: Pause requested while executing runAndExec()"); } - }.start(); + if(r.state != EXECED) return r.exitStatus(); + r = r.execedRuntime; + } + } + + private String[] readStringArray(int addr) throws ReadFaultException { + int count = 0; + for(int p=addr;memRead(p) != 0;p+=4) count++; + String[] a = new String[count]; + for(int i=0,p=addr;i= 0) return -EACCES; - return -ENOENT; - } - catch(IOException e) { return -EIO; } - catch(FaultException e) { return -EFAULT; } + private int sys_stat(int cstring, int addr) throws FaultException, ErrnoException { + FStat s = gd.stat(this,normalizePath(cstring(cstring))); + if(s == null) return -ENOENT; + return stat(s,addr); } + private int sys_lstat(int cstring, int addr) throws FaultException, ErrnoException { + FStat s = gd.lstat(this,normalizePath(cstring(cstring))); + if(s == null) return -ENOENT; + return stat(s,addr); + } - private int sys_mkdir(int cstring, int mode) { - try { - fs.mkdir(cleanupPath(cstring(cstring))); - return 0; - } - catch(ErrnoException e) { return -e.errno; } - catch(FileNotFoundException e) { return -ENOENT; } - catch(IOException e) { return -EIO; } - catch(FaultException e) { return -EFAULT; } + private int sys_mkdir(int cstring, int mode) throws FaultException, ErrnoException { + gd.mkdir(this,normalizePath(cstring(cstring)),mode); + return 0; } - private int sys_getcwd(int addr, int size) { + private int sys_getcwd(int addr, int size) throws FaultException, ErrnoException { byte[] b = getBytes(cwd); if(size == 0) return -EINVAL; - if(size < b.length+1) return -ERANGE; - if(!new File(cwd).exists()) return -ENOENT; - try { - copyout(b,addr,b.length); - memset(addr+b.length+1,0,1); - return addr; - } catch(FaultException e) { - return -EFAULT; - } + if(size < b.length+2) return -ERANGE; + memset(addr,'/',1); + copyout(b,addr+1,b.length); + memset(addr+b.length+1,0,1); + return addr; } - private int sys_chdir(int addr) { - try { - String path = cleanupPath(cstring(addr)); - System.err.println("Chdir: " + cstring(addr) + " -> " + path + " pwd: " + cwd); - if(fs.stat(path).type() != FStat.S_IFDIR) return -ENOTDIR; - cwd = path; - System.err.println("Now: " + cwd); - return 0; - } - catch(ErrnoException e) { return -e.errno; } - catch(FileNotFoundException e) { return -ENOENT; } - catch(IOException e) { return -EIO; } - catch(FaultException e) { return -EFAULT; } + private int sys_chdir(int addr) throws ErrnoException, FaultException { + String path = normalizePath(cstring(addr)); + System.err.println("Chdir: " + cstring(addr) + " -> " + path + " pwd: " + cwd); + if(gd.stat(this,path).type() != FStat.S_IFDIR) return -ENOTDIR; + cwd = path; + System.err.println("Now: [" + cwd + "]"); + return 0; } + + // FEATURE: Run through the fork/wait stuff one more time + public static class GlobalState { + protected static final int OPEN = 1; + protected static final int STAT = 2; + protected static final int LSTAT = 3; + protected static final int MKDIR = 4; + + // Make this protected (need to fix _exit) + private final UnixRuntime[] tasks; + private int nextPID = 1; + + private final MP[][] mps = new MP[128][]; + private FS root; + + public GlobalState() { this(255); } + public GlobalState(int maxProcs) { this(maxProcs,true); } + public GlobalState(int maxProcs, boolean defaultMounts) { + tasks = new UnixRuntime[maxProcs+1]; + if(defaultMounts) { + root = new HostFS(); + addMount("/dev",new DevFS()); + } + } + + private static class MP { + public MP(String path, FS fs) { this.path = path; this.fs = fs; } + public String path; + public FS fs; + public int compareTo(Object o) { + if(!(o instanceof MP)) return 1; + return -path.compareTo(((MP)o).path); + } + } + + public synchronized FS getMount(String path) { + if(!path.startsWith("/")) throw new IllegalArgumentException("Mount point doesn't start with a /"); + if(path.equals("/")) return root; + path = path.substring(1); + int f = path.charAt(0) & 0x7f; + for(int i=0;mps[f] != null && i < mps[f].length;i++) + if(mps[f][i].path.equals(path)) return mps[f][i].fs; + return null; + } + + public synchronized void addMount(String path, FS fs) { + if(getMount(path) != null) throw new IllegalArgumentException("mount point already exists"); + if(!path.startsWith("/")) throw new IllegalArgumentException("Mount point doesn't start with a /"); + if(path.equals("/")) { root = fs; return; } + path = path.substring(1); + int f = path.charAt(0) & 0x7f; + int oldLength = mps[f] == null ? 0 : mps[f].length; + MP[] newList = new MP[oldLength + 1]; + if(oldLength != 0) System.arraycopy(mps[f],0,newList,0,oldLength); + newList[oldLength] = new MP(path,fs); + Arrays.sort(newList); + mps[f] = newList; + } + + public synchronized void removeMount(String path) { + if(getMount(path) == null) throw new IllegalArgumentException("mount point doesn't exist"); + if(!path.startsWith("/")) throw new IllegalArgumentException("Mount point doesn't start with a /"); + if(path.equals("/")) { root = null; return; } + path = path.substring(1); + int f = path.charAt(0) & 0x7f; + MP[] oldList = mps[f]; + MP[] newList = new MP[oldList.length - 1]; + int p = 0; + for(p=0;p= RUNNING) throw new IllegalStateException("Can't chdir while process is running"); - try { - dir = cleanupPath(dir); - if(fs.stat(dir).type() != FStat.S_IFDIR) throw new FileNotFoundException(); - } catch(IOException e) { - throw new FileNotFoundException(); + public synchronized Object exec(UnixRuntime r, String path) throws ErrnoException { + FStat fstat = stat(r,path); + if(fstat == null) return null; + long mtime = fstat.mtime(); + long size = fstat.size(); + CacheEnt ent = (CacheEnt) execCache.get(path); + if(ent != null) { + System.err.println("Found cached entry for " + path); + if(ent.time == mtime && ent.size == size) return ent.o; + System.err.println("Cache was out of date"); + execCache.remove(path); + } + FD fd = open(r,path,RD_ONLY,0); + if(fd == null) return null; + Seekable s = fd.seekable(); + + String[] command = null; + + if(s == null) throw new ErrnoException(EACCES); + byte[] buf = new byte[4096]; + + try { + int n = s.read(buf,0,buf.length); + if(n == -1) throw new Error("zero length file"); + + switch(buf[0]) { + case '\177': // possible ELF + if(n < 4 && s.tryReadFully(buf,n,4-n) != 4-n) throw new ErrnoException(ENOEXEC); + if(buf[1] != 'E' || buf[2] != 'L' || buf[3] != 'F') throw new ErrnoException(ENOEXEC); + break; + case '#': + if(n == 1) { + int n2 = s.read(buf,1,buf.length-1); + if(n2 == -1) throw new ErrnoException(ENOEXEC); + n += n2; + } + if(buf[1] != '!') throw new ErrnoException(ENOEXEC); + int p = 2; + n -= 2; + OUTER: for(;;) { + for(int i=p;i 0 && out[outp] != '/'); - } else if(in[inp+1] == '/') { - inp++; - } else { - out[outp++] = '/'; - } - } else { - out[outp++] = '/'; - out[outp++] = in[inp++]; - } - } else { - out[outp++] = in[inp++]; - } + if(in[inp] == '\0') continue; + if(in[inp] != '.') { out[outp++] = '/'; out[outp++] = in[inp++]; continue; } + if(in[inp+1] == '\0' || in[inp+1] == '/') { inp++; continue; } + if(in[inp+1] == '.' && (in[inp+2] == '\0' || in[inp+2] == '/')) { // .. + inp += 2; + if(outp > 0) outp--; + while(outp > 0 && out[outp] != '/') outp--; + System.err.println("After ..: " + new String(out,0,outp)); + continue; } - if(outp == 0) out[outp++] = '/'; - return new String(out,0,outp); - } else { - if(p.startsWith("/")) return p; - StringBuffer sb = new StringBuffer(cwd); - if(!cwd.equals("/")) sb.append('/'); - return sb.append(p).toString(); - } - } - - // FEATURE: Probably should make this more general - support mountpoints, etc - public class UnixOverlayFS extends FS { - private final FS root; - private final FS dev = new DevFS(); - public UnixOverlayFS(FS root) { - this.root = root; - } - private String devPath(String path) { - if(path.startsWith("/dev")) { - if(path.length() == 4) return "/"; - if(path.charAt(4) == '/') return path.substring(4); - } - return null; - } - public FD open(String path, int flags, int mode) throws IOException{ - String dp = devPath(path); - return dp == null ? root.open(path,flags,mode) : dev.open(dp,flags,mode); - } - public FStat stat(String path) throws IOException { - String dp = devPath(path); - return dp == null ? root.stat(path) : dev.stat(dp); - } - public void mkdir(String path) throws IOException { - String dp = devPath(path); - if(dp == null) root.mkdir(path); - else dev.mkdir(dp); + inp++; + out[outp++] = '/'; + out[outp++] = '.'; } + if(outp > 0 && out[outp-1] == '/') outp--; + //System.err.println("normalize: " + path + " -> " + new String(out,0,outp) + " (cwd: " + cwd + ")"); + return new String(out,0,outp); } + + // FEATURE: override Runtime.hostFStat to add executable type checking + // hostFStat + /* try { + FileInputStream fis = new FileInputStream(f); + switch(fis.read()) { + case '\177': _executable = fis.read() == 'E' && fis.read() == 'L' && fis.read() == 'F'; break; + case '#': _executable = fis.read() == '!'; + } + fis.close(); + } catch(IOException e) { } + */ + + // FEATURE: inode stuff + FD hostFSDirFD(File f) { return FS.directoryFD(f.list(),f.hashCode()); } - // FIXME: This is totally broken on non-unix hosts - need to do some kind of cygwin type mapping public static class HostFS extends FS { - public static String fixPath(String path) throws FileNotFoundException { - return path; - } + protected File root; + public File getRoot() { return root; } - public String hostCWD() { - return getSystemProperty("user.dir"); + private static File hostRootDir() { + String cwd = getSystemProperty("user.dir"); + File f = new File(cwd != null ? cwd : "."); + f = new File(f.getAbsolutePath()); + while(f.getParent() != null) f = new File(f.getParent()); + return f; } - // FEATURE: This shares a lot with Runtime.open - public FD open(String path, int flags, int mode) throws IOException { - path = fixPath(path); - final File f = new File(path); - // NOTE: createNewFile is a Java2 function - if((flags & (O_EXCL|O_CREAT)) == (O_EXCL|O_CREAT)) - if(!f.createNewFile()) throw new ErrnoException(EEXIST); - if(!f.exists() && (flags&O_CREAT) == 0) return null; - if(f.isDirectory()) { - if((flags&3)!=RD_ONLY) throw new ErrnoException(EACCES); - return directoryFD(f.list(),path.hashCode()); + private File hostFile(String path) { + char sep = File.separatorChar; + if(sep != '/') { + char buf[] = path.toCharArray(); + for(int i=0;i= OPEN_MAX) throw new FileNotFoundException(); - if(fds[n] == null) throw new FileNotFoundException(); - return fds[n].dup(); + if(n < 0 || n >= OPEN_MAX) return null; + if(r.fds[n] == null) return null; + return r.fds[n].dup(); } - if(path.equals("/fd")) { + if(path.equals("fd")) { int count=0; - for(int i=0;i= OPEN_MAX) throw new FileNotFoundException(); - if(fds[n] == null) throw new FileNotFoundException(); - return fds[n].fstat(); + if(n < 0 || n >= OPEN_MAX) return null; + if(r.fds[n] == null) return null; + return r.fds[n].fstat(); } - if(path.equals("/fd")) return new FStat() { public int type() { return S_IFDIR; } public int mode() { return 0444; }}; - if(path.equals("/")) return new FStat() { public int type() { return S_IFDIR; } public int mode() { return 0444; }}; - throw new FileNotFoundException(); + if(path.equals("fd")) return new FStat() { public int type() { return S_IFDIR; } public int mode() { return 0444; }}; + if(path.equals("")) return new FStat() { public int type() { return S_IFDIR; } public int mode() { return 0444; }}; + return null; } - public void mkdir(String path) throws IOException { throw new ErrnoException(EACCES); } + public void mkdir(UnixRuntime r, String path, int mode) throws ErrnoException { throw new ErrnoException(EACCES); } } }