imported brians code
[nestedvm.git] / src / org / xwt / mips / UnixRuntime.java
1 package org.xwt.mips;
2
3 import org.xwt.mips.util.*;
4 import java.io.*;
5 import java.util.*;
6
7 public abstract class UnixRuntime extends Runtime {
8     /** The pid of this "process" */
9     private int pid;
10     private int ppid;
11     protected int getPid() { return pid; }
12     
13     /** processes filesystem */
14     private FS fs;
15     public FS getFS() { return fs; }
16     public void setFS(FS fs) {
17         if(state >= RUNNING) throw new IllegalStateException("Can't change fs while process is running");
18         this.fs = fs;
19     }
20     
21     /** proceses current working directory */
22     private String cwd;
23     
24     /* Static stuff */
25     // FEATURE: Most of this is O(n) or worse - fix it
26     private final Object waitNotification = new Object();
27     private final static int MAX_TASKS = 256;
28     private final static UnixRuntime[] tasks = new UnixRuntime[MAX_TASKS];
29     private static int addTask(UnixRuntime rt) {
30         synchronized(tasks) {
31             for(int i=1;i<MAX_TASKS;i++) {
32                 if(tasks[i] == null) {
33                     tasks[i] = rt;
34                     rt.pid = i;
35                     return i;
36                 }
37             }
38             return -1;
39         }
40     }
41     private static void removeTask(UnixRuntime rt) {
42         synchronized(tasks) {
43             for(int i=1;i<MAX_TASKS;i++)
44                 if(tasks[i] == rt) { tasks[i] = null; break; }
45         }
46     }
47     
48     public UnixRuntime(int pageSize, int totalPages, boolean allowEmptyPages) {
49         super(pageSize,totalPages,allowEmptyPages);
50         
51         HostFS root = new HostFS();
52         fs = new UnixOverlayFS(root);
53         
54         String dir = root.hostCWD();
55         try {
56             chdir(dir == null ? "/" : dir);
57         } catch(FileNotFoundException e) {
58             e.printStackTrace();
59             cwd = "/";
60         }
61     }
62     
63     private static String posixTZ() {
64         StringBuffer sb = new StringBuffer();
65         TimeZone zone = TimeZone.getDefault();
66         int off = zone.getRawOffset() / 1000;
67         sb.append(zone.getDisplayName(false,TimeZone.SHORT));
68         if(off > 0) sb.append("-");
69         else off = -off;
70         sb.append(off/3600); off = off%3600;
71         if(off > 0) sb.append(":").append(off/60); off=off%60;
72         if(off > 0) sb.append(":").append(off);
73         if(zone.useDaylightTime())
74             sb.append(zone.getDisplayName(true,TimeZone.SHORT));
75         return sb.toString();
76     }
77     
78     private static boolean envHas(String key,String[] environ) {
79         for(int i=0;i<environ.length;i++)
80             if(environ[i]!=null && environ[i].startsWith(key + "=")) return true;
81         return false;
82     }
83     
84     protected String[] createEnv(String[] extra) {
85         String[] defaults = new String[5];
86         int n=0;
87         if(extra == null) extra = new String[0];
88         if(!envHas("USER",extra) && getSystemProperty("user.name") != null)
89             defaults[n++] = "USER=" + getSystemProperty("user.name");
90         if(!envHas("HOME",extra) && getSystemProperty("user.name") != null)
91             defaults[n++] = "HOME=" + getSystemProperty("user.home");
92         if(!envHas("SHELL",extra)) defaults[n++] = "SHELL=/bin/sh";
93         if(!envHas("TERM",extra))  defaults[n++] = "TERM=vt100";
94         if(!envHas("TZ",extra))    defaults[n++] = "TZ=" + posixTZ();
95         String[] env = new String[extra.length+n];
96         for(int i=0;i<n;i++) env[i] = defaults[i];
97         for(int i=0;i<extra.length;i++) env[n++] = extra[i];
98         return env;
99     }
100     
101     protected void _start() {
102         if(addTask(this) < 0) throw new Error("Task list full");
103     }
104     
105     protected void _exit() {
106         synchronized(tasks) {
107             if(ppid == 0) removeTask(this);
108             for(int i=0;i<MAX_TASKS;i++) {
109                 if(tasks[i] != null && tasks[i].ppid == pid) {
110                     if(tasks[i].state == DONE) removeTask(tasks[i]);
111                     else tasks[i].ppid = 0;
112                 }
113             }
114             state = DONE;
115             if(ppid != 0) synchronized(tasks[ppid].waitNotification) { tasks[ppid].waitNotification.notify(); }
116         }
117     }
118
119     protected int syscall(int syscall, int a, int b, int c, int d) {
120         switch(syscall) {
121             case SYS_kill: return sys_kill(a,b);
122             case SYS_fork: return sys_fork();
123             case SYS_pipe: return sys_pipe(a);
124             case SYS_dup2: return sys_dup2(a,b);
125             case SYS_waitpid: return sys_waitpid(a,b,c);
126             case SYS_stat: return sys_stat(a,b);
127             case SYS_mkdir: return sys_mkdir(a,b);
128             case SYS_getcwd: return sys_getcwd(a,b);
129             case SYS_chdir: return sys_chdir(a);
130
131             default: return super.syscall(syscall,a,b,c,d);
132         }
133     }
134     
135     protected FD open(String path, int flags, int mode) throws IOException { return fs.open(cleanupPath(path),flags,mode); }
136
137     // FEATURE: Allow simple, broken signal delivery to other processes 
138     // (check if a signal was delivered before and after syscalls)
139     // FEATURE: Implement raise() in terms of call("raise",...) - kinda cheap, but it keeps the complexity in newlib
140     /** The kill syscall.
141        SIGSTOP, SIGTSTO, SIGTTIN, and SIGTTOUT pause the process.
142        SIGCONT, SIGCHLD, SIGIO, and SIGWINCH are ignored.
143        Anything else terminates the process. */
144     private int sys_kill(int pid, int signal) {
145         // This will only be called by raise() in newlib to invoke the default handler
146         // We don't have to worry about actually delivering the signal
147         if(pid != pid) return -ESRCH;
148         if(signal < 0 || signal >= 32) return -EINVAL;
149         switch(signal) {
150             case 0: return 0;
151             case 17: // SIGSTOP
152             case 18: // SIGTSTP
153             case 21: // SIGTTIN
154             case 22: // SIGTTOU
155                 state = PAUSED;
156                 break;
157             case 19: // SIGCONT
158             case 20: // SIGCHLD
159             case 23: // SIGIO
160             case 28: // SIGWINCH
161                 break;
162             default: {
163                 String msg = "Terminating on signal: " + signal + "\n";
164                 exitStatus = 1;
165                 state = DONE;
166                 if(fds[2]==null) {
167                     System.out.print(msg);
168                 } else {
169                     try {
170                         byte[] b = getBytes(msg); 
171                         fds[2].write(b,0,b.length);
172                     }
173                     catch(IOException e) { /* ignore */ }
174                 }
175             }
176         }
177         return 0;
178     }
179
180     private int sys_waitpid(int pid, int statusAddr, int options) {
181         final int WNOHANG = 1;
182         if((options & ~(WNOHANG)) != 0) return -EINVAL;
183         if(pid !=-1 && (pid <= 0 || pid >= MAX_TASKS)) return -ECHILD;
184         for(;;) {
185             synchronized(tasks) {
186                 UnixRuntime task = null;
187                 if(pid == -1) {
188                     for(int i=0;i<MAX_TASKS;i++) {
189                         if(tasks[i] != null && tasks[i].ppid == this.pid && tasks[i].state == DONE) {
190                             task = tasks[i];
191                             break;
192                         }
193                     }
194                 } else if(tasks[pid] != null && tasks[pid].ppid == this.pid && tasks[pid].state == DONE) {
195                     task = tasks[pid];
196                 }
197                 
198                 if(task != null) {
199                     removeTask(task);
200                     try {
201                         if(statusAddr!=0) memWrite(statusAddr,task.exitStatus()<<8);
202                     } catch(FaultException e) {
203                         return -EFAULT;
204                     }
205
206                     return task.pid;
207                 }
208             }
209             if((options&WNOHANG)!=0) return 0;
210             synchronized(waitNotification) {
211                 try { waitNotification.wait(); } catch(InterruptedException e) { throw new Error(e); }
212             }
213         }
214     }
215     
216     // Great ugliness lies within.....
217     private int sys_fork() {
218         CPUState state = getCPUState();
219         int sp = state.r[SP];
220         final UnixRuntime r;
221         try {
222             r = (UnixRuntime) getClass().newInstance();
223         } catch(Exception e) {
224             System.err.println(e);
225             return -ENOMEM;
226         }
227         int child_pid = addTask(r);
228         if(child_pid < 0) return -ENOMEM;
229         
230         r.ppid = pid;
231         r.brkAddr = brkAddr;
232         r.fds = new FD[OPEN_MAX];
233         for(int i=0;i<OPEN_MAX;i++) if(fds[i] != null) r.fds[i] = fds[i].dup();
234         r.cwd = cwd;
235         r.fs = fs;
236         for(int i=0;i<TOTAL_PAGES;i++) {
237             if(readPages[i] == null) continue;
238             if(isEmptyPage(writePages[i])) {
239                 r.readPages[i] = r.writePages[i] = writePages[i];
240             } else if(writePages[i] != null) {
241                 r.readPages[i] = r.writePages[i] = new int[PAGE_WORDS];
242                 if(STACK_BOTTOM == 0 || i*PAGE_SIZE < STACK_BOTTOM || i*PAGE_SIZE >= sp-PAGE_SIZE*2)
243                     System.arraycopy(writePages[i],0,r.writePages[i],0,PAGE_WORDS);
244             } else {
245                 r.readPages[i] = r.readPages[i];
246             }
247         }
248         state.r[V0] = 0;
249         state.pc += 4;
250         r.setCPUState(state);
251         r.state = PAUSED;
252         
253         new Thread() {
254             public void run() {
255                 try {
256                     while(!r.execute());
257                 } catch(Exception e) {
258                     System.err.println("Forked process threw exception: ");
259                     e.printStackTrace();
260                 }
261             }
262         }.start();
263         
264         return child_pid;        
265     }
266             
267     private int sys_pipe(int addr) {
268         PipedOutputStream writerStream = new PipedOutputStream();
269         PipedInputStream readerStream;
270         try {
271              readerStream = new PipedInputStream(writerStream);
272         } catch(IOException e) {
273             return -EIO;
274         }
275         FD reader = new InputStreamFD(readerStream);
276         FD writer = new OutputStreamFD(writerStream);
277         int fd1 = addFD(reader);
278         if(fd1 < 0) return -ENFILE;
279         int fd2 = addFD(writer);
280         if(fd2 < 0) { closeFD(fd1); return -ENFILE; }
281         try {
282             memWrite(addr,fd1);
283             memWrite(addr+4,fd2);
284         } catch(FaultException e) {
285             closeFD(fd1);
286             closeFD(fd2);
287             return -EFAULT;
288         }
289         return 0;
290     }
291     
292     private int sys_dup2(int oldd, int newd) {
293         if(oldd == newd) return 0;
294         if(oldd < 0 || oldd >= OPEN_MAX) return -EBADFD;
295         if(newd < 0 || newd >= OPEN_MAX) return -EBADFD;
296         if(fds[oldd] == null) return -EBADFD;
297         if(fds[newd] != null) fds[newd].close();
298         fds[newd] = fds[oldd].dup();
299         return 0;
300     }
301     
302     private int sys_stat(int cstring, int addr) {
303         try {
304             String path = cleanupPath(cstring(cstring));
305             return stat(fs.stat(path),addr);
306         }
307         catch(ErrnoException e) { return -e.errno; }
308         catch(FileNotFoundException e) {
309             if(e.getMessage() != null && e.getMessage().indexOf("Permission denied") >= 0) return -EACCES;
310             return -ENOENT;
311         }
312         catch(IOException e) { return -EIO; }
313         catch(FaultException e) { return -EFAULT; }
314     }
315     
316     
317     private int sys_mkdir(int cstring, int mode) {
318         try {
319             fs.mkdir(cleanupPath(cstring(cstring)));
320             return 0;
321         }
322         catch(ErrnoException e) { return -e.errno; }
323         catch(FileNotFoundException e) { return -ENOENT; }
324         catch(IOException e) { return -EIO; }
325         catch(FaultException e) { return -EFAULT; }
326     }
327    
328     
329     private int sys_getcwd(int addr, int size) {
330         byte[] b = getBytes(cwd);
331         if(size == 0) return -EINVAL;
332         if(size < b.length+1) return -ERANGE;
333         if(!new File(cwd).exists()) return -ENOENT;
334         try {
335             copyout(b,addr,b.length);
336             memset(addr+b.length+1,0,1);
337             return addr;
338         } catch(FaultException e) {
339             return -EFAULT;
340         }
341     }
342     
343     private int sys_chdir(int addr) {
344         try {
345             String path = cleanupPath(cstring(addr));
346             System.err.println("Chdir: " + cstring(addr) + " -> " + path + " pwd: " + cwd);
347             if(fs.stat(path).type() != FStat.S_IFDIR) return -ENOTDIR;
348             cwd = path;
349             System.err.println("Now: " + cwd);
350             return 0;
351         }
352         catch(ErrnoException e) { return -e.errno; }
353         catch(FileNotFoundException e) { return -ENOENT; }
354         catch(IOException e) { return -EIO; }
355         catch(FaultException e) { return -EFAULT; }
356     }
357
358     public void chdir(String dir) throws FileNotFoundException {
359         if(state >= RUNNING) throw new IllegalStateException("Can't chdir while process is running");
360         try {
361             dir = cleanupPath(dir);
362             if(fs.stat(dir).type() != FStat.S_IFDIR) throw new FileNotFoundException();
363         } catch(IOException e) {
364             throw new FileNotFoundException();
365         }
366         cwd = dir;
367     }
368         
369     public abstract static class FS {
370         public FD open(String path, int flags, int mode) throws IOException { throw new FileNotFoundException(); }
371         public FStat stat(String path) throws IOException { throw new FileNotFoundException(); }
372         public void mkdir(String path) throws IOException { throw new ErrnoException(ENOTDIR); }
373         
374         public static FD directoryFD(String[] files, int hashCode) throws IOException {
375             ByteArrayOutputStream bos = new ByteArrayOutputStream();
376             DataOutputStream dos = new DataOutputStream(bos);
377             for(int i=0;i<files.length;i++) {
378                 byte[] b = getBytes(files[i]);
379                 int inode = (files[i].hashCode() ^ hashCode) & 0xfffff;
380                 dos.writeInt(inode);
381                 dos.writeInt(b.length);
382                 dos.write(b,0,b.length);
383             }
384             final byte[] data = bos.toByteArray();
385             return new SeekableFD(new SeekableByteArray(data,false),RD_ONLY) {
386                 protected FStat _fstat() { return  new FStat() {
387                     public int length() { return data.length; }
388                     public int type() { return S_IFDIR; }
389                 }; }
390             };
391         }
392     }
393         
394     public static void main(String[] args) throws Exception {
395         UnixRuntime rt = new Interpreter();
396         rt.cwd = getSystemProperty("user.dir");
397         BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
398         String line;
399         while((line = br.readLine()) != null) {
400             System.out.println("[" + rt.cleanupPath(line) + "]");
401         }
402     }
403     
404     
405     private static boolean needsCleanup(String path) {
406         if(path.indexOf("//") != -1) return true;
407         if(path.indexOf('.') != -1) {
408             if(path.length() == 1) return true;
409             if(path.equals("..")) return true;
410             if(path.startsWith("./")  || path.indexOf("/./")  != -1 || path.endsWith("/."))  return true;
411             if(path.startsWith("../") || path.indexOf("/../") != -1 || path.endsWith("/..")) return true;
412         }
413         return false;
414     }
415     
416     // FIXME: This is probably still buggy
417     // FEATURE: Remove some of the "should never happen checks"
418     protected String cleanupPath(String p) throws ErrnoException {
419         if(p.length() == 0) throw new ErrnoException(ENOENT);
420         if(needsCleanup(p)) {
421             char[] in = p.toCharArray();
422             char[] out;
423             int outp ;
424             if(in[0] == '/') {
425                 out = new char[in.length];
426                 outp = 0;
427             } else {
428                 out = new char[cwd.length() + in.length + 1];
429                 outp = cwd.length();
430                 for(int i=0;i<outp;i++) out[i] = cwd.charAt(i);
431                 if(outp == 0 || out[0] != '/') throw new Error("should never happen");
432             }
433             int inLength = in.length;
434             int inp = 0;
435             while(inp<inLength) {
436                 if(inp == 0 || in[inp] == '/') {
437                     while(inp < inLength && in[inp] == '/') inp++;
438                     if(inp == inLength) break;
439                     if(in[inp] == '.') {
440                         if(inp+1 == inLength) break;
441                         if(in[inp+1] == '.' && (inp+2 == inLength || in[inp+2] == '/')) {
442                             inp+=2;
443                             if(outp == 0) continue;
444                             do { outp--; } while(outp > 0 && out[outp] != '/');
445                         } else if(in[inp+1] == '/') {
446                             inp++;
447                         } else {
448                             out[outp++] = '/';
449                         }
450                     } else {
451                         out[outp++] = '/';
452                         out[outp++] = in[inp++];
453                     }
454                 } else {
455                     out[outp++] = in[inp++];
456                 }
457             }
458             if(outp == 0) out[outp++] = '/';
459             return new String(out,0,outp);
460         } else {
461             if(p.startsWith("/")) return p;
462             StringBuffer sb = new StringBuffer(cwd);
463             if(!cwd.equals("/")) sb.append('/');
464             return sb.append(p).toString();
465         }
466     }
467     
468     // FEATURE: Probably should make this more general - support mountpoints, etc
469     public class UnixOverlayFS extends FS {
470         private final FS root;
471         private final FS dev = new DevFS();
472         public UnixOverlayFS(FS root) {
473             this.root = root;
474         }
475         private String devPath(String path) {
476             if(path.startsWith("/dev")) {
477                 if(path.length() == 4) return "/";
478                 if(path.charAt(4) == '/') return path.substring(4);
479             }
480             return null;
481         }
482         public FD open(String path, int flags, int mode) throws IOException{
483             String dp = devPath(path);
484             return dp == null ? root.open(path,flags,mode) : dev.open(dp,flags,mode);
485         }
486         public FStat stat(String path) throws IOException {
487             String dp = devPath(path);
488             return dp == null ? root.stat(path) : dev.stat(dp);
489         }
490         public void mkdir(String path) throws IOException {
491             String dp = devPath(path);
492             if(dp == null) root.mkdir(path);
493             else dev.mkdir(dp);
494         }
495     }
496     
497     // FIXME: This is totally broken on non-unix hosts - need to do some kind of cygwin type mapping
498     public static class HostFS extends FS {
499         public static String fixPath(String path) throws FileNotFoundException {
500             return path;
501         }
502         
503         public String hostCWD() {
504             return getSystemProperty("user.dir");
505         }
506         
507         // FEATURE: This shares a lot with Runtime.open
508         public FD open(String path, int flags, int mode) throws IOException {
509             path = fixPath(path);
510             final File f = new File(path);
511             // NOTE: createNewFile is a Java2 function
512             if((flags & (O_EXCL|O_CREAT)) == (O_EXCL|O_CREAT))
513                 if(!f.createNewFile()) throw new ErrnoException(EEXIST);
514             if(!f.exists() && (flags&O_CREAT) == 0) return null;
515             if(f.isDirectory()) {
516                 if((flags&3)!=RD_ONLY) throw new ErrnoException(EACCES);
517                 return directoryFD(f.list(),path.hashCode());
518             }
519             final SeekableFile sf = new SeekableFile(path,(flags&3)!=RD_ONLY);
520             if((flags&O_TRUNC)!=0) sf.setLength(0);
521             return new SeekableFD(sf,mode) {
522                 protected FStat _fstat() { return new HostFStat(f) {
523                     public int size() {
524                         try { return sf.length(); } catch(IOException e) { return 0; }
525                     }
526                 };}
527             };
528         }
529         
530         public FStat stat(String path) throws FileNotFoundException {
531             File f = new File(fixPath(path));
532             if(!f.exists()) throw new FileNotFoundException();
533             return new HostFStat(f);
534         }
535         
536         public void mkdir(String path) throws IOException {
537             File f = new File(fixPath(path));
538             if(f.exists() && f.isDirectory()) throw new ErrnoException(EEXIST);
539             if(f.exists()) throw new ErrnoException(ENOTDIR);
540             File parent = f.getParentFile();
541             if(parent!=null && (!parent.exists() || !parent.isDirectory())) throw new ErrnoException(ENOTDIR);
542             if(!f.mkdir()) throw new ErrnoException(EIO);            
543         }
544     }
545     
546     private static class DevFStat extends FStat {
547         public int dev() { return 1; }
548         public int mode() { return 0666; }
549         public int type() { return S_IFCHR; }
550         public int nlink() { return 1; }
551     }
552     private static FD devZeroFD = new FD() {
553         public boolean readable() { return true; }
554         public boolean writable() { return true; }
555         public int read(byte[] a, int off, int length) { Arrays.fill(a,off,off+length,(byte)0); return length; }
556         public int write(byte[] a, int off, int length) { return length; }
557         public int seek(int n, int whence) { return 0; }
558         public FStat _fstat() { return new DevFStat(); }
559     };
560     private static FD devNullFD = new FD() {
561         public boolean readable() { return true; }
562         public boolean writable() { return true; }
563         public int read(byte[] a, int off, int length) { return 0; }
564         public int write(byte[] a, int off, int length) { return length; }
565         public int seek(int n, int whence) { return 0; }
566         public FStat _fstat() { return new DevFStat(); }
567     };    
568     
569     public class DevFS extends FS {
570         public FD open(String path, int mode, int flags) throws IOException {
571             if(path.equals("/null")) return devNullFD;
572             if(path.equals("/zero")) return devZeroFD;
573             if(path.startsWith("/fd/")) {
574                 int n;
575                 try {
576                     n = Integer.parseInt(path.substring(4));
577                 } catch(NumberFormatException e) {
578                     throw new FileNotFoundException();
579                 }
580                 if(n < 0 || n >= OPEN_MAX) throw new FileNotFoundException();
581                 if(fds[n] == null) throw new FileNotFoundException();
582                 return fds[n].dup();
583             }
584             if(path.equals("/fd")) {
585                 int count=0;
586                 for(int i=0;i<OPEN_MAX;i++) if(fds[i] != null) count++; 
587                 String[] files = new String[count];
588                 count = 0;
589                 for(int i=0;i<OPEN_MAX;i++) if(fds[i] != null) files[count++] = Integer.toString(i);
590                 return directoryFD(files,hashCode());
591             }
592             if(path.equals("/")) {
593                 String[] files = { "null", "zero", "fd" };
594                 return directoryFD(files,hashCode());
595             }
596             throw new FileNotFoundException();
597         }
598         
599         public FStat stat(String path) throws IOException {
600             if(path.equals("/null")) return devNullFD.fstat();
601             if(path.equals("/zero")) return devZeroFD.fstat();            
602             if(path.startsWith("/fd/")) {
603                 int n;
604                 try {
605                     n = Integer.parseInt(path.substring(4));
606                 } catch(NumberFormatException e) {
607                     throw new FileNotFoundException();
608                 }
609                 if(n < 0 || n >= OPEN_MAX) throw new FileNotFoundException();
610                 if(fds[n] == null) throw new FileNotFoundException();
611                 return fds[n].fstat();
612             }
613             if(path.equals("/fd")) return new FStat() { public int type() { return S_IFDIR; } public int mode() { return 0444; }};
614             if(path.equals("/")) return new FStat() { public int type() { return S_IFDIR; } public int mode() { return 0444; }};
615             throw new FileNotFoundException();
616         }
617         
618         public void mkdir(String path) throws IOException { throw new ErrnoException(EACCES); }
619     }
620 }