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