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