3 import org.xwt.mips.util.*;
7 public abstract class UnixRuntime extends Runtime {
8 /** The pid of this "process" */
11 protected int getPid() { return pid; }
13 /** processes filesystem */
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");
21 /** proceses current working directory */
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) {
31 for(int i=1;i<MAX_TASKS;i++) {
32 if(tasks[i] == null) {
41 private static void removeTask(UnixRuntime rt) {
43 for(int i=1;i<MAX_TASKS;i++)
44 if(tasks[i] == rt) { tasks[i] = null; break; }
48 public UnixRuntime(int pageSize, int totalPages, boolean allowEmptyPages) {
49 super(pageSize,totalPages,allowEmptyPages);
51 HostFS root = new HostFS();
52 fs = new UnixOverlayFS(root);
54 String dir = root.hostCWD();
56 chdir(dir == null ? "/" : dir);
57 } catch(FileNotFoundException e) {
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("-");
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));
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;
85 protected String[] createEnv(String[] extra) {
86 String[] defaults = new String[5];
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];
102 protected void _start() {
103 if(addTask(this) < 0) throw new Error("Task list full");
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;
116 if(ppid != 0) synchronized(tasks[ppid].waitNotification) { tasks[ppid].waitNotification.notify(); }
120 protected int syscall(int syscall, int a, int b, int c, int d) {
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);
132 default: return super.syscall(syscall,a,b,c,d);
136 protected FD open(String path, int flags, int mode) throws IOException { return fs.open(cleanupPath(path),flags,mode); }
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;
164 String msg = "Terminating on signal: " + signal + "\n";
168 System.out.print(msg);
171 byte[] b = getBytes(msg);
172 fds[2].write(b,0,b.length);
174 catch(IOException e) { /* ignore */ }
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;
186 synchronized(tasks) {
187 UnixRuntime task = null;
189 for(int i=0;i<MAX_TASKS;i++) {
190 if(tasks[i] != null && tasks[i].ppid == this.pid && tasks[i].state == DONE) {
195 } else if(tasks[pid] != null && tasks[pid].ppid == this.pid && tasks[pid].state == DONE) {
202 if(statusAddr!=0) memWrite(statusAddr,task.exitStatus()<<8);
203 } catch(FaultException e) {
210 if((options&WNOHANG)!=0) return 0;
211 synchronized(waitNotification) {
212 try { waitNotification.wait(); } catch(InterruptedException e) { throw new Error(e); }
217 // Great ugliness lies within.....
218 private int sys_fork() {
219 CPUState state = getCPUState();
220 int sp = state.r[SP];
223 r = (UnixRuntime) getClass().newInstance();
224 } catch(Exception e) {
225 System.err.println(e);
228 int child_pid = addTask(r);
229 if(child_pid < 0) return -ENOMEM;
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();
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);
246 r.readPages[i] = r.readPages[i];
251 r.setCPUState(state);
258 } catch(Exception e) {
259 System.err.println("Forked process threw exception: ");
268 private int sys_pipe(int addr) {
269 PipedOutputStream writerStream = new PipedOutputStream();
270 PipedInputStream readerStream;
272 readerStream = new PipedInputStream(writerStream);
273 } catch(IOException e) {
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; }
284 memWrite(addr+4,fd2);
285 } catch(FaultException e) {
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();
303 private int sys_stat(int cstring, int addr) {
305 String path = cleanupPath(cstring(cstring));
306 return stat(fs.stat(path),addr);
308 catch(ErrnoException e) { return -e.errno; }
309 catch(FileNotFoundException e) {
310 if(e.getMessage() != null && e.getMessage().indexOf("Permission denied") >= 0) return -EACCES;
313 catch(IOException e) { return -EIO; }
314 catch(FaultException e) { return -EFAULT; }
318 private int sys_mkdir(int cstring, int mode) {
320 fs.mkdir(cleanupPath(cstring(cstring)));
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; }
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;
336 copyout(b,addr,b.length);
337 memset(addr+b.length+1,0,1);
339 } catch(FaultException e) {
344 private int sys_chdir(int addr) {
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;
350 System.err.println("Now: " + cwd);
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; }
359 public void chdir(String dir) throws FileNotFoundException {
360 if(state >= RUNNING) throw new IllegalStateException("Can't chdir while process is running");
362 dir = cleanupPath(dir);
363 if(fs.stat(dir).type() != FStat.S_IFDIR) throw new FileNotFoundException();
364 } catch(IOException e) {
365 throw new FileNotFoundException();
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); }
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;
382 dos.writeInt(b.length);
383 dos.write(b,0,b.length);
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; }
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));
400 while((line = br.readLine()) != null) {
401 System.out.println("[" + rt.cleanupPath(line) + "]");
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;
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();
426 out = new char[in.length];
429 out = new char[cwd.length() + in.length + 1];
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");
434 int inLength = in.length;
436 while(inp<inLength) {
437 if(inp == 0 || in[inp] == '/') {
438 while(inp < inLength && in[inp] == '/') inp++;
439 if(inp == inLength) break;
441 if(inp+1 == inLength) break;
442 if(in[inp+1] == '.' && (inp+2 == inLength || in[inp+2] == '/')) {
444 if(outp == 0) continue;
445 do { outp--; } while(outp > 0 && out[outp] != '/');
446 } else if(in[inp+1] == '/') {
453 out[outp++] = in[inp++];
456 out[outp++] = in[inp++];
459 if(outp == 0) out[outp++] = '/';
460 return new String(out,0,outp);
462 if(p.startsWith("/")) return p;
463 StringBuffer sb = new StringBuffer(cwd);
464 if(!cwd.equals("/")) sb.append('/');
465 return sb.append(p).toString();
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) {
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);
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);
487 public FStat stat(String path) throws IOException {
488 String dp = devPath(path);
489 return dp == null ? root.stat(path) : dev.stat(dp);
491 public void mkdir(String path) throws IOException {
492 String dp = devPath(path);
493 if(dp == null) root.mkdir(path);
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 {
504 public String hostCWD() {
505 return getSystemProperty("user.dir");
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());
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) {
525 try { return sf.length(); } catch(IOException e) { return 0; }
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);
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);
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; }
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(); }
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(); }
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/")) {
577 n = Integer.parseInt(path.substring(4));
578 } catch(NumberFormatException e) {
579 throw new FileNotFoundException();
581 if(n < 0 || n >= OPEN_MAX) throw new FileNotFoundException();
582 if(fds[n] == null) throw new FileNotFoundException();
585 if(path.equals("/fd")) {
587 for(int i=0;i<OPEN_MAX;i++) if(fds[i] != null) count++;
588 String[] files = new String[count];
590 for(int i=0;i<OPEN_MAX;i++) if(fds[i] != null) files[count++] = Integer.toString(i);
591 return directoryFD(files,hashCode());
593 if(path.equals("/")) {
594 String[] files = { "null", "zero", "fd" };
595 return directoryFD(files,hashCode());
597 throw new FileNotFoundException();
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/")) {
606 n = Integer.parseInt(path.substring(4));
607 } catch(NumberFormatException e) {
608 throw new FileNotFoundException();
610 if(n < 0 || n >= OPEN_MAX) throw new FileNotFoundException();
611 if(fds[n] == null) throw new FileNotFoundException();
612 return fds[n].fstat();
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();
619 public void mkdir(String path) throws IOException { throw new ErrnoException(EACCES); }