get rid of useless main() func
[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) { /* ignore */ }
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     private static boolean needsCleanup(String path) {
396         if(path.indexOf("//") != -1) return true;
397         if(path.indexOf('.') != -1) {
398             if(path.length() == 1) return true;
399             if(path.equals("..")) return true;
400             if(path.startsWith("./")  || path.indexOf("/./")  != -1 || path.endsWith("/."))  return true;
401             if(path.startsWith("../") || path.indexOf("/../") != -1 || path.endsWith("/..")) return true;
402         }
403         return false;
404     }
405     
406     // FIXME: This is probably still buggy
407     // FEATURE: Remove some of the "should never happen checks"
408     protected String cleanupPath(String p) throws ErrnoException {
409         if(p.length() == 0) throw new ErrnoException(ENOENT);
410         if(needsCleanup(p)) {
411             char[] in = p.toCharArray();
412             char[] out;
413             int outp ;
414             if(in[0] == '/') {
415                 out = new char[in.length];
416                 outp = 0;
417             } else {
418                 out = new char[cwd.length() + in.length + 1];
419                 outp = cwd.length();
420                 for(int i=0;i<outp;i++) out[i] = cwd.charAt(i);
421                 if(outp == 0 || out[0] != '/') throw new Error("should never happen");
422             }
423             int inLength = in.length;
424             int inp = 0;
425             while(inp<inLength) {
426                 if(inp == 0 || in[inp] == '/') {
427                     while(inp < inLength && in[inp] == '/') inp++;
428                     if(inp == inLength) break;
429                     if(in[inp] == '.') {
430                         if(inp+1 == inLength) break;
431                         if(in[inp+1] == '.' && (inp+2 == inLength || in[inp+2] == '/')) {
432                             inp+=2;
433                             if(outp == 0) continue;
434                             do { outp--; } while(outp > 0 && out[outp] != '/');
435                         } else if(in[inp+1] == '/') {
436                             inp++;
437                         } else {
438                             out[outp++] = '/';
439                         }
440                     } else {
441                         out[outp++] = '/';
442                         out[outp++] = in[inp++];
443                     }
444                 } else {
445                     out[outp++] = in[inp++];
446                 }
447             }
448             if(outp == 0) out[outp++] = '/';
449             return new String(out,0,outp);
450         } else {
451             if(p.startsWith("/")) return p;
452             StringBuffer sb = new StringBuffer(cwd);
453             if(!cwd.equals("/")) sb.append('/');
454             return sb.append(p).toString();
455         }
456     }
457     
458     // FEATURE: Probably should make this more general - support mountpoints, etc
459     public class UnixOverlayFS extends FS {
460         private final FS root;
461         private final FS dev = new DevFS();
462         public UnixOverlayFS(FS root) {
463             this.root = root;
464         }
465         private String devPath(String path) {
466             if(path.startsWith("/dev")) {
467                 if(path.length() == 4) return "/";
468                 if(path.charAt(4) == '/') return path.substring(4);
469             }
470             return null;
471         }
472         public FD open(String path, int flags, int mode) throws IOException{
473             String dp = devPath(path);
474             return dp == null ? root.open(path,flags,mode) : dev.open(dp,flags,mode);
475         }
476         public FStat stat(String path) throws IOException {
477             String dp = devPath(path);
478             return dp == null ? root.stat(path) : dev.stat(dp);
479         }
480         public void mkdir(String path) throws IOException {
481             String dp = devPath(path);
482             if(dp == null) root.mkdir(path);
483             else dev.mkdir(dp);
484         }
485     }
486     
487     // FIXME: This is totally broken on non-unix hosts - need to do some kind of cygwin type mapping
488     public static class HostFS extends FS {
489         public static String fixPath(String path) throws FileNotFoundException {
490             return path;
491         }
492         
493         public String hostCWD() {
494             return getSystemProperty("user.dir");
495         }
496         
497         // FEATURE: This shares a lot with Runtime.open
498         public FD open(String path, int flags, int mode) throws IOException {
499             path = fixPath(path);
500             final File f = new File(path);
501             // NOTE: createNewFile is a Java2 function
502             if((flags & (O_EXCL|O_CREAT)) == (O_EXCL|O_CREAT))
503                 if(!f.createNewFile()) throw new ErrnoException(EEXIST);
504             if(!f.exists() && (flags&O_CREAT) == 0) return null;
505             if(f.isDirectory()) {
506                 if((flags&3)!=RD_ONLY) throw new ErrnoException(EACCES);
507                 return directoryFD(f.list(),path.hashCode());
508             }
509             final SeekableFile sf = new SeekableFile(path,(flags&3)!=RD_ONLY);
510             if((flags&O_TRUNC)!=0) sf.setLength(0);
511             return new SeekableFD(sf,mode) {
512                 protected FStat _fstat() { return new HostFStat(f) {
513                     public int size() {
514                         try { return sf.length(); } catch(IOException e) { return 0; }
515                     }
516                 };}
517             };
518         }
519         
520         public FStat stat(String path) throws FileNotFoundException {
521             File f = new File(fixPath(path));
522             if(!f.exists()) throw new FileNotFoundException();
523             return new HostFStat(f);
524         }
525         
526         public void mkdir(String path) throws IOException {
527             File f = new File(fixPath(path));
528             if(f.exists() && f.isDirectory()) throw new ErrnoException(EEXIST);
529             if(f.exists()) throw new ErrnoException(ENOTDIR);
530             File parent = f.getParentFile();
531             if(parent!=null && (!parent.exists() || !parent.isDirectory())) throw new ErrnoException(ENOTDIR);
532             if(!f.mkdir()) throw new ErrnoException(EIO);            
533         }
534     }
535     
536     private static class DevFStat extends FStat {
537         public int dev() { return 1; }
538         public int mode() { return 0666; }
539         public int type() { return S_IFCHR; }
540         public int nlink() { return 1; }
541     }
542     private static FD devZeroFD = new FD() {
543         public boolean readable() { return true; }
544         public boolean writable() { return true; }
545         public int read(byte[] a, int off, int length) { Arrays.fill(a,off,off+length,(byte)0); return length; }
546         public int write(byte[] a, int off, int length) { return length; }
547         public int seek(int n, int whence) { return 0; }
548         public FStat _fstat() { return new DevFStat(); }
549     };
550     private static FD devNullFD = new FD() {
551         public boolean readable() { return true; }
552         public boolean writable() { return true; }
553         public int read(byte[] a, int off, int length) { return 0; }
554         public int write(byte[] a, int off, int length) { return length; }
555         public int seek(int n, int whence) { return 0; }
556         public FStat _fstat() { return new DevFStat(); }
557     };    
558     
559     public class DevFS extends FS {
560         public FD open(String path, int mode, int flags) throws IOException {
561             if(path.equals("/null")) return devNullFD;
562             if(path.equals("/zero")) return devZeroFD;
563             if(path.startsWith("/fd/")) {
564                 int n;
565                 try {
566                     n = Integer.parseInt(path.substring(4));
567                 } catch(NumberFormatException e) {
568                     throw new FileNotFoundException();
569                 }
570                 if(n < 0 || n >= OPEN_MAX) throw new FileNotFoundException();
571                 if(fds[n] == null) throw new FileNotFoundException();
572                 return fds[n].dup();
573             }
574             if(path.equals("/fd")) {
575                 int count=0;
576                 for(int i=0;i<OPEN_MAX;i++) if(fds[i] != null) count++; 
577                 String[] files = new String[count];
578                 count = 0;
579                 for(int i=0;i<OPEN_MAX;i++) if(fds[i] != null) files[count++] = Integer.toString(i);
580                 return directoryFD(files,hashCode());
581             }
582             if(path.equals("/")) {
583                 String[] files = { "null", "zero", "fd" };
584                 return directoryFD(files,hashCode());
585             }
586             throw new FileNotFoundException();
587         }
588         
589         public FStat stat(String path) throws IOException {
590             if(path.equals("/null")) return devNullFD.fstat();
591             if(path.equals("/zero")) return devZeroFD.fstat();            
592             if(path.startsWith("/fd/")) {
593                 int n;
594                 try {
595                     n = Integer.parseInt(path.substring(4));
596                 } catch(NumberFormatException e) {
597                     throw new FileNotFoundException();
598                 }
599                 if(n < 0 || n >= OPEN_MAX) throw new FileNotFoundException();
600                 if(fds[n] == null) throw new FileNotFoundException();
601                 return fds[n].fstat();
602             }
603             if(path.equals("/fd")) return new FStat() { public int type() { return S_IFDIR; } public int mode() { return 0444; }};
604             if(path.equals("/")) return new FStat() { public int type() { return S_IFDIR; } public int mode() { return 0444; }};
605             throw new FileNotFoundException();
606         }
607         
608         public void mkdir(String path) throws IOException { throw new ErrnoException(EACCES); }
609     }
610 }