de-eclipseify
[nestedvm.git] / src / org / ibex / nestedvm / UnixRuntime.java
1 package org.ibex.nestedvm;
2
3 import org.ibex.nestedvm.util.*;
4 import java.io.*;
5 import java.util.*;
6
7 public abstract class UnixRuntime extends Runtime implements Cloneable {
8     /** The pid of this "process" */
9     private int pid;
10     private UnixRuntime parent;
11     public final int getPid() { return pid; }
12     
13     private static final GlobalState defaultGS = new GlobalState();
14     private GlobalState gs = defaultGS;
15     public void setGlobalState(GlobalState gs) {
16         if(state != STOPPED) throw new IllegalStateException("can't change GlobalState when running");
17         this.gs = gs;
18     }
19     
20     /** proceses' current working directory - absolute path WITHOUT leading slash
21         "" = root, "bin" = /bin "usr/bin" = /usr/bin */
22     private String cwd;
23     
24     /** The runtime that should be run next when in state == EXECED */
25     private UnixRuntime execedRuntime;
26
27     private Object children; // used only for synchronizatin
28     private Vector activeChildren;
29     private Vector exitedChildren;
30     
31     protected UnixRuntime(int pageSize, int totalPages) {
32         super(pageSize,totalPages);
33                 
34         // FEATURE: Do the proper mangling for non-unix hosts
35         String userdir = getSystemProperty("user.dir");
36         cwd = userdir != null && userdir.startsWith("/") && File.separatorChar == '/'  ? userdir.substring(1) : "";
37     }
38     
39     // NOTE: getDisplayName() is a Java2 function
40     private static String posixTZ() {
41         StringBuffer sb = new StringBuffer();
42         TimeZone zone = TimeZone.getDefault();
43         int off = zone.getRawOffset() / 1000;
44         sb.append(zone.getDisplayName(false,TimeZone.SHORT));
45         if(off > 0) sb.append("-");
46         else off = -off;
47         sb.append(off/3600); off = off%3600;
48         if(off > 0) sb.append(":").append(off/60); off=off%60;
49         if(off > 0) sb.append(":").append(off);
50         if(zone.useDaylightTime())
51             sb.append(zone.getDisplayName(true,TimeZone.SHORT));
52         return sb.toString();
53     }
54     
55     private static boolean envHas(String key,String[] environ) {
56         for(int i=0;i<environ.length;i++)
57             if(environ[i]!=null && environ[i].startsWith(key + "=")) return true;
58         return false;
59     }
60     
61     String[] createEnv(String[] extra) {
62         String[] defaults = new String[5];
63         int n=0;
64         if(extra == null) extra = new String[0];
65         if(!envHas("USER",extra) && getSystemProperty("user.name") != null)
66             defaults[n++] = "USER=" + getSystemProperty("user.name");
67         if(!envHas("HOME",extra) && getSystemProperty("user.home") != null)
68             defaults[n++] = "HOME=" + getSystemProperty("user.home");
69         if(!envHas("SHELL",extra)) defaults[n++] = "SHELL=/bin/sh";
70         if(!envHas("TERM",extra))  defaults[n++] = "TERM=vt100";
71         if(!envHas("TZ",extra))    defaults[n++] = "TZ=" + posixTZ();
72         String[] env = new String[extra.length+n];
73         for(int i=0;i<n;i++) env[i] = defaults[i];
74         for(int i=0;i<extra.length;i++) env[n++] = extra[i];
75         return env;
76     }
77     
78     private static class ProcessTableFullExn extends RuntimeException { }
79     
80     void _started() {
81         UnixRuntime[] tasks = gs.tasks;
82         synchronized(gs) {
83             if(pid != 0) {
84                 if(tasks[pid] == null || tasks[pid].pid != pid) throw new Error("should never happen");
85             } else {
86                 int newpid = -1;
87                 int nextPID = gs.nextPID;
88                     for(int i=nextPID;i<tasks.length;i++) if(tasks[i] == null) { newpid = i; break; }
89                     if(newpid == -1) for(int i=1;i<nextPID;i++) if(tasks[i] == null) { newpid = i; break; }
90                     if(newpid == -1) throw new ProcessTableFullExn();
91                     pid = newpid;
92                 gs.nextPID = newpid + 1;
93             }
94             tasks[pid] = this;
95         }
96     }
97     
98     int _syscall(int syscall, int a, int b, int c, int d) throws ErrnoException, FaultException {
99         switch(syscall) {
100             case SYS_kill: return sys_kill(a,b);
101             case SYS_fork: return sys_fork();
102             case SYS_pipe: return sys_pipe(a);
103             case SYS_dup2: return sys_dup2(a,b);
104             case SYS_waitpid: return sys_waitpid(a,b,c);
105             case SYS_stat: return sys_stat(a,b);
106             case SYS_lstat: return sys_lstat(a,b);
107             case SYS_mkdir: return sys_mkdir(a,b);
108             case SYS_getcwd: return sys_getcwd(a,b);
109             case SYS_chdir: return sys_chdir(a);
110             case SYS_exec: return sys_exec(a,b,c);
111             case SYS_getdents: return sys_getdents(a,b,c,d);
112
113             default: return super._syscall(syscall,a,b,c,d);
114         }
115     }
116     
117     FD _open(String path, int flags, int mode) throws ErrnoException {
118         return gs.open(this,normalizePath(path),flags,mode);
119     }
120
121     /** The kill syscall.
122        SIGSTOP, SIGTSTO, SIGTTIN, and SIGTTOUT pause the process.
123        SIGCONT, SIGCHLD, SIGIO, and SIGWINCH are ignored.
124        Anything else terminates the process. */
125     private int sys_kill(int pid, int signal) {
126         // This will only be called by raise() in newlib to invoke the default handler
127         // We don't have to worry about actually delivering the signal
128         if(pid != pid) return -ESRCH;
129         if(signal < 0 || signal >= 32) return -EINVAL;
130         switch(signal) {
131             case 0: return 0;
132             case 17: // SIGSTOP
133             case 18: // SIGTSTP
134             case 21: // SIGTTIN
135             case 22: // SIGTTOU
136                 state = PAUSED;
137                 break;
138             case 19: // SIGCONT
139             case 20: // SIGCHLD
140             case 23: // SIGIO
141             case 28: // SIGWINCH
142                 break;
143             default:
144                 return syscall(SYS_exit,128+signal,0,0,0);
145         }
146         return 0;
147     }
148
149     private int sys_waitpid(int pid, int statusAddr, int options) throws FaultException, ErrnoException {
150         final int WNOHANG = 1;
151         if((options & ~(WNOHANG)) != 0) return -EINVAL;
152         if(pid == 0 || pid < -1) {
153             if(STDERR_DIAG) System.err.println("WARNING: waitpid called with a pid of " + pid);
154             return -ECHILD;
155         }
156         boolean blocking = (options&WNOHANG)==0;
157         
158         if(pid !=-1 && (pid <= 0 || pid >= gs.tasks.length)) return -ECHILD;
159         if(children == null) return blocking ? -ECHILD : 0;
160         
161         UnixRuntime done = null;
162         
163         synchronized(children) {
164             for(;;) {
165                 if(pid == -1) {
166                     if(exitedChildren.size() > 0) done = (UnixRuntime)exitedChildren.remove(exitedChildren.size() - 1);
167                 } else if(pid > 0) {
168                     UnixRuntime t = gs.tasks[pid];
169                     if(t.parent != this) return -ECHILD;
170                     if(t.state == EXITED) {
171                         if(!exitedChildren.remove(t)) throw new Error("should never happen");
172                         done = t;
173                     }
174                 } else {
175                     // process group stuff, EINVAL returned above
176                         throw new Error("should never happen");
177                 }
178                 if(done == null) {
179                     if(!blocking) return 0;
180                     try { children.wait(); } catch(InterruptedException e) {}
181                     //System.err.println("waitpid woke up: " + exitedChildren.size());
182                 } else {
183                     gs.tasks[done.pid] = null;
184                     break;
185                 }
186             }
187         }
188         if(statusAddr!=0) memWrite(statusAddr,done.exitStatus()<<8);
189         return done.pid;
190     }
191     
192     
193     void _exited() {
194         if(children != null) synchronized(children) {
195             for(Enumeration e = exitedChildren.elements(); e.hasMoreElements(); ) {
196                 UnixRuntime child = (UnixRuntime) e.nextElement();
197                 gs.tasks[child.pid] = null;
198             }
199             exitedChildren.clear();
200             for(Enumeration e = activeChildren.elements(); e.hasMoreElements(); ) {
201                 UnixRuntime child = (UnixRuntime) e.nextElement();
202                 child.parent = null;
203             }
204             activeChildren.clear();
205         }
206         
207         UnixRuntime _parent = parent;
208         if(_parent == null) {
209             gs.tasks[pid] = null;
210         } else {
211             synchronized(_parent.children) {
212                 if(parent == null) {
213                     gs.tasks[pid] = null;
214                 } else {
215                     parent.activeChildren.remove(this);
216                     parent.exitedChildren.add(this);
217                     parent.children.notify();
218                 }
219             }
220         }
221     }
222     
223     protected Object clone() throws CloneNotSupportedException {
224         UnixRuntime r = (UnixRuntime) super.clone();
225         r.pid = 0;
226         r.parent = null;
227         r.children = null;
228         r.activeChildren = r.exitedChildren = null;
229         return r;
230     }
231
232     private int sys_fork() {
233         CPUState state = new CPUState();
234         getCPUState(state);
235         int sp = state.r[SP];
236         final UnixRuntime r;
237         
238         try {
239             r = (UnixRuntime) clone();
240         } catch(Exception e) {
241             e.printStackTrace();
242             return -ENOMEM;
243         }
244
245         r.parent = this;
246
247         try {
248             r._started();
249         } catch(ProcessTableFullExn e) {
250             return -ENOMEM;
251         }
252
253         //System.err.println("fork " + pid + " -> " + r.pid + " tasks[" + r.pid + "] = " + gd.tasks[r.pid]);
254         if(children == null) {
255             children = new Object();
256             activeChildren = new Vector();
257             exitedChildren = new Vector();
258         }
259         activeChildren.add(r);
260         
261         state.r[V0] = 0; // return 0 to child
262         state.pc += 4; // skip over syscall instruction
263         r.setCPUState(state);
264         r.state = PAUSED;
265         
266         new ForkedProcess(r);
267         
268         return r.pid;
269     }
270     
271     public static final class ForkedProcess extends Thread {
272         private final UnixRuntime initial;
273         public ForkedProcess(UnixRuntime initial) { this.initial = initial; start(); }
274         public void run() { UnixRuntime.executeAndExec(initial); }
275     }
276     
277     public static int runAndExec(UnixRuntime r, String argv0, String[] rest) { return runAndExec(r,concatArgv(argv0,rest)); }
278     public static int runAndExec(UnixRuntime r, String[] argv) { r.start(argv); return executeAndExec(r); }
279     
280     public static int executeAndExec(UnixRuntime r) {
281         for(;;) {
282             for(;;) {
283                 if(r.execute()) break;
284                 if(STDERR_DIAG) System.err.println("WARNING: Pause requested while executing runAndExec()");
285             }
286             if(r.state != EXECED) return r.exitStatus();
287             r = r.execedRuntime;
288         }
289     }
290      
291     private String[] readStringArray(int addr) throws ReadFaultException {
292         int count = 0;
293         for(int p=addr;memRead(p) != 0;p+=4) count++;
294         String[] a = new String[count];
295         for(int i=0,p=addr;i<count;i++,p+=4) a[i] = cstring(memRead(p));
296         return a;
297     }
298     
299     private int sys_exec(int cpath, int cargv, int cenvp) throws ErrnoException, FaultException {
300         return exec(normalizePath(cstring(cpath)),readStringArray(cargv),readStringArray(cenvp));
301     }
302         
303     private int exec(String normalizedPath, String[] argv, String[] envp) throws ErrnoException {
304         if(argv.length == 0) argv = new String[]{""};
305
306         Object o = gs.exec(this,normalizedPath);
307         if(o == null) return -ENOENT;
308
309         if(o instanceof Class) {
310             Class c = (Class) o;
311             try {
312                     return exec((UnixRuntime) c.newInstance(),argv,envp);
313             } catch(Exception e) {
314                     e.printStackTrace();
315                 return -ENOEXEC;
316             }
317         } else {
318             String[] command = (String[]) o;
319             String[] newArgv = new String[argv.length + command[1] != null ? 2 : 1];
320             int p = command[0].lastIndexOf('/');
321             newArgv[0] = p == -1 ? command[0] : command[0].substring(p+1);
322             p = 1;
323             if(command[1] != null) newArgv[p++] = command[1];
324             newArgv[p++] = "/" + normalizedPath;
325             for(int i=1;i<argv.length;i++) newArgv[p++] = argv[i];
326             return exec(command[0],newArgv,envp);
327         }
328     }
329     
330     private int exec(UnixRuntime r, String[] argv, String[] envp) {     
331         
332         //System.err.println("Execing " + r);
333         for(int i=0;i<OPEN_MAX;i++) if(closeOnExec[i]) closeFD(i);
334         r.fds = fds;
335         r.closeOnExec = closeOnExec;
336         // make sure this doesn't get messed with these since we didn't copy them
337         fds = null;
338         closeOnExec = null;
339         
340         r.gs = gs;
341         r.sm = sm;
342         r.cwd = cwd;
343         r.pid = pid;
344         r.parent = parent;
345         r.start(argv,envp);
346                 
347         state = EXECED;
348         execedRuntime = r;
349         
350         return 0;   
351     }
352     
353     // FEATURE: Make sure fstat info is correct
354     // FEATURE: This could be faster if we did direct copies from each process' memory
355     // FEATURE: Check this logic one more time
356     public static class Pipe {
357         private final byte[] pipebuf = new byte[PIPE_BUF*4];
358         private int readPos;
359         private int writePos;
360         
361         public final FD reader = new Reader();
362         public final FD writer = new Writer();
363         
364         public class Reader extends FD {
365             protected FStat _fstat() { return new FStat(); }
366             public int read(byte[] buf, int off, int len) throws ErrnoException {
367                 if(len == 0) return 0;
368                 synchronized(Pipe.this) {
369                     while(writePos != -1 && readPos == writePos) {
370                         try { Pipe.this.wait(); } catch(InterruptedException e) { /* ignore */ }
371                     }
372                     if(writePos == -1) return 0; // eof
373                     len = Math.min(len,writePos-readPos);
374                     System.arraycopy(pipebuf,readPos,buf,off,len);
375                     readPos += len;
376                     if(readPos == writePos) Pipe.this.notify();
377                     return len;
378                 }
379             }
380             public void _close() { synchronized(Pipe.this) { readPos = -1; Pipe.this.notify(); } }
381         }
382         
383         public class Writer extends FD {   
384             protected FStat _fstat() { return new FStat(); }
385             public int write(byte[] buf, int off, int len) throws ErrnoException {
386                 if(len == 0) return 0;
387                 synchronized(Pipe.this) {
388                     if(readPos == -1) throw new ErrnoException(EPIPE);
389                     if(pipebuf.length - writePos < Math.min(len,PIPE_BUF)) {
390                         // not enough space to atomicly write the data
391                         while(readPos != -1 && readPos != writePos) {
392                             try { Pipe.this.wait(); } catch(InterruptedException e) { /* ignore */ }
393                         }
394                         if(readPos == -1) throw new ErrnoException(EPIPE);
395                         readPos = writePos = 0;
396                     }
397                     len = Math.min(len,pipebuf.length - writePos);
398                     System.arraycopy(buf,off,pipebuf,writePos,len);
399                     if(readPos == writePos) Pipe.this.notify();
400                     writePos += len;
401                     return len;
402                 }
403             }
404             public void _close() { synchronized(Pipe.this) { writePos = -1; Pipe.this.notify(); } }
405         }
406     }
407     
408     private int sys_pipe(int addr) {
409         Pipe pipe = new Pipe();
410         
411         int fd1 = addFD(pipe.reader);
412         if(fd1 < 0) return -ENFILE;
413         int fd2 = addFD(pipe.writer);
414         if(fd2 < 0) { closeFD(fd1); return -ENFILE; }
415         
416         try {
417             memWrite(addr,fd1);
418             memWrite(addr+4,fd2);
419         } catch(FaultException e) {
420             closeFD(fd1);
421             closeFD(fd2);
422             return -EFAULT;
423         }
424         return 0;
425     }
426     
427     private int sys_dup2(int oldd, int newd) {
428         if(oldd == newd) return 0;
429         if(oldd < 0 || oldd >= OPEN_MAX) return -EBADFD;
430         if(newd < 0 || newd >= OPEN_MAX) return -EBADFD;
431         if(fds[oldd] == null) return -EBADFD;
432         if(fds[newd] != null) fds[newd].close();
433         fds[newd] = fds[oldd].dup();
434         return 0;
435     }
436     
437     private int sys_stat(int cstring, int addr) throws FaultException, ErrnoException {
438         FStat s = gs.stat(this,normalizePath(cstring(cstring)));
439         if(s == null) return -ENOENT;
440         return stat(s,addr);
441     }
442     
443     private int sys_lstat(int cstring, int addr) throws FaultException, ErrnoException {
444         FStat s = gs.lstat(this,normalizePath(cstring(cstring)));
445         if(s == null) return -ENOENT;
446         return stat(s,addr);
447     }
448     
449     private int sys_mkdir(int cstring, int mode) throws FaultException, ErrnoException {
450         gs.mkdir(this,normalizePath(cstring(cstring)),mode);
451         return 0;
452     }
453    
454     private int sys_getcwd(int addr, int size) throws FaultException, ErrnoException {
455         byte[] b = getBytes(cwd);
456         if(size == 0) return -EINVAL;
457         if(size < b.length+2) return -ERANGE;
458         memset(addr,'/',1);
459         copyout(b,addr+1,b.length);
460         memset(addr+b.length+1,0,1);
461         return addr;
462     }
463     
464     private int sys_chdir(int addr) throws ErrnoException, FaultException {
465         String path = normalizePath(cstring(addr));
466         //System.err.println("Chdir: " + cstring(addr) + " -> " + path + " pwd: " + cwd);
467         if(gs.stat(this,path).type() != FStat.S_IFDIR) return -ENOTDIR;
468         cwd = path;
469         //System.err.println("Now: [" + cwd + "]");
470         return 0;
471     }
472     
473     private int sys_getdents(int fdn, int addr, int count, int seekptr) throws FaultException, ErrnoException {
474         count = Math.min(count,MAX_CHUNK);
475         if(fdn < 0 || fdn >= OPEN_MAX) return -EBADFD;
476         if(fds[fdn] == null) return -EBADFD;
477         byte[] buf = byteBuf(count);
478         int n = fds[fdn].getdents(buf,0,count);
479         copyout(buf,addr,n);
480         return n;
481     }
482     
483     //  FEATURE: Run through the fork/wait stuff one more time
484     public static class GlobalState {    
485         protected static final int OPEN = 1;
486         protected static final int STAT = 2;
487         protected static final int LSTAT = 3;
488         protected static final int MKDIR = 4;
489         
490         final UnixRuntime[] tasks;
491         int nextPID = 1;
492         
493         private MP[] mps = new MP[0];
494         private FS root;
495         
496         public GlobalState() { this(255); }
497         public GlobalState(int maxProcs) { this(maxProcs,true); }
498         public GlobalState(int maxProcs, boolean defaultMounts) {
499             tasks = new UnixRuntime[maxProcs+1];
500             if(defaultMounts) {
501                 addMount("/",new HostFS());
502                 addMount("/dev",new DevFS());
503             }
504         }
505         
506         private static class MP {
507             public MP(String path, FS fs) { this.path = path; this.fs = fs; }
508             public String path;
509             public FS fs;
510             public int compareTo(Object o) {
511                 if(!(o instanceof MP)) return 1;
512                 return -path.compareTo(((MP)o).path);
513             }
514         }
515         
516         public synchronized FS getMount(String path) {
517             if(!path.startsWith("/")) throw new IllegalArgumentException("Mount point doesn't start with a /");
518             if(path.equals("/")) return root;
519             path  = path.substring(1);
520             for(int i=0;i<mps.length;i++)
521                 if(mps[i].path.equals(path)) return mps[i].fs;
522             return null;
523         }
524         
525         public synchronized void addMount(String path, FS fs) {
526             if(getMount(path) != null) throw new IllegalArgumentException("mount point already exists");
527             if(!path.startsWith("/")) throw new IllegalArgumentException("Mount point doesn't start with a /");
528             
529             if(fs.owner != null) fs.owner.removeMount(fs);
530             fs.owner = this;
531             
532             if(path.equals("/")) { root = fs; fs.devno = 1; return; }
533             path = path.substring(1);
534             int oldLength = mps.length;
535             MP[] newMPS = new MP[oldLength + 1];
536             if(oldLength != 0) System.arraycopy(mps,0,newMPS,0,oldLength);
537             newMPS[oldLength] = new MP(path,fs);
538             Arrays.sort(newMPS);
539             mps = newMPS;
540             int highdevno = 0;
541             for(int i=0;i<mps.length;i++) highdevno = max(highdevno,mps[i].fs.devno);
542             fs.devno = highdevno + 2;
543         }
544         
545         public synchronized void removeMount(FS fs) {
546             for(int i=0;i<mps.length;i++) if(mps[i].fs == fs) { removeMount(i); return; }
547             throw new IllegalArgumentException("mount point doesn't exist");
548         }
549         
550         public synchronized void removeMount(String path) {
551             if(!path.startsWith("/")) throw new IllegalArgumentException("Mount point doesn't start with a /");
552             if(path.equals("/")) {
553                 removeMount(-1);
554             } else {
555                 path = path.substring(1);
556                 int p;
557                 for(p=0;p<mps.length;p++) if(mps[p].path.equals(path)) break;
558                 if(p == mps.length) throw new IllegalArgumentException("mount point doesn't exist");
559                 removeMount(p);
560             }
561         }
562         
563         private void removeMount(int index) {
564             if(index == -1) { root.owner = null; root = null; return; }
565             MP[] newMPS = new MP[mps.length - 1];
566             System.arraycopy(mps,0,newMPS,0,index);
567             System.arraycopy(mps,0,newMPS,index,mps.length-index-1);
568             mps = newMPS;
569         }
570         
571         private Object fsop(int op, UnixRuntime r, String normalizedPath, int arg1, int arg2) throws ErrnoException {
572             int pl = normalizedPath.length();
573             if(pl != 0) {
574                 MP[] list;
575                 synchronized(this) { list = mps; }
576                 for(int i=0;i<list.length;i++) {
577                     MP mp = list[i];
578                     int mpl = mp.path.length();
579                     if(normalizedPath.startsWith(mp.path) && (pl == mpl || (pl < mpl && normalizedPath.charAt(mpl) == '/')))
580                         return dispatch(mp.fs,op,r,pl == mpl ? "" : normalizedPath.substring(mpl+1),arg1,arg2);
581                 }
582             }
583             return dispatch(root,op,r,normalizedPath,arg1,arg2);
584         }
585         
586         private static Object dispatch(FS fs, int op, UnixRuntime r, String path, int arg1, int arg2) throws ErrnoException {
587             switch(op) {
588                 case OPEN: return fs.open(r,path,arg1,arg2);
589                 case STAT: return fs.stat(r,path);
590                 case LSTAT: return fs.lstat(r,path);
591                 case MKDIR: fs.mkdir(r,path,arg1); return null;
592                 default: throw new Error("should never happen");
593             }
594         }
595         
596         public final FD open(UnixRuntime r, String path, int flags, int mode) throws ErrnoException { return (FD) fsop(OPEN,r,path,flags,mode); }
597         public final FStat stat(UnixRuntime r, String path) throws ErrnoException { return (FStat) fsop(STAT,r,path,0,0); }
598         public final FStat lstat(UnixRuntime r, String path) throws ErrnoException { return (FStat) fsop(LSTAT,r,path,0,0); }
599         public final void mkdir(UnixRuntime r, String path, int mode) throws ErrnoException { fsop(MKDIR,r,path,mode,0); }
600         
601         private Hashtable execCache = new Hashtable();
602         private static class CacheEnt {
603             public final long time;
604             public final long size;
605             public final Object o;
606             public CacheEnt(long time, long size, Object o) { this.time = time; this.size = size; this.o = o; }
607         }
608
609         public synchronized Object exec(UnixRuntime r, String path) throws ErrnoException {
610             FStat fstat = stat(r,path);
611             if(fstat == null) return null;
612             long mtime = fstat.mtime();
613             long size = fstat.size();
614             CacheEnt ent = (CacheEnt) execCache.get(path);
615             if(ent != null) {
616                 //System.err.println("Found cached entry for " + path);
617                 if(ent.time == mtime && ent.size == size) return ent.o;
618                 //System.err.println("Cache was out of date");
619                 execCache.remove(path);
620             }
621             FD fd = open(r,path,RD_ONLY,0);
622             if(fd == null) return null;
623             Seekable s = fd.seekable();
624             
625             String[] command  = null;
626
627             if(s == null) throw new ErrnoException(EACCES);
628             byte[] buf = new byte[4096];
629             
630             try {
631                 int n = s.read(buf,0,buf.length);
632                 if(n == -1) throw new ErrnoException(ENOEXEC);
633                 
634                 switch(buf[0]) {
635                     case '\177': // possible ELF
636                         if(n < 4 && s.tryReadFully(buf,n,4-n) != 4-n) throw new ErrnoException(ENOEXEC);
637                         if(buf[1] != 'E' || buf[2] != 'L' || buf[3] != 'F') throw new ErrnoException(ENOEXEC);
638                         break;
639                     case '#':
640                         if(n == 1) {
641                             int n2 = s.read(buf,1,buf.length-1);
642                             if(n2 == -1) throw new ErrnoException(ENOEXEC);
643                             n += n2;
644                         }
645                         if(buf[1] != '!') throw new ErrnoException(ENOEXEC);
646                         int p = 2;
647                         n -= 2;
648                         OUTER: for(;;) {
649                             for(int i=p;i<p+n;i++) if(buf[i] == '\n') { p = i; break OUTER; }
650                             p += n;
651                             if(p == buf.length) break OUTER;
652                             n = s.read(buf,p,buf.length-p);
653                         }
654                         command = new String[2];
655                         int arg;
656                         for(arg=2;arg<p;arg++) if(buf[arg] == ' ') break;
657                         if(arg < p) {
658                             int cmdEnd = arg;
659                             while(arg < p && buf[arg] == ' ') arg++;
660                             command[0] = new String(buf,2,cmdEnd);
661                             command[1] = arg < p ? new String(buf,arg,p-arg) : null;
662                         } else {
663                             command[0] = new String(buf,2,p-2);
664                         }
665                         //System.err.println("command[0]: " + command[0] + " command[1]: " + command[1]);
666                         break;
667                     default:
668                         throw new ErrnoException(ENOEXEC);
669                 }
670             } catch(IOException e) {
671                 fd.close();
672                 throw new ErrnoException(EIO);
673             }
674                         
675             if(command == null) {
676                 // its an elf binary
677                 try {
678                     s.seek(0);
679                     Class c = RuntimeCompiler.compile(s);
680                     //System.err.println("Compile succeeded: " + c);
681                     ent = new CacheEnt(mtime,size,c);
682                 } catch(Compiler.Exn e) {
683                     if(STDERR_DIAG) e.printStackTrace();
684                     throw new ErrnoException(ENOEXEC);
685                 } catch(IOException e) {
686                     if(STDERR_DIAG) e.printStackTrace();
687                     throw new ErrnoException(EIO);
688                 }
689             } else {
690                 ent = new CacheEnt(mtime,size,command);
691             }
692             
693             fd.close();
694             
695             execCache.put(path,ent);
696             return ent.o;
697         }
698     }
699     
700     public abstract static class FS {
701         GlobalState owner;
702         int devno;
703         
704         public FStat lstat(UnixRuntime r, String path) throws ErrnoException { return stat(r,path); }
705
706         // If this returns null it'll be truned into an ENOENT
707         public abstract FD open(UnixRuntime r, String path, int flags, int mode) throws ErrnoException;
708         // If this returns null it'll be turned into an ENOENT
709         public abstract FStat stat(UnixRuntime r, String path) throws ErrnoException;
710         public abstract void mkdir(UnixRuntime r, String path, int mode) throws ErrnoException;
711     }
712         
713     // FEATURE: chroot support in here
714     private String normalizePath(String path) {
715         boolean absolute = path.startsWith("/");
716         int cwdl = cwd.length();
717         // NOTE: This isn't just a fast path, it handles cases the code below doesn't
718         if(!path.startsWith(".") && path.indexOf("./") == -1 && path.indexOf("//") == -1 && !path.endsWith("."))
719             return absolute ? path.substring(1) : cwdl == 0 ? path : path.length() == 0 ? cwd : cwd + "/" + path;
720         
721         char[] in = new char[path.length()+1];
722         char[] out = new char[in.length + (absolute ? -1 : cwd.length())];
723         int inp=0, outp=0;
724         
725         if(absolute) {
726             do { inp++; } while(in[inp] == '/');
727         } else if(cwdl != 0) {
728             cwd.getChars(0,cwdl,out,0);
729             outp = cwdl;
730         }
731             
732         path.getChars(0,path.length(),in,0);
733         while(in[inp] != 0) {
734             if(inp != 0) {
735                 if(in[inp] != '/') { out[outp++] = in[inp++]; continue; }
736                 while(in[inp] == '/') inp++;
737             }
738             if(in[inp] == '\0') continue;
739             if(in[inp] != '.') { out[outp++] = '/'; out[outp++] = in[inp++]; continue; }
740             if(in[inp+1] == '\0' || in[inp+1] == '/') { inp++; continue; }
741             if(in[inp+1] == '.' && (in[inp+2] == '\0' || in[inp+2] == '/')) { // ..
742                 inp += 2;
743                 if(outp > 0) outp--;
744                 while(outp > 0 && out[outp] != '/') outp--;
745                 //System.err.println("After ..: " + new String(out,0,outp));
746                 continue;
747             }
748             inp++;
749             out[outp++] = '/';
750             out[outp++] = '.';
751         }
752         if(outp > 0 && out[outp-1] == '/') outp--;
753         //System.err.println("normalize: " + path + " -> " + new String(out,0,outp) + " (cwd: " + cwd + ")");
754         return new String(out,0,outp);
755     }
756     
757     FStat hostFStat(final File f, Object data) {
758         boolean e = false;
759         try {
760             FileInputStream fis = new FileInputStream(f);
761             switch(fis.read()) {
762                 case '\177': e = fis.read() == 'E' && fis.read() == 'L' && fis.read() == 'F'; break;
763                 case '#': e = fis.read() == '!';
764             }
765             fis.close();
766         } catch(IOException e2) { } 
767         HostFS fs = (HostFS) data;
768         final int inode = fs.inodes.get(f.getAbsolutePath());
769         final int devno = fs.devno;
770         return new HostFStat(f,e) {
771             public int inode() { return inode; }
772             public int dev() { return devno; }
773         };
774     }
775
776     FD hostFSDirFD(File f, Object _fs) {
777         HostFS fs = (HostFS) _fs;
778         return fs.new HostDirFD(f);
779     }
780     
781     public static class HostFS extends FS {
782         InodeCache inodes = new InodeCache(4000);
783         protected File root;
784         public File getRoot() { return root; }
785         
786         private static File hostRootDir() {
787             String cwd = getSystemProperty("user.dir");
788             File f = new File(cwd != null ? cwd : ".");
789             f = new File(f.getAbsolutePath());
790             while(f.getParent() != null) f = new File(f.getParent());
791             return f;
792         }
793         
794         private File hostFile(String path) {
795             char sep = File.separatorChar;
796             if(sep != '/') {
797                 char buf[] = path.toCharArray();
798                 for(int i=0;i<buf.length;i++) {
799                     char c = buf[i];
800                     if(c == '/') buf[i] = sep;
801                     else if(c == sep) buf[i] = '/';
802                 }
803                 path = new String(buf);
804             }
805             return new File(root,path);
806         }
807         
808         public HostFS() { this(hostRootDir()); }
809         public HostFS(String root) { this(new File(root)); }
810         public HostFS(File root) { this.root = root; }
811         
812         
813         public FD open(UnixRuntime r, String path, int flags, int mode) throws ErrnoException {
814             final File f = hostFile(path);
815             return r.hostFSOpen(f,flags,mode,this);
816         }
817         
818         public FStat stat(UnixRuntime r, String path) throws ErrnoException {
819             File f = hostFile(path);
820             if(r.sm != null && !r.sm.allowStat(f)) throw new ErrnoException(EACCES);
821             if(!f.exists()) return null;
822             return r.hostFStat(f,this);
823         }
824         
825         public void mkdir(UnixRuntime r, String path, int mode) throws ErrnoException {
826             File f = hostFile(path);
827             if(r.sm != null && !r.sm.allowWrite(f)) throw new ErrnoException(EACCES);
828             if(f.exists() && f.isDirectory()) throw new ErrnoException(EEXIST);
829             if(f.exists()) throw new ErrnoException(ENOTDIR);
830             File parent = f.getParentFile();
831             if(parent!=null && (!parent.exists() || !parent.isDirectory())) throw new ErrnoException(ENOTDIR);
832             if(!f.mkdir()) throw new ErrnoException(EIO);            
833         }
834         
835         public class HostDirFD extends DirFD {
836             private final File f;
837             private final File[] children;
838             public HostDirFD(File f) { this.f = f; children = f.listFiles(); }
839             public int size() { return children.length; }
840             public String name(int n) { return children[n].getName(); }
841             public int inode(int n) { return inodes.get(children[n].getAbsolutePath()); }
842             public int parentInode() {
843                 File parent = f.getParentFile();
844                 return parent == null ? -1 : inodes.get(parent.getAbsolutePath());
845             }
846             public int myInode() { return inodes.get(f.getAbsolutePath()); }
847             public int myDev() { return devno; } 
848         }
849     }
850     
851     private static void putInt(byte[] buf, int off, int n) {
852         buf[off+0] = (byte)((n>>>24)&0xff);
853         buf[off+1] = (byte)((n>>>16)&0xff);
854         buf[off+2] = (byte)((n>>> 8)&0xff);
855         buf[off+3] = (byte)((n>>> 0)&0xff);
856     }
857     
858     public static abstract class DirFD extends FD {
859         private int pos = -2;
860         
861         protected abstract int size();
862         protected abstract String name(int n);
863         protected abstract int inode(int n);
864         protected abstract int myDev();
865         protected int parentInode() { return -1; }
866         protected int myInode() { return -1; }
867         
868         public int getdents(byte[] buf, int off, int len) {
869             int ooff = off;
870             int ino;
871             int reclen;
872             OUTER: for(;len > 0 && pos < size();pos++){
873                 switch(pos) {
874                     case -2:
875                     case -1:
876                         ino = pos == -1 ? parentInode() : myInode();
877                         if(ino == -1) continue;
878                         reclen = 9 + (pos == -1 ? 2 : 1);
879                         if(reclen > len) break OUTER;
880                         buf[off+8] = '.';
881                         if(pos == -1) buf[off+9] = '.';
882                         break;
883                     default: {
884                         String f = name(pos);
885                         byte[] fb = getBytes(f);
886                         reclen = fb.length + 9;
887                         if(reclen > len) break OUTER;
888                         ino = inode(pos);
889                         System.arraycopy(fb,0,buf,off+8,fb.length);
890                     }
891                 }
892                 buf[off+reclen-1] = 0; // null terminate
893                 reclen = (reclen + 3) & ~3; // add padding
894                 putInt(buf,off,reclen);
895                 putInt(buf,off+4,ino);
896                 off += reclen;
897                 len -= reclen;    
898             }
899             return off-ooff;
900         }
901         
902         protected FStat _fstat() {
903             return new FStat() { 
904                 public int type() { return S_IFDIR; }
905                 public int inode() { return myInode(); }
906                 public int dev() { return myDev(); }
907             };
908         }
909     }
910         
911     public static class DevFS extends FS {
912         private static final int ROOT_INODE = 1;
913         private static final int NULL_INODE = 2;
914         private static final int ZERO_INODE = 3;
915         private static final int FD_INODE = 4;
916         private static final int FD_INODES = 32;
917         
918         private class DevFStat extends FStat {
919             public int dev() { return devno; }
920             public int mode() { return 0666; }
921             public int type() { return S_IFCHR; }
922             public int nlink() { return 1; }
923         }
924         
925         private abstract class DevDirFD extends DirFD {
926             public int myDev() { return devno; }
927         }
928         
929         private FD devZeroFD = new FD() {
930             public boolean readable() { return true; }
931             public boolean writable() { return true; }
932             public int read(byte[] a, int off, int length) { Arrays.fill(a,off,off+length,(byte)0); return length; }
933             public int write(byte[] a, int off, int length) { return length; }
934             public int seek(int n, int whence) { return 0; }
935             public FStat _fstat() { return new DevFStat(){ public int inode() { return ZERO_INODE; } }; }
936         };
937         private FD devNullFD = new FD() {
938             public boolean readable() { return true; }
939             public boolean writable() { return true; }
940             public int read(byte[] a, int off, int length) { return 0; }
941             public int write(byte[] a, int off, int length) { return length; }
942             public int seek(int n, int whence) { return 0; }
943             public FStat _fstat() { return new DevFStat(){ public int inode() { return NULL_INODE; } }; }
944         }; 
945         
946         public FD open(UnixRuntime r, String path, int mode, int flags) throws ErrnoException {
947             if(path.equals("null")) return devNullFD;
948             if(path.equals("zero")) return devZeroFD;
949             if(path.startsWith("fd/")) {
950                 int n;
951                 try {
952                     n = Integer.parseInt(path.substring(4));
953                 } catch(NumberFormatException e) {
954                     return null;
955                 }
956                 if(n < 0 || n >= OPEN_MAX) return null;
957                 if(r.fds[n] == null) return null;
958                 return r.fds[n].dup();
959             }
960             if(path.equals("fd")) {
961                 int count=0;
962                 for(int i=0;i<OPEN_MAX;i++) if(r.fds[i] != null) { count++; }
963                 final int[] files = new int[count];
964                 count = 0;
965                 for(int i=0;i<OPEN_MAX;i++) if(r.fds[i] != null) files[count++] = i;
966                 return new DevDirFD() {
967                     public int myInode() { return FD_INODE; }
968                     public int parentInode() { return ROOT_INODE; }
969                     public int inode(int n) { return FD_INODES + n; }
970                     public String name(int n) { return Integer.toString(files[n]); }
971                     public int size() { return files.length; }
972                 };
973             }
974             if(path.equals("")) {
975                 return new DevDirFD() {
976                     public int myInode() { return ROOT_INODE; }
977                     // FEATURE: Get the real parent inode somehow
978                     public int parentInode() { return -1; }
979                     public int inode(int n) {
980                         switch(n) {
981                             case 0: return NULL_INODE;
982                             case 1: return ZERO_INODE;
983                             case 2: return FD_INODE;
984                             default: return -1;
985                         }
986                     }
987                     
988                     public String name(int n) {
989                         switch(n) {
990                             case 0: return "null";
991                             case 1: return "zero";
992                             case 2: return "fd";
993                             default: return null;
994                         }
995                     }
996                     public int size() { return 3; }
997                 };
998             }
999             return null;
1000         }
1001         
1002         public FStat stat(UnixRuntime r,String path) throws ErrnoException {
1003             if(path.equals("null")) return devNullFD.fstat();
1004             if(path.equals("zero")) return devZeroFD.fstat();            
1005             if(path.startsWith("fd/")) {
1006                 int n;
1007                 try {
1008                     n = Integer.parseInt(path.substring(4));
1009                 } catch(NumberFormatException e) {
1010                     return null;
1011                 }
1012                 if(n < 0 || n >= OPEN_MAX) return null;
1013                 if(r.fds[n] == null) return null;
1014                 return r.fds[n].fstat();
1015             }
1016             // FEATURE: inode stuff
1017             if(path.equals("fd")) return new FStat() { public int type() { return S_IFDIR; } public int mode() { return 0444; }};
1018             if(path.equals("")) return new FStat() { public int type() { return S_IFDIR; } public int mode() { return 0444; }};
1019             return null;
1020         }
1021         
1022         public void mkdir(UnixRuntime r, String path, int mode) throws ErrnoException { throw new ErrnoException(EACCES); }
1023     }
1024 }