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