Repository navigation
Category Filesystem
Home › All Categories › Filesystem
#import "std/fs"
using fs
print PathJoin( "C:\dev", "tiko", "src" ) ' C:\dev\tiko\src
print PathExtension( "frmMain.tiko" ) ' .tiko
print PathStem( ".gitignore" ) ' .gitignore -- no extension
dim as FsResultInfo info = Stat( "C:\dev\tiko" )
if( info.IsOk( ) ) then
print info.Value( ).Size( ), info.Value( ).IsDirectory( )
else
print info.Failure( ).Message( ), info.Failure( ).Path( )
end if
for each entry in WalkDir( "C:\dev\tiko" )
if( entry.IsOk( ) ) then
print entry.Value( ).Path( )
else
print "skipped: "; entry.Failure( ).Path( )
end if
nextstd/fs is tiko's filesystem library: paths, file information, directories and files, behind one import and with the same behaviour on windows-x86_64, linux-x86_64 and macos-arm64.
This page covers the filesystem and its paths. Reading and writing what is inside a file -- whole files, open files, streams and text -- is on File Streams.
A complete path toolkit. Paths are ordinary strings, and free functions take them apart and put them together: PathJoin, PathSplit, PathParent, PathFileName, PathStem, PathExtension, PathChangeExtension, PathNormalize, PathRelativeTo, PathEquals and more. Drive letters, UNC shares, \\?\ prefixes and trailing separators are handled, so no program has to slice a path by hand.
Everything about a file in one call. Stat( p ) returns one FileInfo: whether it exists, which of the four kinds it is (file, directory, symbolic link or other), its size, its modified, created and accessed times, and its read-only and hidden flags -- all read together, in one call. LStat( p ) does the same without following a symbolic link.
Directories you walk with FOR EACH. ReadDir lists one directory and WalkDir a whole tree, and both plug straight into FOR EACH. Every walk keeps its own position, so two scans can run side by side or one inside the other. ListDir gives an eager, sortable listing when you want to change entries as you go.
Whole files in one line. ReadAllText, WriteAllText, WriteAtomic and the rest read or replace a file in one call; they, File and the streams are described on File Streams.
Errors you cannot ignore by accident. Every call that can fail returns a Result( of T, FsError ) carrying a code, a message and the path involved, and a default-constructed Result is a failure. Calls that cannot fail return their value directly, so the signature alone says which is which.
Unicode paths everywhere. Every entry point takes a USTRING, and STRING, ZSTRING and WSTRING convert to it, so names in any language work on every platform.
And beyond those: copying, moving and removing files and whole trees (CopyFile, CopyDir, MoveFile, CreateDirAll, RemoveDirAll), temporary files and directories created safely, glob patterns, links and permissions, volumes, advisory file locks, memory-mapped files and directory watching.
One import pulls in everything:
#import "std/fs"Everything belongs to the module fs, and a module is imported whole. Nothing is a keyword: Stat, ReadDir, CopyFile and the rest stay available as your own identifiers, because nothing is added to a program until it imports std/fs, and the bare names only after using fs. JoinPath, DirName, BaseName, Extension, IsAbsolute and Separator are exported macros, written qualified: fs.JoinPath( a, b ).
There is no Path type: paths are strings, and the path functions are free functions over them. Directory iteration follows the structural iterator protocol, which is what lets FOR EACH walk a directory.
This is a library, with no compiler support involved. The runtime half is in tiko-rt (lib/rt/src/fs/) and the tiko types in the std/fs module (pkg/std/fs/), whose files are fspath, fsinfo, fsdir, fsops, fsglob, fslink, fsvolume, fsadvanced and fserror.
Every fallible call returns a Result. Every call that cannot fail returns its value directly. The split is visible in the signature, which is the point.
dim as FsResultUStr r = ReadAllText( "a.txt" )
if( r.IsOk( ) ) then
print r.Value( )
else
print r.Failure( ).Code( ), r.Failure( ).Message( )
end ifA default-constructed Result is a failure, so a forgotten assignment reads as an error rather than as a silent success. Reading the wrong side -- Value( ) on a failure, Failure( ) on a success -- raises FB_RTERROR_ILLEGALFUNCTIONCALL. IsOk( ) is the guard and costs nothing.
There is no ? operator and no must-use diagnostic, so ignoring a Result stays a convention. What the type buys is that the failure has a name and a shape, not that you are forced to look at it.
.Code( ) FsErrorCode -- the portable code. Branch on this.
.OsCode( ) GetLastError( ) / errno, VERBATIM and unmapped. Zero when
the library synthesised the error itself.
.Path( ) the path the call was operating on. Empty if none.
.Path2( ) the second path, for CopyFile / MoveFile / CreateSymlink.
.Operation( ) "CreateFileW", "unlink", "CopyDir". For diagnostics, NEVER
for branching.
.Message( ) a fixed English one-liner derived from Code( ).
Twenty-five codes plus FS_OK, closed on purpose:
FS_OK (never appears inside a failure)
FS_NOT_FOUND no such file or directory
FS_ACCESS_DENIED permission, or in use by policy
FS_ALREADY_EXISTS create refused because the target is there
FS_NOT_A_DIRECTORY a component that must be a directory is not
FS_IS_A_DIRECTORY a directory where a file is required
FS_DIR_NOT_EMPTY RemoveDir on a populated directory
FS_INVALID_PATH malformed, or a character the OS refuses
FS_NAME_TOO_LONG component or whole path exceeds the OS limit
FS_LOOP too many symbolic links resolving one path
FS_NO_SPACE the volume is full, or a quota is exhausted
FS_READ_ONLY the volume or the object is write-protected
FS_IN_USE a sharing violation, or the file is locked
FS_CROSS_DEVICE rename across volumes
FS_INTERRUPTED a signal interrupted the call (POSIX EINTR)
FS_UNSUPPORTED the platform or filesystem cannot do this AT ALL
FS_UNKNOWN mapped to nothing; OsCode( ) is the real answer
FS_CLOSED an operation on a file that is not open
FS_UNEXPECTED_EOF the data ended before the amount asked for
FS_NOT_SEEKABLE a seek or positional IO on a pipe, socket or terminal
FS_INVALID_DATA text decoding met data it was told to reject
FS_TOO_LARGE a size limit was exceeded
FS_WOULD_BLOCK a non-blocking attempt found the lock held
FS_TOO_MANY_LINKS the hard-link limit was reached
FS_BROKEN_PIPE a write to a pipe with no reader
FS_INVALID_ARGUMENT contradictory options, a negative offset, a zero size
The codes after FS_UNKNOWN were added later and appended, so every code keeps its number.
FS_UNSUPPORTED means this will never work here, as distinct from FS_ACCESS_DENIED, which means try again with more privilege. A caller can branch on it once at startup instead of treating it as transient.
Message( ) is not FormatMessageW and not strerror. Those are locale-dependent, vary between Windows builds and libc versions, and produce text no test could assert. It is a fixed table, so the same code gives the same words on every machine. For the OS's own wording, use OsCode( ) and ask the OS.
One per returned type:
FsResultBool FsResultLong FsResultI64 FsResultUStr FsResultStr
FsResultInfo FsResultULong FsResultSpace FsResultVol DirEntryResult
They are concrete typedefs rather than one generic alias because tiko has no generic typedef: type FsResult( of T ) as ... is read as the opening of a generic TYPE body and does not compile.
The kernel is USTRING. Every entry point takes USTRING, and all four string types reach it: STRING, ZSTRING and WSTRING convert implicitly.
The conversion is UTF-8, both ways, on every platform, and it is lossy:
STRING -> USTRING decoded as UTF-8; invalid bytes become U+FFFD
USTRING -> STRING encoded as UTF-8
It is not the ANSI codepage, and it does not fail. A lone &hE9 -- what a Latin-1 or Windows-codepage string holds for 'e-acute' -- becomes U+FFFD silently, with no error and no way to detect it afterwards. That path will not open, and the library does not pretend otherwise. So a Windows program holding an ANSI-codepage path in a STRING cannot use it here: decode it to USTRING yourself first.
Paths that came from the filesystem are USTRING, and should stay that way. DirEntry.Name, ReadLink, CanonicalPath, CurrentDir and the rest return USTRING deliberately, and there are no STRING-returning versions -- adding them would offer a convenient, type-matching way to do the one thing this design exists to prevent. NTFS permits unpaired surrogates in file names; such a name does not survive a trip through STRING, which means afterwards you cannot open, delete or walk it.
The path functions are different, because they are pure string manipulation and nothing they touch came from the filesystem. They carry the full three-overload family and return the width they were given:
string, zstring -> string
wstring, ustring -> ustring
so dim as string p = PathParent( someString ) involves no conversion at all.
Pure. No syscalls, no filesystem access, no locale. Whether a path names anything is Stat's question. Every function takes an optional final gram argument selecting the path grammar; it defaults to the host's and ordinary code never passes it. It exists so the Windows rules can be tested on Linux and the POSIX rules on Windows.
PathRoot( p ) "C:\", "\\srv\share\", "/", "" -- see below
PathParent( p ) "" for a root and for a single relative component
PathFileName( p ) the leaf; "" for a root
PathStem( p ) the leaf without its extension
PathExtension( p ) ".tiko", WITH the dot; "" when there is none
PathSplit( p, comps ) fills an Array( of ustring ); returns the count
PathJoin( a, b ) also 3- and 4-argument forms
PathChangeExtension( p, ext ) ext with or without its dot; "" removes
PathNormalize( p ) collapses '.', resolves '..' LEXICALLY, removes
duplicate separators and a trailing one
PathToNative( p ) separators to native, duplicates collapsed, but
NO '.' or '..' resolution
PathRelativeTo( p, base ) FsResultUStr
PathIsAbsolute( p ) see the Windows note below
PathIsRoot( p ) the whole path is exactly its root
PathRootLen( p ) length of the root prefix, in code units
PathHasExtension( p )
PathEquals( a, b ) normalized, case-insensitive on Windows
PathStartsWith( p, pre ) WHOLE COMPONENTS -- see below
PathNextComponent( p, gram, from, outlen ) the cursor PathSplit uses
PathSeparator( ) "\" or "/"
PathIsCaseSensitive( ) compile-time, per platform -- NOT per volume
PathComponents( p [, gram ] ) for each -- PATH_PREFIX PATH_ROOT PATH_CURDIR PATH_PARENT PATH_NORMAL
PathComponentList( p, comps [, gram ] ) the same, into an Array( of PathComponent )
PathFilePrefix( p ) "a.tar.gz" -> "a" (PathStem gives "a.tar")
PathAddExtension( p, ext ) "a.tar" + "gz" -> "a.tar.gz"
PathIsLocal( p ) safe to join under a directory, lexically
PathIsReserved( p ) CON, NUL, COM1 ... under the Windows grammar
CanonicalPath( p, allowMissing ) canonical even when the end of it does not exist yet
Join an untrusted name only after PathIsLocal. PathJoin( base, name ) discards base when name is absolute, and a name with ".." can climb out of it; PathIsLocal rejects both, and reserved device names on Windows. It is lexical, so a symbolic link inside base can still lead elsewhere.
Three of them are where hand-rolled path code goes wrong:
| Path | Root | Absolute? |
|---|---|---|
C:\dev\tiko |
C:\ |
yes |
C:dev\tiko |
C: |
no |
\dev\tiko |
\ |
no |
dev\tiko |
(empty) | no |
\\srv\share\dev |
\\srv\share\ |
yes |
\\?\C:\dev |
\\?\C:\ |
yes |
\\?\UNC\srv\share\dev |
\\?\UNC\srv\share\ |
yes |
\\.\PhysicalDrive0 |
\\.\ |
yes |
Drive-relative C:dev is not absolute. It names a path relative to the per-drive current directory, which Windows still maintains. It is not C:\dev, and treating it as such is a real bug with real consequences. PathRoot tells them apart by the trailing separator: C: versus C:\.
Rooted-no-drive \dev is not absolute either. It is on the current drive.
Under \\?\ the OS does no parsing at all. . and .. are ordinary names and / is not a separator, so PathNormalize deliberately leaves the interior of such a path alone -- collapsing it would change which file is named.
Each of these has a defensible opposite:
-
PathJoin yields b when b is absolute or rooted, discarding a. This matches Go, Python, Rust and .NET. It also makes
PathJoin( base, userInput )a security consideration rather than a convenience -- if you need containment, check PathStartsWith afterwards. -
PathJoin inserts exactly one separator and never a leading one. An empty argument contributes nothing.
-
PathJoin does not normalize.
PathJoin( "a/b", ".." )is"a/b/..". A join that quietly resolved..would be resolving it without knowing whether b is a symlink. -
PathNormalize( "" )is".". A root normalizes to itself. -
..clamps at a root and never escapes it:"/a/../.."is"/". A relative path has no root to clamp to, so leading..survive:"a/../../b"is"../b". -
PathParent of a root is
"", and of a single relative component is"". A trailing separator does not create a phantom empty component. -
PathFileName of a root is
"". -
The extension is the final
.in the file name, and only when that dot is neither the first character of the name nor the last:"a.tiko" -> ".tiko" "a.b.c" -> ".c" ".gitignore" -> "" "a." -> "" "C:\a.b\c" -> ""A dotfile has no extension -- it is a name.
PathStem( ".gitignore" )is".gitignore". This matches Python, Rust and Go. -
PathChangeExtension takes the new extension with or without its dot;
""removes the extension including the dot. -
PathEquals compares normalized paths, case-insensitively under the Windows grammar, treating
\and/as equal there. It is a lexical answer: two paths naming one file through a symlink compare unequal. CanonicalPath is the call that answers identity. -
PathStartsWith compares whole components.
C:\abcdoes not start withC:\ab. Anything else would make it useless for the containment check rule 1 sends you here for. -
PathSplit normalizes first and does not include the root. PathRoot joined with its components reconstructs PathNormalize exactly.
-
PathRelativeTo fails with FS_INVALID_PATH when the two have different roots -- different drives, one absolute and one relative, UNC versus local. There is no relative path between
C:\aandD:\b, and returning..\..\D:\bwould be a fabrication. Two equal paths give".". -
NUL is never valid in a path and is rejected everywhere.
Normalize to build, canonicalize to compare identity. PathNormalize resolves .. lexically. If a/b is a symlink, a/b/.. is / to the OS but a here. Both answers are correct for their question, and resolving properly would mean hitting the disk from a pure function -- making it fallible, slow, and dependent on whether the path exists yet, which is exactly wrong for something whose main use is building a path before creating it.
Stat( p ) FsResultInfo -- FOLLOWS symbolic links
LStat( p ) FsResultInfo -- does NOT follow
Exists( p ) boolean
IsFile( p ) boolean -- follows links
IsDir( p ) boolean -- follows links
TryExists( p ) FsResultBool -- follows links; fails when it cannot tell
Absence is an error, not an empty success. Stat and LStat fail with FS_NOT_FOUND, so FS_NOT_FOUND and FS_ACCESS_DENIED stay distinguishable. That is the whole argument for Result over Optional here.
The three predicates collapse every failure to false. Exists( p ) is false when p is absent and also when the directory above it denies access. That is a real loss of information and it is deliberate -- it is what makes them usable in an IF. So false means "could not confirm", not "does not exist". Call TryExists when you need the distinction: Ok( true ) when p is there, Ok( false ) only when it is absent (or a component of the path is a file), and a failure -- FS_ACCESS_DENIED, FS_LOOP -- when the answer is unknown. A dangling symbolic link is Ok( false ).
There is deliberately no IsSymlink( p ) predicate: the answer depends on whether you followed, a two-valued result cannot say which it did, and LStat( p ) is one line away.
.Exists( ) .IsFile( ) .IsDirectory( ) .IsSymlink( ) .IsOther( )
.Size( ) uint64
.ModifiedUtc( ) int64 -- ticks, see below
.CreatedUtc( ) .AccessedUtc( )
.HasCreated( ) .HasAccessed( )
.IsReadOnly( ) .IsHidden( )
.NativeAttributes( ) dwFileAttributes / st_mode
The four kinds are exclusive and exhaustive. Exactly one of IsFile, IsDirectory, IsSymlink and IsOther is true whenever Exists is true, and none is when it is false. IsOther covers devices, FIFOs, sockets and Windows reparse points that are not name surrogates -- things a program should notice and refuse rather than treat as files.
IsSymlink is only ever true from LStat. Stat follows, so it reports what the link points at; a Stat result with IsSymlink true would be a contradiction.
Times are ticks of 100 ns since 0001-01-01 UTC -- the same unit std/chrono uses, so the two libraries interoperate without conversion.
Not every platform has every time, and the library says so rather than inventing one:
| Windows | Linux | macOS | |
|---|---|---|---|
| Modified | always | always | always |
| Created | always | statx birth time, where available | always (the file's birth time) |
| Accessed | always | always | always |
HasCreated( ) and HasAccessed( ) report availability. When false the getter is exactly 0 and means nothing. There is no HasModified -- modification time is universal. st_ctime is the inode change time, not a birth time, and is deliberately not reported as CreatedUtc.
Access time is often stale on both platforms -- NTFS LastAccess updates are frequently disabled and Linux mounts default to relatime -- so a program that branches on AccessedUtc is building on sand.
IsReadOnly and IsHidden are the portable two, and they mean different things underneath:
| Windows | Linux | macOS | |
|---|---|---|---|
| IsReadOnly | FILE_ATTRIBUTE_READONLY | no write bit for this user | no write bit for this user |
| IsHidden | FILE_ATTRIBUTE_HIDDEN | the name begins with .
|
the name begins with .
|
A dotfile is hidden on Linux and macOS and not on Windows. That divergence is real.
GetFileId( p [, followLinks ] ) FsResultId -- the volume and the file's number on it
SameFile( a, b ) FsResultBool -- follows links; a missing path is a failure
File.Id( ) the identity of an open file
FileInfo.Modified( ) .Created( ) .Accessed( ) the same times as chrono.Instant
FileInfo.HasOwner( ) .OwnerUid( ) .OwnerGid( ) POSIX
Chown( p, uid, gid ) / LChown( p, uid, gid ) POSIX; -1 leaves an id unchanged
SetTimes( p, modified, accessed ) with chrono.Instant
SetTimesNoFollow( p, ... ) a symbolic link's own times
A FileId is not a FileInfo field. On Windows the file id needs an open handle, so Stat does not pay for it: ask with GetFileId or File.Id when you need to know whether two paths are one file.
FileInfo's ticks are chrono.Instant's ticks -- 100 ns since 0001-01-01 UTC -- so Modified( ), Created( ) and Accessed( ) convert nothing, and SetTimes takes an Instant directly.
AbsolutePath( p ) FsResultUStr
CanonicalPath( p ) FsResultUStr
CurrentDir( ) FsResultUStr
SetCurrentDir( p ) FsResultBool
| AbsolutePath | CanonicalPath | |
|---|---|---|
| must exist | no | yes |
| resolves links | no | yes |
.. semantics |
lexical | filesystem |
| use it to | build a path | compare identity |
AbsolutePath does not require the path to exist, which is the point -- the usual caller is about to create it. On Windows it delegates to GetFullPathNameW because "the relevant current directory" is not one thing: a drive-relative C:foo resolves against the per-drive current directory.
CanonicalPath on Windows returns a \\?\-prefixed path and strips the prefix unless the result exceeds MAX_PATH, where removing it would produce a path the OS cannot open. So compare canonical paths with PathEquals, not with =.
SetCurrentDir changes a process-global, so calling it from one thread races every other thread's relative paths. This library never calls it.
ReadDir( p ) one level
WalkDir( p ) recursive, pre-order
WalkDir( p, opts )
ListDir( p, entries ) eager; fills an Array( of DirEntry )
Every iterator owns its own handle. Nesting, recursion, two concurrent scans on one thread and scans on many threads are all simply correct.
for each r in ReadDir( "C:\dev" )
if( r.IsOk( ) ) then
print r.Value( ).Name( )
else
print "skipped: "; r.Failure( ).Message( )
end if
nextThe element is a Result. The iterator protocol has three members and none of them is an error channel, but a scan can fail partway -- permission denied, a device that went away. Making the failure an element puts it where the loop already looks. The cost is that every loop body unwraps; the alternative is a walk that lies about its coverage.
Value( ) returns BYREF, and the reference is invalidated by the next MoveNext. Copy the entry if you need to keep it.
. and .. are never yielded -- not as an option, not ever.
An unopenable directory yields exactly one element -- the failure -- rather than looking like an empty directory.
.Name( ) the leaf
.Path( ) the full path, directly usable
.Depth( ) 0 for the immediate children of the walk root
.IsFile( ) .IsDirectory( ) .IsSymlink( ) .IsOther( )
.Info( ) FsResultInfo -- MAY cost a stat
The kind predicates are free and LStat-shaped: a symlink reports IsSymlink, not the kind of its target. Info( ) is free on Windows, where the enumeration record already carries size, times and attributes, and costs one lstat on Linux and macOS, where dirent carries neither.
default
.FollowSymlinks FALSE
.YieldDirectories TRUE
.IncludeHidden TRUE
.StopOnError FALSE
.MaxDepth 0 = unbounded
dim as WalkOptions o ' constructed, so the TRUE defaults are set
o.MaxDepth = 1
for each r in WalkDir( root, o ) : ... : next.Filter / .FilterUser a WalkFilter returning WalkDecision bits, with your pointer
.ContentsFirst FALSE; TRUE = a directory after what is in it
.SortByName FALSE; TRUE = ordinal name order in every directory
WalkIterator.SkipChildren( ) do not enter the entry just yielded
The filter sees every entry the walk reaches. An entry is yielded only when the options and the filter both allow it, and walked into only when both do, so a filter can prune a directory that YieldDirectories = FALSE would never show. WALK_STOP ends the whole walk. A for each loop prunes this way; an explicit iterator loop can also call SkipChildren on the entry it was just given.
ContentsFirst is the order for a recursive delete: everything inside a directory comes before it. SortByName compares names code unit by code unit, so the order is the same everywhere.
- Pre-order. A directory is yielded before its contents.
- The root itself is not yielded. Its immediate children are at depth 0.
- Directory symlinks are not followed by default, and neither are Windows junctions and mount points. This makes the traversal a tree by construction: no cycle is possible and no visited set is needed. It is also what makes RemoveDirAll safe.
- With FollowSymlinks a visited set becomes mandatory and is used. It is scoped to the current path, so a directory legitimately reachable twice by different routes is visited twice and only a genuine cycle is cut.
- Hidden entries are included by default. Excluding them would silently omit .git and every hidden system directory. Go, Rust and Python all include them.
- Depth is unbounded by default. MaxDepth = 1 yields only the immediate children.
- An error yields an element and the walk continues. The failing subtree is skipped; everything else is still visited. StopOnError makes the failure the last thing yielded -- it is still yielded.
- No directory handle is held across the descent; each level is listed and its handle closed. The walk cannot exhaust a handle limit.
ListDir is eager and closes its handle before returning. Use it for the two things the iterator cannot do: produce a sorted listing, and let you delete or rename entries while working through them. Mutating a directory's structure during iteration invalidates the iterator, and on Windows is explicitly undefined.
FOR EACH is the normal way, but the protocol types are public and can be driven directly -- which is what you want for two independent cursors over one directory, or to interleave two scans:
DirRange what ReadDir returns; .GetIterator( ) as DirIterator
WalkRange what WalkDir returns; .GetIterator( ) as WalkIterator
DirIterator / WalkIterator
.IsValid( ) still on an element?
.Value( ) BYREF DirEntryResult -- see the warning above
.MoveNext( ) advance
dim as DirIterator a = ReadDir( "C:\dev" ).GetIterator( )
while( a.IsValid( ) )
if( a.Value( ).IsOk( ) ) then print a.Value( ).Value( ).Name( )
a.MoveNext( )
wendA freshly returned iterator is already on the first element. There is no before-the-first state, and MoveNext is not called before the first Value.
The handle opens lazily, on the first IsValid or Value. A copy taken before first use is therefore harmless -- which is the copy FOR EACH makes at loop entry. A copy taken mid-scan shares one cursor and is a bug. The destructor closes the handle if one was opened, including on EXIT FOR and while an error unwinds the scope.
CreateDir( p ) fails with FS_ALREADY_EXISTS if the target is there
CreateDirAll( p ) mkdir -p; SUCCEEDS if it is already a directory
RemoveDir( p ) FS_DIR_NOT_EMPTY on a populated directory
RemoveDirAll( p ) rm -rf; FsResultI64, the number of objects removed
CopyDir( src, dst ) FsResultI64, the number created
CopyDir( src, dst, opts )
CreateDirAll tolerates another process creating the same tree concurrently: an FS_ALREADY_EXISTS on an intermediate component is re-checked and accepted if what is there is a directory. A file in the way is FS_NOT_A_DIRECTORY.
CopyFile( src, dst [, overwrite ] ) overwrite defaults FALSE
MoveFile( src, dst [, overwrite ] )
RemoveFile( p )
ReplaceFile( target, replacement )
WriteAtomic( p, s )
WriteAtomic( p, bytes ) Array( of ubyte ), written as given
What WriteAtomic guarantees is set out on File Streams.
TempDir( ) the system temp directory; creates nothing
CreateTempFile( [ prefix ] ) CREATES it and returns the path; prefix defaults to "tiko"
CreateTempDir( [ prefix ] ) CREATES it and returns the path; prefix defaults to "tiko"
Four rules, and the second and third are the most important destructive behaviour in the library:
- It lists each directory whole before deleting anything in it, because deleting while enumerating is undefined.
-
A symlink or junction inside the tree is unlinked, never descended. This is what stops a recursive delete of a tree containing a link to
C:\from doing what it sounds like. - It cannot be raced into following a link. Each directory is opened relative to its already-open parent and without following a link, and every deletion is made relative to an open directory, so a subdirectory swapped for a link while the removal runs is removed as that link. For the same reason p itself a link is refused with FS_NOT_A_DIRECTORY and nothing is deleted.
- Read-only files are removed, as
rm -rfdoes. Without that the call fails partway through a checked-out repository.
On failure it reports the first error and stops; it does not continue destructively past something it did not understand.
CopyDir refuses when the destination is inside the source, with FS_INVALID_PATH. Without that check CopyDir( "a", "a\b" ) never terminates. The check is lexical, because the destination usually does not exist yet and CanonicalPath requires that it does.
CopyOptions default
.PreserveTimestamps FALSE
.PreservePermissions FALSE
.CopySymlinksAsLinks TRUE
.Overwrite FALSE
Without PreserveTimestamps the behaviour is platform-defined: Windows CopyFileW carries the source's last-write time across on its own, and the POSIX read/write loop creates a file with the current time. If you care, pass the option. PreservePermissions is the platform's own model on each side -- POSIX mode bits or Windows attributes -- not a translation between them.
Without overwrite, MoveFile refuses an existing destination atomically wherever the system can: Windows refuses in the rename itself, Linux uses renameat2 with RENAME_NOREPLACE and macOS renamex_np with RENAME_EXCL, or link-then-unlink on a filesystem without it. Only on a filesystem that supports neither (vfat, exFAT) does it fall back to checking first and renaming after, which is not atomic.
MoveFile handles FS_CROSS_DEVICE. On a cross-volume rename it copies, then deletes. If the delete then fails, the copy is not rolled back: the call reports the delete's error and the destination exists. Losing the destination to tidy up after a partial failure is worse than leaving both. The fallback is not atomic, and no API on any platform can make it so.
They create the object and return its path. They do not hand back a name for you to create: that is the classic tmpnam race, where the window between choosing and creating is an attacker's opportunity. Creation is atomic and the names come from the platform CSPRNG, never from RND. Neither is deleted automatically.
MatchPattern( s, pattern ) boolean. PURE -- no syscalls.
Glob( pattern, results ) FsResultLong; fills Array( of ustring )
GlobRoot( pattern ) the directory a pattern will be walked from
PatternWildcardAt( pattern ) offset of the first wildcard, or -1
PatternIsRecursive( pattern ) does it contain a '**' component
? exactly one character, NOT a separator
* zero or more characters, NOT crossing a separator
** zero or more WHOLE COMPONENTS, including none
[abc] one character from the set
[a-z] one from the range
[!abc] one NOT in the set; [^abc] also works
\x a literal x -- POSIX ONLY
-
*does not cross a separator.a*cdoes not matcha/b/c. -
**is only special as a whole component.a**bis a single*. -
**matches zero components, soa/**/cmatchesa/c. This is the most-missed glob behaviour anywhere. - Case follows the grammar: insensitive on Windows, sensitive on POSIX.
- Separators are equivalent under the Windows grammar, so a pattern written with
/matches a path written with\. -
There is no escape character on Windows, because
\is the separator. A literal[is matched there by the class[[]. -
A malformed class is a literal.
[abcmatches those four characters. A glob that raised on a typo in a user-supplied pattern would be worse. - Matching is whole-string. There is no implicit
*at either end. - The empty string is one empty component, so
*matches"".
Glob splits the pattern at the last component with no wildcard and walks from there, so C:\dev\tiko\src\*.tiko walks src and not the whole volume. A pattern without ** does a single ReadDir and never engages the walk.
Results are not sorted -- directory order is whatever the filesystem gives. Errors during the walk are skipped: this is the one place in the library that swallows one, because Glob returns an array and has nowhere to put a per-entry failure. It fails as a whole only when the walk root cannot be opened. If you need per-entry errors, use WalkDir with MatchPattern -- which is what Glob itself does.
Hidden entries are matched. Unlike a shell, * here matches a leading dot.
CreateSymlink( target, lnk [, targetIsDir ] ) FsResultBool
CreateHardLink( target, lnk ) FsResultBool
ReadLink( lnk ) FsResultUStr
IsLink( p ) boolean
LinkCount( p ) FsResultULong
Argument order is (target, link) -- the thing that exists first, matching ln -s, symlink(2) and Python. It is the opposite of CreateSymbolicLinkW. Getting it backwards silently creates a dangling link pointing at the wrong place.
targetIsDir is required on Windows and ignored on POSIX. Windows encodes the target's kind in the link and needs it at creation time -- even when the target does not exist yet, which is exactly why it cannot be inferred. This is the ugliest portability seam in the library and it is not smoothable.
On Windows, creating a symlink usually needs privilege: expect FS_ACCESS_DENIED unless the process is elevated or Developer Mode is on.
ReadLink returns the target as stored -- unresolved, possibly relative, possibly dangling. It does not canonicalize; conflating the two would make a dangling link unreadable. FS_INVALID_PATH when the path is not a link, on both platforms. Junctions are readable but not creatable.
Three separate things, not one:
SetReadOnly( p, v ) portable
SetHidden( p, v ) portable IN NAME ONLY
GetPosixMode( p ) / SetPosixMode( p, mode ) FS_UNSUPPORTED on Windows
GetWin32Attributes( p ) / SetWin32Attributes( p, attrs )
FS_UNSUPPORTED on POSIX
SetTimes( p, mtimeUtc [, atimeUtc ] ) 0 leaves one alone
The two platforms have no common core worth the name: POSIX has nine mode bits plus setuid/setgid/sticky, an owner and a group; Windows has attribute flags plus a full discretionary ACL. Mapping either onto the other produces something wrong in both directions.
SetReadOnly is genuinely portable, but is not an exact inverse on POSIX -- clearing restores the write bits subject to the current umask. Use GetPosixMode/SetPosixMode when you need exactness.
SetHidden is portable in name only: on POSIX hiddenness is a naming convention, so honouring it would mean renaming the file behind your back. It returns FS_UNSUPPORTED there instead.
There is no owner, group or ACL API. There is no portable subset beyond the read-only bit, and a partial ACL API is worse than none -- it invites code that looks portable and silently grants more than it meant to on one platform.
HomeDir( ) the home directory
ConfigDir( ) user configuration AppData\Roaming $XDG_CONFIG_HOME ~/.config
DataDir( ) user data AppData\Local $XDG_DATA_HOME ~/.local/share
CacheDir( ) files that can be rebuilt AppData\Local $XDG_CACHE_HOME ~/.cache
StateDir( ) logs, history, recents AppData\Local $XDG_STATE_HOME ~/.local/state
ExecutablePath( ) the running program's full path
ExpandHome( p ) a leading ~ replaced by HomeDir( )
Each returns an FsResultUStr without a trailing separator. Nothing is created and no application name is added: join your own name and CreateDirAll it before writing there. On Windows the answers come from the shell's known folders, falling back to the environment; on Linux and macOS an XDG variable is honoured only when it is an absolute path, and $HOME falls back to the passwd entry.
#import "std/fs"
using fs
dim as ustring cfg = PathJoin( ConfigDir( ).ValueOr( "." ), "myapp" )
CreateDirAll( cfg )
WriteAtomic( PathJoin( cfg, "settings.ini" ), "[main]" & chr( 10 ) )GetDiskSpace( p ) FsResultSpace
GetVolumeInfo( p ) FsResultVol
GetVolumes( vols ) FsResultLong; fills Array( of VolumeInfo )
p is any path on the volume, not a volume name. The path must exist.
.TotalBytes capacity
.FreeBytes free, IGNORING quota -- for reporting
.AvailableBytes free TO THIS USER -- the one to branch on
AvailableBytes is the one to use. FreeBytes counts space you may not be permitted to consume: reserved blocks on ext4, a per-user quota on any platform. Where there is no quota the two are equal, which is why the distinction is invisible on a developer machine and bites in production.
All three are snapshots and are stale on return. A program that checks free space and then writes is racing every other process on the machine; the write's FS_NO_SPACE is the real answer.
.RootPath "C:\", "/", "\\srv\share\"
.Name the label. MAY BE EMPTY -- always is on Linux and macOS
.FileSystem "NTFS", "ext4", "exFAT". May be empty.
.MaxComponentLen
.IsReadOnly
.IsCaseSensitive
.SupportsLinks
IsCaseSensitive is per-volume and is not the same question as PathIsCaseSensitive( ), which is a compile-time per-platform constant. They disagree in two real cases: an NTFS directory with per-directory case sensitivity enabled (WSL sets this), and a case-insensitive filesystem mounted on Linux or, by default, on macOS. Use PathIsCaseSensitive for lexical comparison, which must be deterministic and must not do IO; use this one for "will these two names collide on this disk".
SupportsLinks is asymmetric: a FALSE is reliable, a TRUE is best-effort. Windows reports the filesystem's own flag; Linux and macOS have no portable probe, so everything but the FAT family and ISO 9660 is assumed able.
GetVolumes on Windows is the logical drives, skipping any reporting no media. On Linux there are no drives, only mount points, so it reads /proc/self/mountinfo and reports only filesystems backed by a real block device -- which filters proc, sysfs, cgroup and tmpfs. macOS has no drives either: it reads getmntinfo and applies the same rule. That is a judgement call: it will occasionally omit something you wanted, such as a network mount.
Three subsystems that share nothing but a file.
Advisory, whole-file:
.Acquire( p [, exclusive ] [, timeoutMs ] ) FsResultBool
.Release( )
.IsHeld( )
timeoutMs below 0 waits indefinitely, 0 tries once, and above 0 gives up with FS_IN_USE. The destructor releases.
Only one cooperating process at a time gets past Acquire. The lock file is created if it does not exist:
#import "std/fs"
using fs
dim lk as FileLock
dim r as FsResultBool = lk.Acquire( "app.lock", true, 2000 ) ' exclusive; wait up to 2 s
if r.IsOk( ) then
print "got the lock"
' ... the part only one cooperating process may run at a time ...
lk.Release( )
else
print "busy: "; r.Failure( ).Message( )
end ifFileLock holds a whole file against other processes. It is a different tool from the LOCK and UNLOCK statements, which lock byte ranges of a file already open on a file number.
It is advisory everywhere, and that is the contract. Windows LockFileEx is mandatory -- the kernel enforces it against every process. POSIX flock is advisory -- it binds only processes that also ask. The only promise every platform can keep is the weaker one, so all participants must use this API. Code written against the Windows behaviour silently protects nothing on Linux and macOS.
.Open( p [, writable ] ) FsResultBool
.Close( )
.Data( ) uint8 ptr
.Size( ) uint64
.IsOpen( )
.Flush( ) FsResultBool
Count the lines of a file by reading the mapped bytes in place, without copying the file into memory:
#import "std/fs"
using fs
dim mm as MemoryMappedFile
if mm.Open( "data.txt" ).IsOk( ) then ' read-only; Open( p, TRUE ) to write
dim as uint8 ptr bytes = mm.Data( )
dim as longint lines = 0
for i as longint = 0 to mm.Size( ) - 1
if bytes[i] = 10 then lines += 1 ' count line feeds, no copy made
next
print lines; " lines"
mm.Close( )
end ifData( ) is a raw pointer, which is the point -- the alternative is a copy, and a copy is ReadAllBytes. Three things no library can protect you from:
- the pointer is invalid after Close or destruction;
- another process truncating the file turns reads into a fault -- SIGBUS on POSIX, a structured exception on Windows -- not an error return, which is why mapping is not a drop-in replacement for reading;
- mapping a file on a network share is permitted, and is a good way to hang.
A zero-length file cannot be mapped and reports FS_INVALID_PATH on every platform, rather than handing back a pointer that cannot be dereferenced.
.Start( p [, recursive ] ) FsResultBool
.Stop( )
.IsWatching( )
.Poll( changes, [ timeoutMs ] ) FsResultLong -- the count
FsChange:
.Kind( ) FsChangeKind
.Path( ) the FULL path, not a bare leaf
FsChangeKind:
FS_CHANGE_CREATED FS_CHANGE_DELETED FS_CHANGE_MODIFIED
FS_CHANGE_RENAMED_FROM FS_CHANGE_RENAMED_TO FS_CHANGE_OVERFLOW
Report what happens in a folder until a key is pressed. Each Poll refills the array with the changes since the last one:
#import "std/containers"
#import "std/fs"
using fs, containers
dim w as FileWatcher
if w.Start( "C:\dev\inbox" ).IsOk( ) then
dim changes as Array( of FsChange )
do
if w.Poll( changes, 1000 ).IsOk( ) then ' wait up to 1 s; refills changes
for each c in changes
select case c.Kind( )
case FS_CHANGE_CREATED : print "new: "; c.Path( )
case FS_CHANGE_MODIFIED : print "changed: "; c.Path( )
case FS_CHANGE_DELETED : print "gone: "; c.Path( )
case FS_CHANGE_OVERFLOW : print "too many changes -- rescan the folder"
end select
next
end if
loop until len( inkey ) > 0 ' any key stops watching
w.Stop( )
end ifPolling, not callbacks. A callback fires on the watcher's own thread, so every user immediately needs a queue and a lock and most get the threading wrong. Poll puts the events on your thread.
A timeout is an empty success, not a failure -- "nothing happened" and "the watch broke" are different answers.
FS_CHANGE_OVERFLOW is an event, not an error. Both platforms drop events under load, and every correct consumer must respond by rescanning. Modelling it as an error would let you ignore it.
Renames are two events. RENAMED_FROM and RENAMED_TO arrive as a pair, and a rename out of the watched tree produces only the first -- a single event carrying both paths could not represent that.
A single change may produce more than one event: writing a new file yields both CREATED and MODIFIED on Windows. Do not assume a count.
- A default-constructed FsResult is a failure, not a success.
- Value( ) on a failure raises. IsOk( ) is the guard.
- Exists( ) is false for "denied" as well as "absent". Stat tells them apart.
-
C:devis not absolute. Neither is\dev. -
.gitignorehas no extension; its stem is.gitignore. - PathJoin discards the left side when the right is absolute -- check PathStartsWith if you need containment.
- PathEquals is lexical. Two paths naming one file through a symlink compare unequal.
- PathNormalize resolves
..lexically, which is not what the OS does through a symlink. - Compare canonical paths with PathEquals, not
=-- because of the\\?\prefix. - The walk's element is a Result. Unwrap it, or you are not checking.
- Value( ) byref dies at the next MoveNext. Copy to keep.
- WalkDir does not follow directory symlinks unless you ask.
- RemoveDirAll unlinks a directory symlink; it does not descend it.
- A STRING path is UTF-8 and lossy. ANSI-codepage paths do not work.
- Paths that came from the filesystem are USTRING. Keep them that way.
- There is no automatic
\\?\prefixing: paths over MAX_PATH are refused on Windows, with FS_NAME_TOO_LONG and the native code preserved. - Free space is a snapshot and is stale on return.
- A FileLock is advisory, even on Windows, as far as this API promises.
Each was considered and excluded:
- Async, overlapped or promise-based IO. Introducing an async model inside a filesystem library would be a language project wearing a library's clothes.
- A Path value type. Free functions over strings -- a Path type would have to pick one of STRING and USTRING and force a conversion at every boundary with the other. It remains additive later.
- A virtual filesystem abstraction. It needs an interface feature independent of inheritance, which tiko does not have.
- Archives and compression. Zip and tar are file formats, not filesystem operations, and share no code with this.
- Recycle Bin / trash deletion. Shell APIs, COM-dependent, no POSIX counterpart worth the name.
- Extended attributes, alternate data streams, full ACLs. No portable subset that means anything; NativeAttributes( ) is the escape hatch.
- CreateJunction. Junctions are readable but not creatable -- CreateSymlink with targetIsDir covers the portable need.
-
Checksums of files.
hash.DigestFilein std/hash reads a file in blocks and returns its CRC-32 or SHA-256;hash.HashStreamdoes the same for anyStream.
The tiko side, in pkg/std/fs/:
| File | Holds |
|---|---|
| fserror.tiko | FsError, the codes, the result aliases |
| fspath.tiko | the path family |
| fsinfo.tiko | FileInfo, Stat, the resolvers |
| fsdir.tiko | DirEntry, the iterators, ReadDir, WalkDir |
| fsops.tiko | whole-file, bulk, atomic, temp |
| fsglob.tiko | MatchPattern, Glob |
| fslink.tiko | links, permissions, SetTimes, CopyOptions |
| fsvolume.tiko | DiskSpace, VolumeInfo |
| fsadvanced.tiko | FileLock, MemoryMappedFile, FileWatcher |
| fs.tiko | the umbrella |
The runtime side, in tiko-rt's lib/rt/src/fs/:
| File | Holds |
|---|---|
| path.tiko | the path algebra: pure, portable, one copy, both grammars always compiled |
| glob.tiko | the matcher: pure, portable, iterative |
| text.tiko | the BOM and encoding rules, portable |
| errmsg.tiko | the message table |
| dirent.tiko | record-to-USTRING accessors, ABI size hooks |
| error, stat, dir, ops, link, volume, lock, mmap, watch (.tiko) | each split by platform with #if
|
The unit is FB_UCHAR (UTF-16), not FB_WCHAR, which is 4 bytes on Linux and macOS -- building the kernel on it would silently change the path encoding depending on the host. Every struct crossing the C/tiko boundary uses fixed-width fields for the same reason: unsigned long is 4 bytes on Windows and 8 on Linux and macOS while tiko's ULONG is 32-bit everywhere, and getting that wrong shifts every field after it.
The glob matcher is iterative, not recursive. A recursive matcher on a hostile pattern such as a*a*a*a*a*b is exponential and will blow the stack.
Platform code is split by #if, one file per area. tiko-rt's build has no per-target source selection, so error, stat, dir, ops, link, volume, lock, mmap and watch each hold a Windows half and a POSIX half. The path and glob grammars are the exception -- they take the grammar as a parameter, so both are testable on either host.
Nine fbcunit suites in tests/vendor/fbc-tests/filesystem/, and the fs-p run tests in tests/codegen/. The tests for files and streams are listed on File Streams.
| Suite | Covers |
|---|---|
| fspath_core | one block per numbered path rule, every case run under both grammars regardless of host |
| fserror_codes | the tiko enum cross-checked against its C constants |
| fsinfo_dir | Stat, the kinds, and the interleaved-scan test |
| fsops_walk | the encoding rules, the bulk operations, the walk |
| fsglob | one block per glob rule, plus the pathological pattern |
| fslink | links, permissions, and the two destructive rules |
| fsvolume | invariants, plus the one ground-truth check |
| fsadvanced | locking, mapping, watching |
| fsstrings | the whole STRING overload layer |
| fs-p1 | the error codes added after FS_UNKNOWN, WriteAtomic's guarantees, MoveFile refusing an existing destination, TryExists |
| fs-p5 | FileId through hard and symbolic links, SameFile, File.Id, Instant round trips, owner and Chown, SetTimesNoFollow on a link |
| fs-p6 | walk order, filters, pruning and SkipChildren; components of every Windows prefix; PathIsLocal and PathIsReserved tables; CanonicalPath with a missing tail; CreateDirAll's mode |
| fs-p7 | the user directories against the platform's own answer, XDG overrides and the passwd fallback, ExecutablePath, ExpandHome |
| fs-p8 | RemoveDirAll's count, read-only files, a link inside the tree, p itself a link or a file, and a subdirectory swapped for a link to an outside sentinel while the removal runs |
The suites deliberately do not assert that a truncating writer faults a live mapping, nor cross-process lock exclusion.
Every entry, grouped by task. Files and folders lists them beside the rest of the area.
| Function | What it does | From |
|---|---|---|
| Exists | Returns true if a file or folder is there; false also when access is denied. | std/fs |
| IsFile | Returns true if the path is a file, following links; false if it cannot tell. | std/fs |
| IsDir | Returns true if the path is a folder, following links; false if it cannot tell. | std/fs |
| IsLink | Returns true if the path itself is a symbolic link. | std/fs |
| TryExists | Returns whether a path exists, or fails when it cannot tell, as when access is denied. | std/fs |
| Function | What it does | From |
|---|---|---|
| PathJoin | Joins two to four path parts with one separator; an absolute part drops those before it. | std/fs |
| PathParent | Returns the path without its last part, or an empty string for a root. | std/fs |
| PathFileName | Returns the last part of a path, the file name, or an empty string for a root. | std/fs |
| PathStem | Returns the file name without its extension; a dotfile such as .gitignore keeps its name. | std/fs |
| PathExtension | Returns the extension with its dot, such as .tiko, or an empty string if there is none. | std/fs |
| PathFilePrefix | Returns the file name up to its first dot, so a.tar.gz gives a. | std/fs |
| PathHasExtension | Returns true if the file name has an extension; a dotfile has none. | std/fs |
| PathChangeExtension | Replaces a path's extension, or removes it when the new one is empty. | std/fs |
| PathAddExtension | Adds an extension after any already there, so a.tar becomes a.tar.gz. | std/fs |
| PathSplit | Splits a normalised path into its parts, without the root, in an Array; returns the count. | std/fs |
| PathComponents | Returns the parts of a path as written, drive and root included, for a for each loop. | std/fs |
| PathComponent | Holds one part of a path: its kind, such as root or parent, and its text. | std/fs |
| PathNextComponent | Finds the next part of a path from an offset; returns a negative number when none is left. | std/fs |
| PathRoot | Returns the root of a path, such as C:\ or /, or an empty string for a relative path. | std/fs |
| PathRootLen | Returns the length of a path's root, in characters. | std/fs |
| PathSeparator | Returns the platform's path separator: a backslash on Windows, a slash on Linux and macOS. | std/fs |
| Function | What it does | From |
|---|---|---|
| PathNormalize | Returns a tidied path: . removed, .. resolved as text, duplicate separators collapsed. | std/fs |
| PathToNative | Returns the path with the platform's separators, duplicates collapsed, nothing resolved. | std/fs |
| PathRelativeTo | Returns a path relative to a base folder; fails if the two have different roots. | std/fs |
| PathEquals | Returns true if two paths match once normalised; case-insensitive on Windows. | std/fs |
| PathStartsWith | Returns true if a path begins with another, comparing whole parts, not characters. | std/fs |
| PathIsAbsolute | Returns true if a path needs no current folder; C:dev and \dev are not absolute. | std/fs |
| PathIsRoot | Returns true if the whole path is just a root, such as C:\ or /. | std/fs |
| PathIsLocal | Returns true if a relative path stays inside the folder it is joined to. | std/fs |
| PathIsReserved | Returns true if a part of the path is a Windows device name such as CON or NUL. | std/fs |
| PathIsCaseSensitive | Returns true if paths compare case-sensitively here: on Linux and macOS, not Windows. | std/fs |
| AbsolutePath | Returns the full form of a path against the current folder; the path need not exist. | std/fs |
| CanonicalPath | Returns the real path with links resolved; it must exist unless allowMissing is true. | std/fs |
| Function | What it does | From |
|---|---|---|
| ReadDir | Lists the entries of one folder for a for each loop; each entry may be a failure to check. | std/fs |
| WalkDir | Lists every entry under a folder, subfolders included, for a for each loop. | std/fs |
| ListDir | Fills an Array with one folder's entries at once; returns the count. | std/fs |
| DirEntry.Name | Returns the entry's name, without its folder. | std/fs |
| DirEntry.Path | Returns the entry's full path, ready to open. | std/fs |
| DirEntry.Depth | Returns how deep the entry is in a walk, 0 for the children of the folder walked. | std/fs |
| DirEntry.IsFile | Returns true if the entry is a file; a link reports as a link, not as its target. | std/fs |
| DirEntry.IsDirectory | Returns true if the entry is a folder; a link to a folder reports as a link. | std/fs |
| DirEntry.IsSymlink | Returns true if the entry is a symbolic link. | std/fs |
| DirEntry.IsOther | Returns true if the entry is a device, pipe or socket rather than a file, folder or link. | std/fs |
| DirEntry.Info | Returns the entry's FileInfo: free on Windows, one extra system call on Linux and macOS. | std/fs |
| Function | What it does | From |
|---|---|---|
| CopyFile | Copies a file; fails if the destination exists unless overwrite is true. | std/fs |
| MoveFile | Moves or renames a file, even across volumes; refuses an existing destination by default. | std/fs |
| RemoveFile | Deletes a file; fails if it is not there. | std/fs |
| ReplaceFile | Replaces one file with another in a single step, so the target is never missing. | std/fs |
| Function | What it does | From |
|---|---|---|
| CreateDir | Creates one folder; fails if something is already there. | std/fs |
| CreateDirAll | Creates a folder and any missing parents; succeeds if it already exists. | std/fs |
| RemoveDir | Deletes an empty folder; fails if it still holds anything. | std/fs |
| RemoveDirAll | Deletes a folder and all it holds without following links; returns how many it removed. | std/fs |
| CopyDir | Copies a folder and everything in it; returns how many entries it created. | std/fs |
| CopyOptions.Overwrite | Chooses whether CopyDir replaces files already at the destination; false by default. | std/fs |
| CopyOptions.PreserveTimestamps | Chooses whether CopyDir keeps each file's times; false by default. | std/fs |
| CopyOptions.PreservePermissions | Chooses whether CopyDir keeps permission bits or attributes; false by default. | std/fs |
| CopyOptions.CopySymlinksAsLinks | Chooses whether CopyDir copies a link as a link rather than its target; true by default. | std/fs |
| Function | What it does | From |
|---|---|---|
| TempDir | Returns the system's temporary folder; creates nothing. | std/fs |
| CreateTempFile | Creates a uniquely named temporary file and returns its path; it is not deleted for you. | std/fs |
| CreateTempDir | Creates a uniquely named temporary folder and returns its path; it is not deleted for you. | std/fs |
| HomeDir | Returns the user's home folder, without a trailing separator. | std/fs |
| ConfigDir | Returns the folder for user settings, such as AppData\Roaming or ~/.config. | std/fs |
| DataDir | Returns the folder for user data files, such as AppData\Local or ~/.local/share. | std/fs |
| CacheDir | Returns the folder for files that can be rebuilt, such as AppData\Local or ~/.cache. | std/fs |
| StateDir | Returns the folder for logs, history and recent files, such as ~/.local/state. | std/fs |
| ExecutablePath | Returns the full path of the running program, however it was started. | std/fs |
| ExpandHome | Returns a path with a leading ~ replaced by the user's home folder. | std/fs |
| CurrentDir | Returns the program's current folder. | std/fs |
| SetCurrentDir | Changes the program's current folder, for every thread at once. | std/fs |
| Function | What it does | From |
|---|---|---|
| Stat | Returns a path's FileInfo, following symbolic links; fails if the path is missing. | std/fs |
| LStat | Returns a path's FileInfo without following a symbolic link, so a link describes itself. | std/fs |
| FileInfo.Exists | Returns true if the FileInfo describes something that exists. | std/fs |
| FileInfo.IsFile | Returns true if the path is a regular file. | std/fs |
| FileInfo.IsDirectory | Returns true if the path is a folder. | std/fs |
| FileInfo.IsSymlink | Returns true if the path is a symbolic link; only ever true from LStat. | std/fs |
| FileInfo.IsOther | Returns true if the path is a device, pipe or socket rather than a file, folder or link. | std/fs |
| FileInfo.Size | Returns the size in bytes. | std/fs |
| FileInfo.Modified | Returns the last-write time as a chrono.Instant. | std/fs |
| FileInfo.ModifiedUtc | Returns the last-write time in ticks of 100 ns since 0001-01-01 UTC. | std/fs |
| FileInfo.Created | Returns the creation time as a chrono.Instant; check HasCreated first. | std/fs |
| FileInfo.CreatedUtc | Returns the creation time in ticks, or 0 when HasCreated is false. | std/fs |
| FileInfo.Accessed | Returns the last-access time as a chrono.Instant; often not kept up to date. | std/fs |
| FileInfo.AccessedUtc | Returns the last-access time in ticks, or 0 when HasAccessed is false. | std/fs |
| FileInfo.HasCreated | Returns true if the platform records creation times, so Created means something. | std/fs |
| FileInfo.HasAccessed | Returns true if the platform records access times, so Accessed means something. | std/fs |
| FileInfo.IsReadOnly | Returns true if unwritable: the read-only flag, or no write bit on Linux and macOS. | std/fs |
| FileInfo.IsHidden | Returns true if hidden: the hidden flag on Windows, a leading dot on Linux and macOS. | std/fs |
| FileInfo.NativeAttributes | Returns the raw attribute word: attributes on Windows, mode bits on Linux and macOS. | std/fs |
| NativeAttributes | Returns the raw attribute word: attributes on Windows, mode bits on Linux and macOS. | std/fs |
| FileInfo.HasOwner | Returns true if OwnerUid and OwnerGid are filled in: on Linux and macOS, not on Windows. | std/fs |
| FileInfo.OwnerUid | Returns the owner's user id, or 0 unless HasOwner is true. | std/fs |
| FileInfo.OwnerGid | Returns the owner's group id, or 0 unless HasOwner is true. | std/fs |
| Function | What it does | From |
|---|---|---|
| Glob | Fills an Array with the paths that match a wildcard pattern; returns the count. | std/fs |
| MatchPattern | Returns true if a string matches a wildcard pattern, without touching the disk. | std/fs |
| GlobRoot | Returns the folder a pattern search starts from: the part before the first wildcard. | std/fs |
| PatternWildcardAt | Returns the offset of a pattern's first wildcard, or -1 if it has none. | std/fs |
| PatternIsRecursive | Returns true if a pattern has a double-star part that searches every subfolder. | std/fs |
| Function | What it does | From |
|---|---|---|
| WalkOptions.MaxDepth | Limits how deep a walk goes: 1 is the immediate children, 0 (the default) no limit. | std/fs |
| WalkOptions.FollowSymlinks | Chooses whether a walk enters linked folders; false by default, so it cannot loop. | std/fs |
| WalkOptions.YieldDirectories | Chooses whether a walk returns folders as well as files; true by default. | std/fs |
| WalkOptions.IncludeHidden | Chooses whether a walk returns hidden entries; true by default. | std/fs |
| WalkOptions.StopOnError | Chooses whether a walk ends at its first error; false by default, so it carries on. | std/fs |
| WalkOptions.ContentsFirst | Chooses whether a walk returns each folder after its contents; false by default. | std/fs |
| WalkOptions.SortByName | Chooses whether a walk lists each folder in name order; false by default. | std/fs |
| WalkOptions.Filter | Sets a function that decides, entry by entry, what a walk returns and what it enters. | std/fs |
| WalkFilter | Holds the type of a walk filter: a function that says skip, yield, descend or stop. | std/fs |
| WalkDecision | Holds a walk filter's answer: skip, yield, descend into, or stop after an entry. | std/fs |
| WalkIterator.SkipChildren | Skips the contents of the folder a walk has just returned. | std/fs |
| DirIterator | Holds the iterator ReadDir hands out, for stepping through a folder without for each. | std/fs |
| WalkIterator | Holds the iterator WalkDir hands out, for stepping through a walk without for each. | std/fs |
| DirIterator.IsValid | Returns true while the iterator is on an entry. | std/fs |
| DirIterator.MoveNext | Moves the iterator on to the next entry. | std/fs |
| DirIterator.Value | Returns the current entry by reference; the next MoveNext makes it invalid. | std/fs |
| Function | What it does | From |
|---|---|---|
| FsError.Code | Returns the portable error code, such as FS_NOT_FOUND, to branch on. | std/fs |
| FsError.Message | Returns a fixed one-line description of the error code. | std/fs |
| FsError.Path | Returns the path the failed call was working on, or an empty string. | std/fs |
| FsError.Path2 | Returns the second path of a failed copy, move or link call. | std/fs |
| FsError.OsCode | Returns the operating system's own error number, or 0 if the library raised the error. | std/fs |
| FsError.Operation | Returns the name of the call that failed, for diagnostics only. | std/fs |
| Function | What it does | From |
|---|---|---|
| CreateSymlink | Creates a symbolic link to a target; on Windows this usually needs privilege. | std/fs |
| CreateHardLink | Gives an existing file a second name on the same volume. | std/fs |
| ReadLink | Returns the target a symbolic link points to, as stored; fails if it is not a link. | std/fs |
| IsLink | Returns true if the path itself is a symbolic link. | std/fs |
| LinkCount | Returns how many hard links, or names, a file has. | std/fs |
| GetFileId | Returns the FileId of a path, which tells whether two paths are the same file. | std/fs |
| SameFile | Returns whether two paths name the same file; fails if either is missing. | std/fs |
| FileId | Holds a volume and file number that are equal for every name of one file. | std/fs |
| SetReadOnly | Sets or clears a file's read-only flag, or its write bits on Linux and macOS. | std/fs |
| SetHidden | Sets or clears a file's hidden flag on Windows; fails as unsupported on Linux and macOS. | std/fs |
| GetPosixMode | Returns a file's permission bits on Linux and macOS; fails as unsupported on Windows. | std/fs |
| SetPosixMode | Sets a file's permission bits on Linux and macOS; fails as unsupported on Windows. | std/fs |
| GetWin32Attributes | Returns a file's raw Windows attribute flags; fails as unsupported on Linux and macOS. | std/fs |
| SetWin32Attributes | Replaces a file's whole Windows attribute word; fails as unsupported on Linux and macOS. | std/fs |
| SetTimes | Sets a file's modified and accessed times; a time of 0 leaves that one alone. | std/fs |
| SetTimesNoFollow | Sets a symbolic link's own times, leaving its target alone. | std/fs |
| Chown | Changes a file's owner and group on Linux and macOS, -1 keeping one; fails on Windows. | std/fs |
| LChown | Changes a symbolic link's own owner and group, on Linux and macOS only. | std/fs |
| Function | What it does | From |
|---|---|---|
| GetDiskSpace | Returns the total, free and available bytes of the volume a path is on. | std/fs |
| DiskSpace.TotalBytes | Returns the capacity of the volume in bytes. | std/fs |
| DiskSpace.FreeBytes | Returns the free bytes on the volume, ignoring quotas; the number to report. | std/fs |
| DiskSpace.AvailableBytes | Returns the bytes this user may still write; the number to check before writing. | std/fs |
| GetVolumeInfo | Returns the VolumeInfo of the volume a path is on: its root, label and file system. | std/fs |
| GetVolumes | Fills an Array with a VolumeInfo for each drive or mounted disk; returns the count. | std/fs |
| VolumeInfo.RootPath | Returns where the volume is mounted, such as C:\ or /. | std/fs |
| VolumeInfo.Name | Returns the volume's label, which may be empty and always is on Linux and macOS. | std/fs |
| VolumeInfo.FileSystem | Returns the file system's name, such as NTFS or ext4, or an empty string. | std/fs |
| VolumeInfo.MaxComponentLen | Returns the longest file or folder name the volume allows, not the longest path. | std/fs |
| VolumeInfo.IsReadOnly | Returns true if the whole volume is read-only. | std/fs |
| VolumeInfo.IsCaseSensitive | Returns true if names on this volume are case-sensitive. | std/fs |
| VolumeInfo.SupportsLinks | Returns true if the volume can hold symbolic links; false is certain, true a best guess. | std/fs |
| Function | What it does | From |
|---|---|---|
| FileLock.Acquire | Takes a lock on a file that cooperating programs share, waiting up to a timeout. | std/fs |
| FileLock.Release | Releases the lock; harmless when none is held. | std/fs |
| FileLock.IsHeld | Returns true while this FileLock holds its lock. | std/fs |
| MemoryMappedFile.Open | Maps a file into memory, read-only unless writable is true; an empty file fails. | std/fs |
| MemoryMappedFile.Data | Returns a pointer to the mapped bytes, valid until Close. | std/fs |
| MemoryMappedFile.Size | Returns the size of the mapped file in bytes. | std/fs |
| MemoryMappedFile.Flush | Writes changed mapped bytes to the disk now. | std/fs |
| MemoryMappedFile.IsOpen | Returns true while a file is mapped. | std/fs |
| MemoryMappedFile.Close | Unmaps the file; harmless when nothing is mapped. | std/fs |
| FileWatcher.Start | Starts watching a folder for changes, subfolders too when recursive is true. | std/fs |
| FileWatcher.Poll | Fills an Array with the changes seen since the last call, waiting up to a timeout. | std/fs |
| FileWatcher.Stop | Stops watching; harmless when not watching. | std/fs |
| FileWatcher.IsWatching | Returns true while the watcher is running. | std/fs |
| FsChange.Kind | Returns what happened: created, deleted, modified, renamed, or overflow. | std/fs |
| FsChange.Path | Returns the full path of the entry that changed. | std/fs |
tiko-lang · Home · Feature Index · Keyword Index · License
FreeBASIC manual pages reproduced under the GNU FDL 1.3.
- Generics
- Interfaces
- For Each
- Iterators
- Tuples
- Array Results
- Defer
- Lambdas
- Block Strings
- Partial Types
- String Slices
- Embed
- Typed Variadics
- C Header Import
- Foreign Library Linking
- UString
- Fixed-Width Types
- Nullptr
- String Library
- Format
- Array
- Map
- Set
- LinkedList
- OrderedMap
- OrderedSet
- Sorting
- Optional
- Result
- Smart Pointers
- Date and Time (Chrono)
- Filesystem
- File Streams
- Config (INI and TOML)
- Codec
- Hash
- UUID
- OS
- Process
- Net
- HTTP and TLS
- Math Library
- Finance
- Logging
- Terminal TUI Library
- PsControls
- tkControls
- sys/win
- Goroutines and Channels
- LLVM Backend
- tiko build
- TIKO_COMPILER
- Variable Declarations
- Standard Data Types
- User Defined Types
- Procedures
- Operators
- Control Flow Statements
- Converting Data Types
- Array Functions
- String Functions
- Mathematical Functions
- Bit Manipulation
- Date and Time Functions
- File I/O Functions
- Console Functions
- User Input (Runtime Library)
- User Input Functions (Graphics Library)
- Screen Functions
- 2D Drawing Functions
- Graphics Library Reference
- Graphics Keyword List
- Memory Functions
- Threading Support Functions
- Error Handling Functions
- Operating System Functions
- Preprocessor
- Intrinsic Defines
- Miscellaneous Keywords
- Modularizing