hare

The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

commit e1260c73c34647d4841a27b59811a0f7150157bd
parent 61bf12fa612cc9c962af7eb0c6b288727b97e95a
Author: Drew DeVault <sir@cmpwn.com>
Date:   Sun,  9 Jan 2022 11:12:30 +0100

fs::mem: drop module

Signed-off-by: Drew DeVault <sir@cmpwn.com>

Diffstat:
Dfs/mem/+test.ha | 162-------------------------------------------------------------------------------
Dfs/mem/mem.ha | 298-------------------------------------------------------------------------------
Dfs/mem/stream.ha | 86-------------------------------------------------------------------------------
Dfs/mem/util.ha | 129-------------------------------------------------------------------------------
Mscripts/gen-stdlib | 19-------------------
Mstdlib.mk | 37-------------------------------------
6 files changed, 0 insertions(+), 731 deletions(-)

diff --git a/fs/mem/+test.ha b/fs/mem/+test.ha @@ -1,162 +0,0 @@ -use bytes; -use errors; -use fs; -use io; -use strconv; - -// TODO: re-enable this test -// @test -fn mem() void = { - const names: [6]str = ["foo", "bar", "baz", "quux", "hare.ha", "asdf"]; - let filename = names[0]; - - let memfs = memopen(); - const input: [_]u8 = [0, 1, 2, 3, 4, 5]; - - // fs::create, fs::stat - for (let i = 0z; i < 6; i += 1) { - let f = fs::create(memfs, names[i], 0, fs::flags::RDWR)!; - io::write(f, input[i..])!; - io::close(f); - let st = fs::stat(memfs, names[i]) as fs::filestat; - assert(st.mask & fs::stat_mask::SIZE == fs::stat_mask::SIZE); - assert(st.sz == len(input) - i); - assert(st.mode & fs::mode::REG == fs::mode::REG); - }; - - let f = fs::open(memfs, filename, fs::flags::WRONLY, fs::flags::APPEND)!; - io::write(f, input)!; - io::close(f); - let st = fs::stat(memfs, filename) as fs::filestat; - assert(st.sz == len(input) * 2); - - fs::create(memfs, filename, 0, fs::flags::RDONLY) - as fs::error as errors::exists: void; - - // fs::open and read - fs::open(memfs, "nonexistent", fs::flags::RDONLY) - as fs::error as errors::noentry: void; - let f = fs::open(memfs, filename, fs::flags::RDWR, fs::flags::EXCL); - f as fs::error as errors::unsupported: void; - fs::remove(memfs, "nonexistent") - as fs::error as errors::noentry: void; - - let f = fs::open(memfs, filename, fs::flags::RDONLY)!; - let f2 = fs::open(memfs, filename, fs::flags::RDONLY)!; - let output: [12]u8 = [0...]; - assert(io::seek(f2, 3, io::whence::SET) as io::off == 3: io::off); - assert(io::read(f2, output) as size == 9); - io::close(f2); - assert(io::read(f, output) as size == 12); - assert(bytes::equal(input, output[..6])); - assert(bytes::equal(input, output[6..])); - io::close(f); - - // fs::iter - let it = fs::iter(memfs, "") as *fs::iterator; - defer free(it); - let count = 0z; - for (true) match (it.next(it)) { - case void => - break; - case let d: fs::dirent => - count += 1; - }; - assert(count == 6); - - // fs::mkdir - fs::mkdir(memfs, "nonexistent/path") - as fs::error as errors::noentry: void; - fs::rmdir(memfs, "nonexistent/path") - as fs::error as errors::noentry: void; - fs::mkdir(memfs, "dir") as void; - fs::open(memfs, "dir", fs::flags::RDONLY) - as fs::error as fs::wrongtype: void; - fs::mkdir(memfs, "dir") as fs::error as errors::exists: void; - fs::mkdir(memfs, "dir/subdir") as void; - fs::rmdir(memfs, "dir/subdir") as void; - fs::rmdir(memfs, "dir") as void; - fs::rmdir(memfs, "") as fs::error as errors::invalid: void; - - fs::mkdir(memfs, "dir") as void; - f = fs::create(memfs, "dir/file", 0, fs::flags::WRONLY)!; - assert(io::write(f, input[..]) as size == 6); - io::close(f); - f = fs::open(memfs, "dir/file", fs::flags::RDONLY)!; - assert(io::read(f, output) as size == 6); - assert(bytes::equal(input, output[..6])); - io::close(f); - fs::rmdir(memfs, "dir") as fs::error as errors::busy: void; - fs::remove(memfs, "dir/file") as void; - fs::rmdir(memfs, "dir") as void; - - // fs::mksubdir, fs::subdir - fs::mksubdir(memfs, filename) as fs::error as errors::exists: void; - fs::subdir(memfs, filename) as fs::error as fs::wrongtype: void; - - let sub = mksubdir(memfs, "dir") as *fs::fs; - let subsub = mksubdir(memfs, "dir/dir2") as *fs::fs; - - let f = fs::create(sub, "file", 0, fs::flags::WRONLY)!; - io::write(f, [42])!; - io::close(f); - let f = fs::create(subsub, "file", 0, fs::flags::WRONLY)!; - io::write(f, [42])!; - fs::remove(subsub, "file")!; - io::close(f); - fs::close(subsub); - - let sub2 = fs::subdir(memfs, "dir") as *fs::fs; - assert(sub2 == sub); - fs::close(sub); - - let f = fs::open(sub2, "file", fs::flags::RDONLY)!; - assert(io::read(f, output) as size == 1); - assert(output[0] == 42); - io::close(f); - - // fs::close - fs::close(memfs); - - // verify that subdirs can outlive parent dirs - let memsub2 = sub2: *inode; - fs::mkdir(sub2, "subdir") as void; - fs::rmdir(sub2, "subdir") as void; - assert(memsub2.opencount == 1); - assert(memsub2.parent == null); - fs::rmdirall(sub2, "")!; - fs::close(sub2); -}; - -@test fn big_dir() void = { - let limit = 32z; - let memfs = memopen(); - for (let i = 0z; i < limit; i += 1) { - let f = fs::create(memfs, strconv::ztos(i), 0, fs::flags::RDWR)!; - io::close(f); - }; - let ino = memfs: *inode; - let dir = ino.data as directory; - assert(dir.sz == limit); - assert(len(dir.ents) > min_buckets); - - let it = fs::iter(memfs, "") as *fs::iterator; - defer free(it); - let count = 0z; - for (true) match (it.next(it)) { - case void => - break; - case let d: fs::dirent => - count += 1; - }; - assert(count == limit); - - for (let i = 0z; i < limit; i += 1) { - fs::remove(memfs, strconv::ztos(i))!; - }; - let ino = memfs: *inode; - let dir = ino.data as directory; - assert(len(dir.ents) == min_buckets); - assert(dir.sz == 0); - fs::close(memfs); -}; diff --git a/fs/mem/mem.ha b/fs/mem/mem.ha @@ -1,298 +0,0 @@ -// TODO: -// - Symlinks, hard links -// - More fs::flags -// - fs::stat_mask::INODE, ::*TIME - -use errors; -use fs; -use io; -use path; -use strings; - -type directory = struct { - ents: []nullable *inode, - sz: size, -}; - -type file = []u8; - -type inode = struct { - fs::fs, - data: (directory | file), - name: str, - hash: u64, - next: nullable *inode, - opencount: size, - parent: nullable *inode, -}; - -type iterator = struct { - fs::iterator, - parent: *inode, - curr: nullable *inode, - idx: size, -}; - -const supported_flags: fs::flags = ( - fs::flags::RDONLY | - fs::flags::WRONLY | - fs::flags::RDWR | - fs::flags::APPEND); - -// Returns the root of an new in-memory file system. The entire directory -// structure and files with metadata only exist in volatile memory. Arbitrary -// number of handles to the same directory simultaneously are allowed. Supports -// any number of readers or one writer at the same time on a file. -export fn memopen() *fs::fs = alloc(inode { - close = &close, - create = &create, - iter = &iter, - mkdir = &mkdir, - mksubdir = &mksubdir, - open = &open, - remove = &remove, - rmdir = &rmdir, - stat = &stat, - subdir = &subdir, - - data = empty_dir(), - name = "", - hash = hash_of(""), - next = null, - opencount = 1, - parent = null, - ... -}); - -fn file_flags(flags: fs::flags...) ((io::mode, bool) | fs::error) = { - let fl: fs::flags = 0; - for (let i = 0z; i < len(flags); i += 1) { - if (flags[i] & supported_flags != flags[i]) { - return errors::unsupported; - }; - fl |= flags[i]; - }; - let appnd = fl & fs::flags::APPEND == fs::flags::APPEND; - let mode = switch (fl & (~fs::flags::APPEND)) { - case fs::flags::RDONLY => - yield io::mode::READ; - case fs::flags::WRONLY => - yield io::mode::WRITE; - case fs::flags::RDWR => - yield io::mode::RDWR; - case => - abort("invalid flag combination"); - }; - return (mode, appnd); -}; - -fn create( - fs: *fs::fs, - path: str, - mode: fs::mode, - flags: fs::flags..., -) (io::handle | fs::error) = { - let t = file_flags(flags...)?; - let mode = t.0, appnd = t.1; - let parent = inode_find(fs: *inode, path::dirname(path))?; - let name = path::basename(path); - match (inode_find(parent, name)) { - case errors::noentry => void; - case => - return errors::exists; - }; - let ino = alloc(inode { - name = strings::dup(name), - hash = hash_of(name), - data = []: []u8, - parent = parent, - ... - }); - inode_insert(parent, ino); - return stream_open(ino, mode, appnd)?; -}; - -fn open( - fs: *fs::fs, - path: str, - flags: fs::flags..., -) (io::handle | fs::error) = { - let t = file_flags(flags...)?; - let mode = t.0, appnd = t.1; - let ino = inode_find(fs: *inode, path)?; - match (ino.data) { - case directory => - return fs::wrongtype; - case file => - return stream_open(ino, mode, appnd)?; - }; -}; - -fn stat(fs: *fs::fs, path: str) (fs::filestat | fs::error) = { - match (inode_find(fs: *inode, path)?.data) { - case directory => - return fs::filestat { mode = fs::mode::DIR | 0o777, ... }; - case let f: file => - return fs::filestat { - mode = fs::mode::REG | 0o777, - mask = fs::stat_mask::SIZE, - sz = len(f), - ... - }; - }; -}; - -fn mkdir(fs: *fs::fs, path: str) (void | fs::error) = { - let ino = mksubdir(fs, path)?: *inode; - ino.opencount = 0; -}; - -fn mksubdir(fs: *fs::fs, path: str) (*fs::fs | fs::error) = { - let parent = inode_find(fs: *inode, path::dirname(path))?; - let name = path::basename(path); - match (inode_find(parent, name)) { - case errors::noentry => void; - case => - return errors::exists; - }; - let ino = alloc(inode { - close = &close, - create = &create, - iter = &iter, - mkdir = &mkdir, - mksubdir = &mksubdir, - open = &open, - remove = &remove, - rmdir = &rmdir, - stat = &stat, - subdir = &subdir, - - data = empty_dir(), - name = strings::dup(name), - hash = hash_of(name), - next = null, - opencount = 1, - parent = parent, - ... - }); - inode_insert(parent, ino); - return ino; -}; - -fn subdir(fs: *fs::fs, path: str) (*fs::fs | fs::error) = { - let ino = inode_find(fs: *inode, path)?; - match (ino.data) { - case directory => - ino.opencount += 1; - return ino; - case file => - return fs::wrongtype; - }; -}; - -fn iter(fs: *fs::fs, path: str) (*fs::iterator | fs::error) = { - let ino = inode_find(fs: *inode, path)?; - match (ino.data) { - case let d: directory => - return alloc(iterator { - next = &next, - parent = ino, - idx = 0, - curr = d.ents[0], - }); - case file => - return fs::wrongtype; - }; -}; - -fn next(iter: *fs::iterator) (fs::dirent | void) = { - match (_next(iter)) { - case null => void; - case let ino: *inode => - return fs::dirent { - name = ino.name, - ftype = match (ino.data) { - case directory => yield fs::mode::DIR; - case file => yield fs::mode::REG; - }, - }; - }; -}; - -fn _next(it: *fs::iterator) nullable *inode = { - let iter = it: *iterator; - if (iter.curr != null) { - let ino = iter.curr: *inode; - iter.curr = ino.next; - return ino; - }; - let p = iter.parent.data: directory; - iter.idx += 1; - for (iter.idx < len(p.ents)) match (p.ents[iter.idx]) { - case null => - iter.idx += 1; - case let ino: *inode => - iter.curr = ino.next; - return ino; - }; - return null; -}; - -fn rmdir(fs: *fs::fs, path: str) (void | fs::error) = { - let ino = inode_find(fs: *inode, path)?; - if (fs: *inode == ino) { - return errors::invalid; - }; - match (ino.data) { - case let d: directory => - if (ino.opencount != 0 || d.sz != 0) { - return errors::busy; - }; - unlink(ino.parent: *inode, ino); - inode_free(ino); - case file => - return fs::wrongtype; - }; -}; - -fn remove(fs: *fs::fs, path: str) (void | fs::error) = { - let ino = inode_find(fs: *inode, path)?; - match (ino.data) { - case file => - unlink(ino.parent: *inode, ino); - if (ino.opencount == 0) { - inode_free(ino); - }; - case directory => - return fs::wrongtype; - }; -}; - -fn close(fs: *fs::fs) void = { - let ino = fs: *inode; - assert(ino.data is directory); - ino.opencount -= 1; - close_rec(ino); -}; - -fn close_rec(ino: *inode) void = { - if (ino.opencount != 0 || ino.parent != null) { - return; - }; - let it = iterator { next = &next, parent = ino, ... }; - for (true) match (_next(&it)) { - case null => - break; - case let ino: *inode => - ino.parent = null; - match (ino.data) { - case file => - inode_free(ino); - case directory => - close_rec(ino); - case => - abort("unreachable"); - }; - }; - inode_free(ino); -}; diff --git a/fs/mem/stream.ha b/fs/mem/stream.ha @@ -1,86 +0,0 @@ -use bufio; -use errors; -use fs; -use io; -use types; - -type stream = struct { - io::stream, - source: *bufio::memstream, - inode: *inode, - appnd: bool, -}; - -fn stream_open( - ino: *inode, - mode: io::mode, - appnd: bool, -) (*io::stream | fs::error) = { - let f = ino.data as file; - let s = alloc(stream { - closer = &stream_close, - seeker = &seek, - inode = ino, - appnd = appnd, - ... - }); - if (mode & io::mode::WRITE == 0) { - assert(mode & io::mode::READ == io::mode::READ); - s.reader = &read; - if (ino.opencount == types::SIZE_MAX) { - return errors::busy; - }; - ino.opencount += 1; - s.source = alloc(bufio::fixed(f, io::mode::READ)); - } else { - s.writer = &write; - if (ino.opencount != 0) { - return errors::busy; - }; - ino.opencount = types::SIZE_MAX; - s.source = alloc(bufio::dynamic_from(f, mode)); - if (!appnd) { - bufio::truncate(s.source)?; - }; - }; - io::seek(s.source, 0, io::whence::SET)?; - return s; -}; - -fn read(s: *io::stream, buf: []u8) (size | io::EOF | io::error) = { - return io::read((s: *stream).source, buf); -}; - -fn write(s: *io::stream, buf: const []u8) (size | io::error) = { - let s = s: *stream; - if (s.appnd) { - io::seek(s.source, 0, io::whence::END)?; - }; - let sz = io::write(s.source, buf)?; - s.inode.data = bufio::buffer(s.source); - return sz; -}; - -fn seek(s: *io::stream, off: io::off, w: io::whence) (io::off | io::error) = { - return io::seek((s: *stream).source, off, w); -}; - -fn stream_close(s: *io::stream) void = { - let s = s: *stream; - defer free(s); - if (s.writer == null) { - io::close(s.source); - s.inode.opencount -= 1; - if (s.inode.opencount > 0) { - return; - }; - } else { - s.inode.opencount = 0; - io::close(s.source); - }; - - if (s.inode.parent == null) { - // this stream was the last reference to this file, free it - inode_free(s.inode); - }; -}; diff --git a/fs/mem/util.ha b/fs/mem/util.ha @@ -1,129 +0,0 @@ -use errors; -use fs; -use hash::fnv; -use hash; -use path; -use strings; - -def min_buckets: size = 1 << 3; -def max_buckets: size = 1 << 24; - -fn ensure(parent: *inode) void = { - let dir = parent.data: directory; - let old = dir.ents; - let new_size = 0z; - if (dir.sz: u64 * 3u64 >= len(dir.ents): u64 * 4u64) { - new_size = len(dir.ents) << 1; - if (new_size > max_buckets) { - new_size = max_buckets; - }; - } else if (dir.sz: u64 * 10u64 < len(dir.ents): u64) { - new_size = len(dir.ents) >> 3; - if (new_size < min_buckets) { - new_size = min_buckets; - }; - } else { - return; - }; - dir.ents = alloc([], new_size); - for (let i = 0z; i < new_size; i += 1) { - append(dir.ents, null); - }; - parent.data = dir; - for (let i = 0z; i < len(old); i += 1) { - for (true) match (old[i]) { - case null => - break; - case let ino: *inode => - old[i] = ino.next; - _inode_insert(parent, ino); - }; - }; -}; - -fn inode_insert(parent: *inode, ino: *inode) void = { - ensure(parent); - _inode_insert(parent, ino); - let p = parent.data as directory; - p.sz += 1; - parent.data = p; -}; - -fn _inode_insert(parent: *inode, ino: *inode) void = { - let p = parent.data as directory; - let idx: size = ino.hash % len(p.ents): u64; - ino.next = p.ents[idx]; - p.ents[idx] = ino; - parent.data = p; -}; - -fn unlink(parent: *inode, ino: *inode) void = { - let p = parent.data as directory; - let prev = &p.ents[ino.hash % len(p.ents): u64]; - let it = *prev; - for (true) match (it) { - case null => - break; - case let ii: *inode => - if (ii.hash == ino.hash && ii.name == ino.name) { - *prev = ii.next; - break; - }; - prev = &ii.next; - it = ii.next; - }; - p.sz -= 1; - parent.data = p; - ensure(parent); -}; - -fn inode_free(ino: *inode) void = { - match (ino.data) { - case let d: directory => - free(d.ents); - case let f: file => - free(f); - }; - free(ino.name); - free(ino); -}; - -fn inode_find(dir: *inode, path: str) (*inode | fs::error) = { - if (path == "" || path == ".") { - return dir; - }; - let it = path::iter(path); - return find_rec(dir, path::next(&it) as str, &it); -}; - -fn find_rec(dir: *inode, name: str, it: *path::iterator) (*inode | fs::error) = { - let p = dir.data as directory; - let bucket = p.ents[hash_of(name) % len(p.ents): u64]; - for (true) match (bucket) { - case null => - break; - case let ino: *inode => - if (name == ino.name) { - match (path::next(it)) { - case void => - return ino; - case let name: str => - return find_rec(ino, name, it); - }; - }; - bucket = ino.next; - }; - return errors::noentry; -}; - -fn empty_dir() directory = directory { - ents = alloc([null...]: [min_buckets]nullable *inode), - sz = 0, -}; - -fn hash_of(name: str) u64 = { - let h = hash::fnv::fnv64a(); - hash::write(&h, strings::toutf8(name)); - return hash::fnv::sum64(&h); -}; - diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib @@ -466,24 +466,6 @@ fs() { gen_ssa fs io strings path time errors } -gensrcs_fs_mem() { - gen_srcs fs::mem \ - mem.ha \ - stream.ha \ - util.ha \ - $* -} - -fs_mem() { - if [ $testing -eq 0 ] - then - gensrcs_fs_mem - else - gensrcs_fs_mem +test.ha - fi - gen_ssa fs::mem bufio errors fs hash hash::fnv io path strconv strings types -} - getopt() { gen_srcs getopt \ getopts.ha @@ -1149,7 +1131,6 @@ format::elf format::ini format::xml fs -fs::mem getopt hare::ast hare::lex diff --git a/stdlib.mk b/stdlib.mk @@ -296,12 +296,6 @@ stdlib_deps_any+=$(stdlib_fs_any) stdlib_fs_linux=$(stdlib_fs_any) stdlib_fs_freebsd=$(stdlib_fs_any) -# gen_lib fs::mem (any) -stdlib_fs_mem_any=$(HARECACHE)/fs/mem/fs_mem-any.o -stdlib_deps_any+=$(stdlib_fs_mem_any) -stdlib_fs_mem_linux=$(stdlib_fs_mem_any) -stdlib_fs_mem_freebsd=$(stdlib_fs_mem_any) - # gen_lib getopt (any) stdlib_getopt_any=$(HARECACHE)/getopt/getopt-any.o stdlib_deps_any+=$(stdlib_getopt_any) @@ -947,18 +941,6 @@ $(HARECACHE)/fs/fs-any.ssa: $(stdlib_fs_any_srcs) $(stdlib_rt) $(stdlib_io_$(PLA @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nfs \ -t$(HARECACHE)/fs/fs.td $(stdlib_fs_any_srcs) -# fs::mem (+any) -stdlib_fs_mem_any_srcs= \ - $(STDLIB)/fs/mem/mem.ha \ - $(STDLIB)/fs/mem/stream.ha \ - $(STDLIB)/fs/mem/util.ha - -$(HARECACHE)/fs/mem/fs_mem-any.ssa: $(stdlib_fs_mem_any_srcs) $(stdlib_rt) $(stdlib_bufio_$(PLATFORM)) $(stdlib_errors_$(PLATFORM)) $(stdlib_fs_$(PLATFORM)) $(stdlib_hash_$(PLATFORM)) $(stdlib_hash_fnv_$(PLATFORM)) $(stdlib_io_$(PLATFORM)) $(stdlib_path_$(PLATFORM)) $(stdlib_strconv_$(PLATFORM)) $(stdlib_strings_$(PLATFORM)) $(stdlib_types_$(PLATFORM)) - @printf 'HAREC \t$@\n' - @mkdir -p $(HARECACHE)/fs/mem - @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nfs::mem \ - -t$(HARECACHE)/fs/mem/fs_mem.td $(stdlib_fs_mem_any_srcs) - # getopt (+any) stdlib_getopt_any_srcs= \ $(STDLIB)/getopt/getopts.ha @@ -2048,12 +2030,6 @@ testlib_deps_any+=$(testlib_fs_any) testlib_fs_linux=$(testlib_fs_any) testlib_fs_freebsd=$(testlib_fs_any) -# gen_lib fs::mem (any) -testlib_fs_mem_any=$(TESTCACHE)/fs/mem/fs_mem-any.o -testlib_deps_any+=$(testlib_fs_mem_any) -testlib_fs_mem_linux=$(testlib_fs_mem_any) -testlib_fs_mem_freebsd=$(testlib_fs_mem_any) - # gen_lib getopt (any) testlib_getopt_any=$(TESTCACHE)/getopt/getopt-any.o testlib_deps_any+=$(testlib_getopt_any) @@ -2716,19 +2692,6 @@ $(TESTCACHE)/fs/fs-any.ssa: $(testlib_fs_any_srcs) $(testlib_rt) $(testlib_io_$( @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nfs \ -t$(TESTCACHE)/fs/fs.td $(testlib_fs_any_srcs) -# fs::mem (+any) -testlib_fs_mem_any_srcs= \ - $(STDLIB)/fs/mem/mem.ha \ - $(STDLIB)/fs/mem/stream.ha \ - $(STDLIB)/fs/mem/util.ha \ - $(STDLIB)/fs/mem/+test.ha - -$(TESTCACHE)/fs/mem/fs_mem-any.ssa: $(testlib_fs_mem_any_srcs) $(testlib_rt) $(testlib_bufio_$(PLATFORM)) $(testlib_errors_$(PLATFORM)) $(testlib_fs_$(PLATFORM)) $(testlib_hash_$(PLATFORM)) $(testlib_hash_fnv_$(PLATFORM)) $(testlib_io_$(PLATFORM)) $(testlib_path_$(PLATFORM)) $(testlib_strconv_$(PLATFORM)) $(testlib_strings_$(PLATFORM)) $(testlib_types_$(PLATFORM)) - @printf 'HAREC \t$@\n' - @mkdir -p $(TESTCACHE)/fs/mem - @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nfs::mem \ - -t$(TESTCACHE)/fs/mem/fs_mem.td $(testlib_fs_mem_any_srcs) - # getopt (+any) testlib_getopt_any_srcs= \ $(STDLIB)/getopt/getopts.ha