1# os - files.2#3# Everything goes through syscall, no dependency on the C library of the host.45syscall = import("syscall")6io = import("io")78O_RDONLY = syscall.O_RDONLY9O_WRONLY = syscall.O_WRONLY10O_RDWR = syscall.O_RDWR11O_CREAT = syscall.O_CREAT12O_TRUNC = syscall.O_TRUNC13O_APPEND = syscall.O_APPEND14O_EXCL = syscall.O_EXCL1516bufsize = 327681718newFile = fn(fd, path) {19 f = new()20 f.fd = fd21 f.Name = path2223 # Read returns up to n bytes, or an empty bytes value at end of file.24 f.Read = fn(n) {25 if n == null {26 n = bufsize27 }2829 buf = bytes(n)30 if failed(read = syscall.Read(f.fd, buf, n)) {31 return read32 }33 return slice(buf, 0, read)34 }3536 # ReadAll returns the rest of the file.37 f.ReadAll = fn() { io.ReadAll(f) }3839 f.ReadString = fn() { string(f.ReadAll()) }4041 # Write writes a string or bytes and returns how many bytes went out.42 f.Write = fn(data) {43 if type(data) != "bytes" {44 if failed(data = bytes(string(data))) {45 return data46 }47 }48 return syscall.Write(f.fd, data, len(data))49 }5051 f.Seek = fn(offset, whence) { syscall.Lseek(f.fd, offset, whence) }52 f.Close = fn() { syscall.Close(f.fd) }5354 return f55}5657# Open returns the file at path open with the given flags. Perm is only used58# when the file is created, 0644 is the usual value.59Open = fn(path, flags, perm) {60 if flags == null {61 flags = O_RDONLY62 }63 if perm == null {64 perm = 064465 }6667 if failed(fd = syscall.Open(path, flags, perm)) {68 return fd69 }70 return newFile(fd, path)71}7273# Create opens path for writing, truncating it or creating it as needed.74Create = fn(path) { Open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644) }7576# ReadFile returns the whole content of path as bytes.77ReadFile = fn(path) {78 if failed(f = Open(path, O_RDONLY, 0)) {79 return f80 }8182 data = f.ReadAll()83 f.Close()84 return data85}8687ReadFileString = fn(path) {88 if failed(b = ReadFile(path)) {89 return b90 }91 return string(b)92}9394# WriteFile writes data, a string or bytes, to path.95WriteFile = fn(path, data) {96 if failed(f = Create(path)) {97 return f98 }99100 n = f.Write(data)101 f.Close()102 return n103}104105# Mkdir, Remove, Rmdir and Chmod give back nothing when they worked and an106# error when they didn't, the way Go does.107108Mkdir = fn(path, perm) {109 if perm == null {110 perm = 0755111 }112 return nilOrError(syscall.Mkdir(path, perm))113}114115Remove = fn(path) { nilOrError(syscall.Unlink(path)) }116Rmdir = fn(path) { nilOrError(syscall.Rmdir(path)) }117Chmod = fn(path, mode) { nilOrError(syscall.Chmod(path, mode)) }118119# nilOrError keeps an error and throws away the number a system call returns120# when there is nothing to say.121nilOrError = fn(x) {122 if failed(x) {123 return x124 }125 return null126}127128# Exists reports whether path exists.129Exists = fn(path) { !failed(syscall.Access(path, syscall.F_OK)) }130131# ========== Standard Streams ==========132133Stdin = newFile(0, "/dev/stdin")134Stdout = newFile(1, "/dev/stdout")135Stderr = newFile(2, "/dev/stderr")136137# ========== Arguments and Environment ==========138139# args reads the command line the runtime left in the environment, one140# variable per argument, and takes it away so that a process started from here141# doesn't inherit it.142args = fn() {143 n = syscall.Getenv("TAU_ARGC")144 if n == null {145 return []146 }147 syscall.Unsetenv("TAU_ARGC")148149 out = []150 for i = 0; i < int(n); ++i {151 name = "TAU_ARG{i}"152 out = append(out, syscall.Getenv(name))153 syscall.Unsetenv(name)154 }155 return out156}157158# Args is the command line: the program itself first, then its arguments.159Args = args()160161# Getenv returns the value of the environment variable name, or an empty162# string when it isn't set. LookupEnv tells the two apart.163Getenv = fn(name) {164 v = syscall.Getenv(name)165 if v == null {166 return ""167 }168 return v169}170171# LookupEnv returns the value of name and whether it was set at all.172LookupEnv = fn(name) {173 v = syscall.Getenv(name)174 return [if v == null { "" } else { v }, v != null]175}176177Setenv = fn(name, value) { syscall.Setenv(name, value) }178Unsetenv = fn(name) { syscall.Unsetenv(name) }179180# ========== Files and Directories ==========181182Rename = fn(from, to) { syscall.Rename(from, to) }183Getwd = fn() { syscall.Getwd() }184Chdir = fn(path) { syscall.Chdir(path) }185186# TempDir returns the directory for temporary files.187TempDir = fn() {188 d = syscall.Getenv("TMPDIR")189 if d == null {190 return "/tmp"191 }192 # macOS sets TMPDIR with a trailing slash; drop it so joining a name does193 # not leave a doubled separator, the way Go's os.TempDir does.194 for len(d) > 1 && d[len(d)-1] == "/" {195 d = slice(d, 0, len(d)-1)196 }197 return d198}199200# Stat returns what is known about the file at path: its Name, Size in bytes,201# Mode, whether it IsDir and when it was last modified, in seconds since the202# Unix epoch.203Stat = fn(path) {204 if failed(size = syscall.StatSize(path)) {205 return size206 }207 if failed(mode = syscall.StatMode(path)) {208 return mode209 }210 if failed(mtime = syscall.StatMtime(path)) {211 return mtime212 }213214 st = new()215 st.Name = path216 st.Size = size217 st.Mode = mode218 st.Perm = mode & 0777219 st.IsDir = (mode & syscall.S_IFMT) == syscall.S_IFDIR220 st.ModTime = mtime221 return st222}223224# IsDir reports whether path is a directory. A path that isn't there is not.225IsDir = fn(path) {226 d = syscall.StatIsDir(path)227 if failed(d) {228 return false229 }230 return d231}232233# ReadDir returns the names inside the directory at path, sorted, without234# "." and "..".235ReadDir = fn(path) {236 if failed(names = syscall.Readdirnames(path)) {237 return names238 }239240 out = []241 for i = 0; i < len(names); ++i {242 if names[i] != "." && names[i] != ".." {243 out = append(out, names[i])244 }245 }246 return sorted(out)247}248249# sorted returns the strings in order. Insertion sort: a directory holds few250# enough names that anything smarter would only be longer to read.251sorted = fn(l) {252 for i = 1; i < len(l); ++i {253 v = l[i]254 j = i - 1255 for j >= 0 && l[j] > v {256 l[j + 1] = l[j]257 --j258 }259 l[j + 1] = v260 }261 return l262}263264# MkdirAll creates path and every directory missing above it.265MkdirAll = fn(path, perm) {266 if perm == null {267 perm = 0755268 }269 if IsDir(path) {270 return null271 }272273 parts = []274 start = 0275 for i = 0; i < len(path); ++i {276 if path[i] == "/" {277 parts = append(parts, slice(path, start, i))278 start = i + 1279 }280 }281 parts = append(parts, slice(path, start, len(path)))282283 # An absolute path starts with an empty part, which rebuilds the leading284 # slash on its own.285 cur = ""286 for i = 0; i < len(parts); ++i {287 if parts[i] == "" {288 # A leading empty part is the root and rebuilds the slash; an289 # interior one is a doubled separator and means nothing.290 if i == 0 {291 cur = "/"292 }293 continue294 }295296 cur = if cur == "" || cur == "/" { cur + parts[i] } else { cur + "/" + parts[i] }297 if !IsDir(cur) {298 if failed(err = Mkdir(cur, perm)) {299 return err300 }301 }302 }303 return null304}305306# RemoveAll removes path and everything under it.307RemoveAll = fn(path) {308 if !IsDir(path) {309 if !Exists(path) {310 return null311 }312 return Remove(path)313 }314315 if failed(names = ReadDir(path)) {316 return names317 }318 for i = 0; i < len(names); ++i {319 if failed(err = RemoveAll(path + "/" + names[i])) {320 return err321 }322 }323 return Rmdir(path)324}