MODULE HostFiles; IMPORT S := SYSTEM, Kernel, HostLang, Files, Log, stdlib := C99stdlib, unistd := C99unistd, dirent := C99dirent, fcntl := C99fcntl, sysstat := C99sys_stat, stdio := C99stdio, errno := C99errno, macro := C99macro, libgen := C99libgen, time := C99time; (* !!! add buffer cache *) CONST closed = 0; new = 1; temp = 2; shared = 3; exclusive = 4; TYPE FullName* = Files.Name; NativeName* = ARRAY 1024 OF SHORTCHAR; Locator = POINTER TO RECORD (Files.Locator) path-: FullName (* pathname # "" *) END; Directory = POINTER TO RECORD (Files.Directory) END; File = POINTER TO RECORD (Files.File) state: INTEGER; len: INTEGER; fd: unistd.int; ino: sysstat.ino_t; pathname: FullName END; Reader = POINTER TO RECORD (Files.Reader) f: File; pos: INTEGER END; Writer = POINTER TO RECORD (Files.Writer) f: File; pos: INTEGER END; InodeIdentifier = RECORD (Kernel.Identifier) ino: sysstat.ino_t END; VAR ignoreAsk-: BOOLEAN; root: Locator; PROCEDURE (VAR id: InodeIdentifier) Identified (): BOOLEAN; VAR f: File; BEGIN f := id.obj(File); RETURN (f.state # closed) & (f.ino = id.ino) END Identified; PROCEDURE GetFileByInode (ino: sysstat.ino_t): File; VAR id: InodeIdentifier; obj: ANYPTR; f: File; BEGIN ASSERT(ino # 0, 20); id.typ := S.TYP(File); id.ino := ino; obj := Kernel.ThisFinObj(id); IF obj # NIL THEN f := obj(File) ELSE f := NIL END; RETURN f END GetFileByInode; PROCEDURE GetError (OUT res: INTEGER); VAR err: INTEGER; BEGIN err := macro.errno(); CASE err OF | errno.ENAMETOOLONG, errno.ENOTDIR: res := 1 (* invalid name/location *) | errno.ENOENT: res := 2 (* file/dir not found *) | errno.EEXIST: res := 3 (* file/dir already exists *) | errno.EROFS: res := 4 (* write-protection *) | errno.EIO: res := 5 (* io error *) | errno.EACCES, errno.EPERM: res := 6 (* access denied *) | errno.ENOMEM: res := 80 (* not enough memory *) | errno.ENFILE, errno.ENOBUFS, errno.ENOSPC: res := 81 (* not enough system resources *) ELSE res := -err END END GetError; (* Locator *) PROCEDURE NewLocator* (IN path: ARRAY OF CHAR): Locator; VAR l: Locator; ch: SHORTCHAR; BEGIN NEW(l); IF path = "" THEN l.path := "." ELSE l.path := path$ END; RETURN l END NewLocator; PROCEDURE (l: Locator) This (IN path: ARRAY OF CHAR): Locator; VAR loc: Locator; BEGIN IF path = "" THEN NEW(loc); loc^ := l^ ELSIF path[0] = "/" THEN loc := NewLocator(path) ELSE loc := NewLocator(l.path + "/" + path) END; RETURN loc END This; (* File *) PROCEDURE (f: File) Length (): INTEGER; BEGIN RETURN f.len END Length; PROCEDURE (f: File) NewReader (old: Files.Reader): Reader; VAR r: Reader; BEGIN ASSERT(f.state # closed, 20); IF (old # NIL) & (old.Base() = f) THEN r := old(Reader); IF r.pos > f.len THEN r.pos := 0 END; r.eof := FALSE ELSE NEW(r); r.f := f; r.pos := 0 END; RETURN r END NewReader; PROCEDURE (f: File) NewWriter (old: Files.Writer): Writer; VAR w: Writer; BEGIN ASSERT(f.state # closed, 20); ASSERT(f.state # shared, 21); IF (old # NIL) & (old.Base() = f) THEN w := old(Writer); IF w.pos > f.len THEN w.pos := 0 END ELSE NEW(w); w.f := f; w.pos := 0 END; RETURN w END NewWriter; PROCEDURE (f: File) Flush; VAR res: unistd.int; BEGIN IF f.state # closed THEN res := unistd.fsync(f.fd); ASSERT(res = 0, 100) END END Flush; PROCEDURE IsName (IN name: Files.Name): BOOLEAN; VAR i: INTEGER; BEGIN i := 0; WHILE (name[i] # "/") & (name[i] # 0X) DO INC(i) END; RETURN name[i] = 0X END IsName; PROCEDURE DirName (VAR path: ARRAY OF CHAR); VAR i, j, k: INTEGER; BEGIN IF path[0] = "/" THEN i := 1; j := 1; k := 1 ELSE i := 0; j := 0; k := 0 END; WHILE path[i] # 0X DO IF path[i] = "/" THEN k := j; j := i; INC(i); WHILE (path[i] # 0X) & (path[i] = "/") DO INC(i) END; IF path[i] = 0X THEN j := k END ELSE INC(i) END END; path[j] := 0X END DirName; PROCEDURE (f: File) Register (name: Files.Name; type: Files.Type; ask: BOOLEAN; OUT res: INTEGER); VAR i, err: INTEGER; dir: FullName; p0, p1: NativeName; s: sysstat.struct_stat; x: unistd.int; BEGIN ASSERT(f.state = new, 20); ASSERT(name # "", 21); ASSERT(IsName(name), 22); HostLang.StringToHost(f.pathname, p0, FALSE, err); IF err = 0 THEN dir := f.pathname$; DirName(dir); HostLang.StringToHost(dir + "/" + name, p1, FALSE, err); IF err = 0 THEN x := stdio.rename(p0, p1); IF x = 0 THEN res := 0 (* no error *) ELSE GetError(res) END; f.state := exclusive; f.Close ELSE res := 1 (* invalid name (too long?) *) END ELSE res := 1 (* invalid name (too long?) *) END END Register; PROCEDURE (f: File) Close; VAR res: unistd.int; path: NativeName; err: INTEGER; BEGIN IF f.state # closed THEN f.Flush; IF f.state = new THEN HostLang.StringToHost(f.pathname, path, FALSE, err); ASSERT(err = 0, 100); res := unistd.unlink(path); ASSERT(res = 0, 101); f.state := temp END; res := unistd.close(f.fd); ASSERT(res = 0, 102); f.state := closed END END Close; PROCEDURE (f: File) Closed (): BOOLEAN; BEGIN RETURN f.state = closed END Closed; PROCEDURE (f: File) Shared (): BOOLEAN; BEGIN RETURN f.state = shared END Shared; PROCEDURE (f: File) FINALIZE; BEGIN f.Close END FINALIZE; (* Reader *) PROCEDURE (r: Reader) Base (): File; BEGIN RETURN r.f END Base; PROCEDURE (r: Reader) Pos (): INTEGER; BEGIN RETURN r.pos END Pos; PROCEDURE (r: Reader) SetPos (pos: INTEGER); BEGIN ASSERT(pos >= 0, 20); ASSERT(pos <= r.f.len, 21); r.pos := pos; r.eof := FALSE END SetPos; PROCEDURE (r: Reader) ReadByte (OUT x: BYTE); VAR res: unistd.int; offset: unistd.off_t; BEGIN ASSERT(r.f.state # closed, 20); offset := unistd.lseek(r.f.fd, r.pos, unistd.SEEK_SET); ASSERT(offset = r.pos, 100); res := unistd.read(r.f.fd, S.ADR(x), 1); ASSERT(res # -1, 101); IF res = 0 THEN x := 0 END; r.pos := r.pos + res; r.eof := res = 0 END ReadByte; PROCEDURE (r: Reader) ReadBytes (VAR x: ARRAY OF BYTE; beg, len: INTEGER); VAR res: unistd.int; offset: unistd.off_t; BEGIN ASSERT(beg >= 0, 20); ASSERT(len >= 0, 2); ASSERT(beg + len <= LEN(x), 22); ASSERT(r.f.state # closed, 23); offset := unistd.lseek(r.f.fd, r.pos, unistd.SEEK_SET); ASSERT(offset = r.pos, 100); res := unistd.read(r.f.fd, S.ADR(x[beg]), len); ASSERT(res # -1, 101); r.pos := r.pos + res; r.eof := res = 0 END ReadBytes; (* Writer *) PROCEDURE (w: Writer) Base (): File; BEGIN RETURN w.f END Base; PROCEDURE (w: Writer) Pos (): INTEGER; BEGIN RETURN w.pos END Pos; PROCEDURE (w: Writer) SetPos (pos: INTEGER); BEGIN ASSERT(pos >= 0, 20); ASSERT(pos <= w.f.len, 21); w.pos := pos END SetPos; PROCEDURE (w: Writer) WriteByte (x: BYTE); VAR res: unistd.int; offset: unistd.off_t; BEGIN ASSERT(w.f.state # closed, 20); offset := unistd.lseek(w.f.fd, w.pos, unistd.SEEK_SET); ASSERT(offset = w.pos, 100); res := unistd.write(w.f.fd, S.ADR(x), 1); ASSERT(res # -1, 101); w.pos := w.pos + res; w.f.len := MAX(w.f.len, w.pos); ASSERT(res = 1, 60) END WriteByte; PROCEDURE (w: Writer) WriteBytes (IN x: ARRAY OF BYTE; beg, len: INTEGER); VAR res: unistd.int; offset: unistd.off_t; BEGIN ASSERT(beg >= 0, 20); ASSERT(len >= 0, 21); ASSERT(beg + len <= LEN(x), 22); ASSERT(w.f.state # closed, 23); offset := unistd.lseek(w.f.fd, w.pos, unistd.SEEK_SET); ASSERT(offset = w.pos, 100); res := unistd.write(w.f.fd, S.ADR(x[beg]), len); ASSERT(res # -1, 101); w.pos := w.pos + res; w.f.len := MAX(w.f.len, w.pos); ASSERT(res = len, 60) END WriteBytes; (* Directory *) PROCEDURE (d: Directory) This (IN path: ARRAY OF CHAR): Locator; BEGIN RETURN root.This(path) END This; PROCEDURE MakeDir (path: ARRAY OF SHORTCHAR; OUT res: unistd.int); VAR i: INTEGER; sep: BOOLEAN; err: unistd.int; s: sysstat.struct_stat; mode: sysstat.mode_t; BEGIN i := 0; err := 0; mode := ORD(BITS(511(*a=rwx*)) - BITS(sysstat.umask(0))); WHILE (err = 0) & (path[i] # 0X) DO WHILE (path[i] # "/") & (path[i] # 0X) DO INC(i) END; sep := path[i] = "/"; IF sep THEN path[i] := 0X END; err := sysstat.mkdir(path, mode); IF err = -1 THEN GetError(err); IF err = 3 THEN (* already exists, continue make dirs *) err := 0 END END; IF sep THEN path[i] := "/" END; INC(i) END; res := err END MakeDir; PROCEDURE (d: Directory) New (loc: Files.Locator; ask: BOOLEAN): File; VAR err: INTEGER; f: File; s: sysstat.struct_stat; fd, res: unistd.int; pathname: NativeName; BEGIN ASSERT(loc # NIL, 20); WITH loc: Locator DO HostLang.StringToHost(loc.path, pathname, FALSE, err); IF err = 0 THEN MakeDir(pathname, res); IF res = 0 THEN (* use fcntl.open() with O_TMPFILE for Linux 3.11+? *) pathname := pathname + "/" + ".newXXXXXX"; fd := stdlib.mkstemp(pathname); IF fd # -1 THEN NEW(f); HostLang.HostToString(pathname, f.pathname, FALSE, err); IF err = 0 THEN (* !!! get valid inode? *) f.fd := fd; f.len := 0; f.state := new; f.ino := 0; loc.res := 0 (* no errors *) ELSE f := NIL; res := unistd.close(fd); ASSERT(res = 0, 100); res := unistd.unlink(pathname); ASSERT(res = 0, 101); loc.res := 1 (* invalid name *) END ELSE GetError(loc.res) END ELSE loc.res := res END ELSE loc.res := 1 (* invalid name *) END ELSE loc.res := 1 (* invalid locator *) END; RETURN f END New; PROCEDURE IsRegFile (IN s: sysstat.struct_stat): BOOLEAN; BEGIN RETURN BITS(s.st_mode) * BITS(sysstat.S_IFMT) = BITS(sysstat.S_IFREG) END IsRegFile; PROCEDURE (d: Directory) Old (loc: Files.Locator; name: Files.Name; isShared: BOOLEAN): File; CONST rwrwrw = 438; VAR err: INTEGER; f, if: File; s: sysstat.struct_stat; fd, flags, res: unistd.int; pathname: NativeName; mode: sysstat.mode_t; lock: fcntl.struct_flock; PROCEDURE Cleanup; BEGIN f := NIL; res := unistd.close(fd); ASSERT(res = 0, 100) END Cleanup; BEGIN ASSERT(loc # NIL, 20); ASSERT(name # "", 21); WITH loc: Locator DO IF IsName(name) THEN HostLang.StringToHost(loc.path + "/" + name, pathname, FALSE, err); IF err = 0 THEN res := macro.stat(pathname, s); IF res = 0 THEN IF IsRegFile(s) THEN if := GetFileByInode(s.st_ino); IF (if = NIL) OR isShared & (if.state = shared) THEN mode := ORD(BITS(rwrwrw) - BITS(sysstat.umask(0))); IF isShared THEN flags := fcntl.O_RDONLY ELSE flags := fcntl.O_RDWR END; fd := fcntl.open(pathname, flags, mode); IF fd # -1 THEN IF isShared THEN lock.l_type := fcntl.F_RDLCK ELSE lock.l_type := fcntl.F_WRLCK END; lock.l_whence := unistd.SEEK_SET; lock.l_start := 0; lock.l_len := 0; lock.l_pid := 0; res := fcntl.fcntl(fd, fcntl.F_SETLK, S.ADR(lock)); IF res # -1 THEN NEW(f); HostLang.HostToString(pathname, f.pathname, FALSE, err); IF err = 0 THEN f.fd := fd; f.len := s.st_size; f.ino := s.st_ino; IF isShared THEN f.state := shared ELSE f.state := exclusive END; loc.res := 0 (* no errors *) ELSE loc.res := 1; (* invalid name *) Cleanup END ELSE GetError(loc.res); (* already locked *) Cleanup END ELSE GetError(loc.res) (* failed to open *) END ELSE loc.res := 6 (* already opened / locked *) END ELSE loc.res := 6 (* access denied (not a regular file) *) END ELSE loc.res := 2 (* file not found *) END ELSE loc.res := 1 (* invalid name *) END ELSE loc.res := 1 (* invalid name *) END ELSE loc.res := 1 (* invalid locator *) END; RETURN f END Old; PROCEDURE (d: Directory) Temp (): File; VAR f: File; fd: unistd.int; name: ARRAY 12 OF SHORTCHAR; BEGIN (* use fcntl.open() with O_TMPFILE for Linux 3.11+? *) name := ".tmpXXXXXX"; fd := stdlib.mkstemp(name); ASSERT(fd # -1, 100); (* !!! get pathname and unlink it here *) NEW(f); f.fd := fd; f.pathname := ""; f.len := 0; f.ino := 0; f.state := temp; RETURN f END Temp; PROCEDURE (d: Directory) Delete (loc: Files.Locator; name: Files.Name); VAR pathname: NativeName; err: INTEGER; res: unistd.int; BEGIN ASSERT(loc # NIL, 20); ASSERT(IsName(name), 21); WITH loc: Locator DO IF IsName(name) THEN HostLang.StringToHost(loc.path + "/" + name, pathname, FALSE, err); IF err = 0 THEN res := unistd.unlink(pathname); IF res = 0 THEN loc.res := 0 (* no error *) ELSE GetError(loc.res) END ELSE loc.res := 1 (* invalid name *) END ELSE loc.res := 1 (* invalid name *) END ELSE loc.res := 1 (* invalid locator *) END END Delete; PROCEDURE (d: Directory) Rename (loc: Files.Locator; old, new: Files.Name; ask: BOOLEAN); VAR p0, p1: NativeName; res: stdio.int; err: INTEGER; BEGIN ASSERT(loc # NIL, 20); ASSERT(old # "", 21); ASSERT(new # "", 22); WITH loc: Locator DO IF IsName(old) & IsName(new) THEN HostLang.StringToHost(loc.path + "/" + old, p0, FALSE, err); IF err = 0 THEN HostLang.StringToHost(loc.path + "/" + new, p1, FALSE, err); IF err = 0 THEN res := stdio.rename(p0, p1); IF res = 0 THEN loc.res := 0 (* no error *) ELSE GetError(loc.res) END ELSE loc.res := 1 (* invalid name *) END ELSE loc.res := 1 (* invalid name *) END ELSE loc.res := 1 (* invalid name *) END ELSE loc.res := 1 (* invalid locator *) END END Rename; PROCEDURE (d: Directory) SameFile (loc0: Files.Locator; name0: Files.Name; loc1: Files.Locator; name1: Files.Name): BOOLEAN; VAR ok: BOOLEAN; a0, a1: NativeName; s0, s1: sysstat.struct_stat; err: INTEGER; BEGIN ASSERT(loc0 # NIL, 20); ASSERT(name0 # "", 21); ASSERT(loc1 # NIL, 22); ASSERT(name1 # "", 23); ok := FALSE; WITH loc0: Locator DO WITH loc1: Locator DO IF IsName(name0) & IsName(name1) THEN HostLang.StringToHost(loc0.path + "/" + name0, a0, FALSE, err); IF err = 0 THEN err := macro.stat(a0, s0); IF err = 0 THEN HostLang.StringToHost(loc1.path + "/" + name1, a1, FALSE, err); IF err = 0 THEN err := macro.stat(a1, s1); IF err = 0 THEN ok := s0.st_ino = s1.st_ino END END END END END ELSE (* don't trap *) END ELSE (* don't trap *) END; RETURN ok END SameFile; PROCEDURE IsDir (IN s: sysstat.struct_stat): BOOLEAN; BEGIN RETURN BITS(s.st_mode) * BITS(sysstat.S_IFMT) = BITS(sysstat.S_IFDIR) END IsDir; PROCEDURE GetAttr (IN path: NativeName; IN name: FullName; s: sysstat.struct_stat): SET; VAR attr: SET; BEGIN attr := {}; IF name[0] = "." THEN INCL(attr, Files.hidden) END; IF BITS(s.st_mode) * BITS(sysstat.S_IXOTH) # {} THEN INCL(attr, 16) END; IF BITS(s.st_mode) * BITS(sysstat.S_IWOTH) # {} THEN INCL(attr, 17) END; IF BITS(s.st_mode) * BITS(sysstat.S_IROTH) # {} THEN INCL(attr, 18) END; IF BITS(s.st_mode) * BITS(sysstat.S_IXGRP) # {} THEN INCL(attr, 19) END; IF BITS(s.st_mode) * BITS(sysstat.S_IWGRP) # {} THEN INCL(attr, 20) END; IF BITS(s.st_mode) * BITS(sysstat.S_IRGRP) # {} THEN INCL(attr, 21) END; IF BITS(s.st_mode) * BITS(sysstat.S_IXUSR) # {} THEN INCL(attr, 22) END; IF BITS(s.st_mode) * BITS(sysstat.S_IWUSR) # {} THEN INCL(attr, 23) END; IF BITS(s.st_mode) * BITS(sysstat.S_IRUSR) # {} THEN INCL(attr, 24) END; IF BITS(s.st_mode) * BITS(sysstat.S_ISVTX) # {} THEN INCL(attr, 25) END; IF BITS(s.st_mode) * BITS(sysstat.S_ISGID) # {} THEN INCL(attr, 26) END; IF BITS(s.st_mode) * BITS(sysstat.S_ISUID) # {} THEN INCL(attr, 27) END; (* !!! better to check real access? *) IF BITS(s.st_mode) * BITS(sysstat.S_IRUSR) # {} THEN INCL(attr, Files.readOnly) END; RETURN attr END GetAttr; PROCEDURE (d: Directory) FileList (loc: Files.Locator): Files.FileInfo; VAR pathname: NativeName; name: FullName; err: INTEGER; p: dirent.PDIR; ent: dirent.Pstruct_dirent; s: sysstat.struct_stat; res: sysstat.int; tm: time.Pstruct_tm; h, t: Files.FileInfo; BEGIN ASSERT(loc # NIL, 20); WITH loc: Locator DO HostLang.StringToHost(loc.path, pathname, FALSE, err); IF err = 0 THEN p := dirent.opendir(pathname); IF p # NIL THEN ent := dirent.readdir(p); WHILE ent # NIL DO HostLang.HostToString(ent.d_name, name, FALSE, err); IF err = 0 THEN HostLang.StringToHost(loc.path + "/" + name, pathname, FALSE, err); IF err = 0 THEN res := macro.stat(pathname, s); IF (res = 0) & ~IsDir(s) THEN IF h = NIL THEN NEW(h); t := h ELSE NEW(t.next); t := t.next END; t.name := name$; t.type := ""; (* ??? *) t.length := s.st_size; tm := time.localtime(s.st_mtim.tv_sec); IF tm # NIL THEN t.modified.year := tm.tm_year + 1900; t.modified.month := tm.tm_mon + 1; t.modified.day := tm.tm_mday; t.modified.hour := tm.tm_hour; t.modified.minute := tm.tm_min; t.modified.second := tm.tm_sec END; t.attr := GetAttr(pathname, name, s) END END END; ent := dirent.readdir(p) END; res := dirent.closedir(p); ASSERT(res = 0, 100); loc.res := 0 (* no error *) ELSE GetError(loc.res) END ELSE loc.res := 1 (* invalid name *) END ELSE loc.res := 1 (* invalid locator *) END; RETURN h END FileList; PROCEDURE (d: Directory) LocList (loc: Files.Locator): Files.LocInfo; VAR pathname: NativeName; name: FullName; err: INTEGER; p: dirent.PDIR; ent: dirent.Pstruct_dirent; s: sysstat.struct_stat; res: sysstat.int; tm: time.Pstruct_tm; h, t: Files.LocInfo; BEGIN ASSERT(loc # NIL, 20); WITH loc: Locator DO HostLang.StringToHost(loc.path, pathname, FALSE, err); IF err = 0 THEN p := dirent.opendir(pathname); IF p # NIL THEN ent := dirent.readdir(p); WHILE ent # NIL DO HostLang.HostToString(ent.d_name, name, FALSE, err); IF err = 0 THEN HostLang.StringToHost(loc.path + "/" + name, pathname, FALSE, err); IF err = 0 THEN res := macro.stat(pathname, s); IF (res = 0) & IsDir(s) & (name # ".") & (name # "..") THEN IF h = NIL THEN NEW(h); t := h ELSE NEW(t.next); t := t.next END; t.name := name$; t.attr := GetAttr(pathname, name, s) END END END; ent := dirent.readdir(p) END; res := dirent.closedir(p); ASSERT(res = 0, 100); loc.res := 0 (* no error *) ELSE GetError(loc.res) END ELSE loc.res := 1 (* invlid name *) END ELSE loc.res := 1 (* invalid locator *) END; RETURN h END LocList; PROCEDURE (d: Directory) GetFileName (name: Files.Name; type: Files.Type; OUT filename: Files.Name); BEGIN filename := name + "." + type END GetFileName; (* Misc *) (* !!! implement NofFiles *) (* !!! implement GetModDate & GetName *) PROCEDURE SetRootDir* (x: ARRAY OF CHAR); BEGIN root := NewLocator(x) END SetRootDir; PROCEDURE UseAsk*; BEGIN ignoreAsk := FALSE END UseAsk; PROCEDURE IgnoreAsk*; BEGIN ignoreAsk := TRUE END IgnoreAsk; PROCEDURE Init; VAR d: Directory; BEGIN SetRootDir("."); NEW(d); Files.SetDir(d) END Init; BEGIN Init END HostFiles.