#!/usr/bin/env dub
/+ dub.sdl:
name "fd_remove_tree"
platforms "posix"
targetPath "build"
buildType "checked" {
buildOptions "optimize" "inline" "debugInfo"
}
+/
/**
* The `rm -r` shape that Rust's CVE-2022-21658 fix, CPython's `rmtree`
* (`avoids_symlink_attacks`) and gnulib's `fts` (`FTS_CWDFD`) converged on:
* recursive deletion through directory *handles*, never through paths.
*
* Each directory is opened relative to its parent with `O_NOFOLLOW`, listed
* with `fdopendir`, and every entry removed with `unlinkat` relative to that
* handle (`AT_REMOVEDIR` for a subdirectory once it is empty). A symlink is a
* name to unlink, never a directory to descend — so the demonstration plants
* a symlink to a directory *outside* the tree and shows that directory's
* contents survive the removal.
*
* Companion to docs/research/safe-path-traversal/rust-std.md
* § "Dimension 7 — enumeration and deletion".
*
* Run with: `dub run --single fd-remove-tree.d`
*/
module (module) fd_remove_treeThe rm -r shape that Rust's CVE-2022-21658 fix, CPython's rmtree
(avoids_symlink_attacks) and gnulib's fts (FTS_CWDFD) converged on:
recursive deletion through directory handles, never through paths.
Each directory is opened relative to its parent with O_NOFOLLOW, listed
with fdopendir, and every entry removed with unlinkat relative to that
handle (AT_REMOVEDIR for a subdirectory once it is empty). A symlink is a
name to unlink, never a directory to descend — so the demonstration plants
a symlink to a directory outside the tree and shows that directory's
contents survive the removal.
Companion to docs/research/safe-path-traversal/rust-std.md
§ "Dimension 7 — enumeration and deletion".
Run with: dub run --single fd-remove-tree.d
fd_remove_tree;
import (package) corecore.(package) core.stdcstdc.(module) core.stdc.errnoD header file for C99.
pubs.opengroup.org/onlinepubs/009695399/basedefs/errno.h.html, errno.h
Source
core/stdc/errno.d
errno : (alias) fd_remove_tree.errno = int core.stdc.errno.__errno_location() nothrow @nogc ref @trustederrno;
import (package) corecore.(package) core.syssys.(package) core.sys.posixposix.(module) core.sys.posix.direntD header file for POSIX.
dirent : (struct) core.sys.posix.dirent.DIRDIR, (alias) fd_remove_tree.closedir = int core.sys.posix.dirent.closedir(core.sys.posix.dirent.DIR*) nothrow @nogcclosedir, (struct) core.sys.posix.dirent.direntdirent, readdir;
import (package) corecore.(package) core.syssys.(package) core.sys.posixposix.(module) core.sys.posix.fcntlD header file for POSIX.
fcntl : (alias constant) fd_remove_tree.O_RDONLY = int core.sys.posix.fcntl.O_RDONLY = 0O_RDONLY, (alias constant) fd_remove_tree.O_NOFOLLOW = int core.sys.posix.fcntl.O_NOFOLLOW = 131072O_NOFOLLOW, open, openat;
// Darwin's druntime `core.sys.posix.fcntl` declares neither; the values are
// XNU's `bsd/sys/fcntl.h`.
version (OSXOSX)
{
enum O_DIRECTORY = 0x100000;
enum O_CLOEXEC = 0x1000000;
}
else
import (package) corecore.(package) core.syssys.(package) core.sys.posixposix.(module) core.sys.posix.fcntlD header file for POSIX.
fcntl : (alias constant) fd_remove_tree.O_DIRECTORY = int core.sys.posix.fcntl.O_DIRECTORY = 65536O_DIRECTORY, (alias constant) fd_remove_tree.O_CLOEXEC = int core.sys.posix.fcntl.O_CLOEXEC = 524288O_CLOEXEC;
import (package) corecore.(package) core.syssys.(package) core.sys.posixposix.(module) core.sys.posix.unistdD header file for POSIX.
unistd : (alias) fd_remove_tree.close = int core.sys.posix.unistd.close(int) nothrow @nogc @trustedclose;
import (package) stdstd.(module) std.convA one-stop shop for converting values from one type to another.
Category Functions Generic asOriginalType castFrom parse to toChars bitCast Strings text wtext dtext writeText writeWText writeDText hexString Numeric octal roundTo signed unsigned Exceptions ConvException ConvOverflowException
Source
std/conv.d
conv : (alias template) fd_remove_tree.text = std.conv.text(T...)(T args) if (T.length > 0)Convenience functions for converting one or more arguments
of any type into text (the three character widths).
text;
import (package) stdstd.(module) std.exceptionThis module defines functions related to exceptions and general error
handling. It also defines functions intended to aid in unit testing.
Category Functions Assumptions assertNotThrown assertThrown assumeUnique assumeWontThrow mayPointTo Enforce doesPointTo enforce errnoEnforce Handlers collectException collectExceptionMsg ifThrown handle Other basicExceptionCtors emptyExceptionMsg ErrnoException RangePrimitive
Source
std/exception.d
Examples
Synopis
import core.stdc.stdlib : malloc, free;
import std.algorithm.comparison : equal;
import std.algorithm.iteration : map, splitter;
import std.algorithm.searching : endsWith;
import std.conv : ConvException, to;
import std.range : front, retro;
// use enforce like assert
int a = 3;
enforce(a > 2, "a needs to be higher than 2.");
// enforce can throw a custom exception
enforce!ConvException(a > 2, "a needs to be higher than 2.");
// enforce will return it's input
enum size = 42;
auto memory = enforce(malloc(size), "malloc failed")[0 .. size];
scope(exit) free(memory.ptr);
// collectException can be used to test for exceptions
Exception e = collectException("abc".to!int);
assert(e.file.endsWith("conv.d"));
// and just for the exception message
string msg = collectExceptionMsg("abc".to!int);
assert(msg == "Unexpected 'a' when converting from type string to type int");
// assertThrown can be used to assert that an exception is thrown
assertThrown!ConvException("abc".to!int);
// ifThrown can be used to provide a default value if an exception is thrown
assert("x".to!int().ifThrown(0) == 0);
// handle is a more advanced version of ifThrown for ranges
auto r = "12,1337z32,54".splitter(',').map!(a => to!int(a));
auto h = r.handle!(ConvException, RangePrimitive.front, (e, r) => 0);
assert(h.equal([12, 0, 54]));
assertThrown!ConvException(h.retro.equal([54, 0, 12]));
// basicExceptionCtors avoids the boilerplate when creating custom exceptions
static class MeaCulpa : Exception
{
mixin basicExceptionCtors;
}
e = collectException((){throw new MeaCulpa("diagnostic message");}());
assert(e.msg == "diagnostic message");
assert(e.file == __FILE__);
assert(e.line == __LINE__ - 3);
// assumeWontThrow can be used to cast throwing code into `nothrow`
void exceptionFreeCode() nothrow
{
// auto-decoding only throws if an invalid UTF char is given
assumeWontThrow("abc".front);
}
// assumeUnique can be used to cast mutable instance to an `immutable` one
// use with care
char[] str = " mutable".dup;
str[0 .. 2] = "im";
immutable res = assumeUnique(str);
assert(res == "immutable");
exception : (alias template) fd_remove_tree.enforce = std.exception.enforce(E : Throwable = Exception) if (is(typeof(new E("", string.init, size_t.init)) : Throwable) || is(typeof(new E(string.init, size_t.init)) : Throwable))Enforces that the given value is true.
If the given value is false, an exception is thrown.
The
msg - error message as a string
dg - custom delegate that return a string and is only called if an exception occurred
ex - custom exception to be thrown. It is lazy and is only created if an exception occurred
enforce;
import (package) stdstd.(module) std.fileUtilities for manipulating files and scanning directories. Functions
in this module handle files as a unit, e.g., read or write one file
at a time. For opening files and manipulating them via handles refer
to module std.stdio.
Category Functions General exists isDir isFile isSymlink rename thisExePath Directories chdir dirEntries getcwd mkdir mkdirRecurse rmdir rmdirRecurse tempDir Files append copy read readText remove slurp write Symlinks symlink readLink Attributes attrIsDir attrIsFile attrIsSymlink getAttributes getLinkAttributes getSize setAttributes Timestamp getTimes getTimesWin setTimes timeLastModified timeLastAccessed timeStatusChanged Other DirEntry FileException PreserveAttributes SpanMode getAvailableDiskSpace
Source
std/file.d
file : (alias template) fd_remove_tree.dirEntries = std.file.dirEntries(bool useDIP1000 = dip1000Enabled)(string path, SpanMode mode, bool followSymlink = true)Returns an input range
of DirEntry that lazily iterates a given directory,
also provides two ways of foreach iteration. The iteration variable can be of
type string if only the name is needed, or DirEntry
if additional details are needed. The span mode dictates how the
directory is traversed. The name of each iterated directory entry
contains the absolute or relative path (depending on pathname).
Note
The order of returned directory entries is as it is provided by the
operating system / filesystem, and may not follow any particular sorting.
Example
// Iterate a directory in depth
foreach (string name; dirEntries("destroy/me", SpanMode.depth))
{
remove(name);
}
// Iterate the current directory in breadth
foreach (string name; dirEntries("", SpanMode.breadth))
{
writeln(name);
}
// Iterate a directory and get detailed info about it
foreach (DirEntry e; dirEntries("dmd-testing", SpanMode.breadth))
{
writeln(e.name, "\t", e.size);
}
// Iterate over all *.d files in current directory and all its subdirectories
auto dFiles = dirEntries("", SpanMode.depth).filter!(f => f.name.endsWith(".d"));
foreach (d; dFiles)
writeln(d.name);
// Hook it up with std.parallelism to compile them all in parallel:
foreach (d; parallel(dFiles, 1)) //passes by 1 file to each thread
{
string cmd = "dmd -c " ~ d.name;
writeln(cmd);
std.process.executeShell(cmd);
}
// Iterate over all D source files in current directory and all its
// subdirectories
auto dFiles = dirEntries("","*.{d,di}",SpanMode.depth);
foreach (d; dFiles)
writeln(d.name);
To handle subdirectories with denied read permission, use SpanMode.shallow:
void scan(string path)
{
foreach (DirEntry entry; dirEntries(path, SpanMode.shallow))
{
try
{
writeln(entry.name);
if (entry.isDir)
scan(entry.name);
}
catch (FileException fe) { continue; } // ignore
}
}
scan("");
dirEntries, (alias template) fd_remove_tree.exists = std.file.exists(R)(R name) if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)Determine whether the given file (or directory) exists.
exists, (alias) fd_remove_tree.mkdirRecurse = void std.file.mkdirRecurse(scope const(char)[] pathname) @safeMake directory and all parent directories as needed.
Does nothing if the directory specified by
pathname already exists.
mkdirRecurse, (alias) fd_remove_tree.rmdirRecurse = void std.file.rmdirRecurse(scope const(char)[] pathname) @safeRemove directory and all of its content and subdirectories,
recursively.
rmdirRecurse, (enum) std.file.SpanModeDictates directory spanning policy for dirEntries (see below).
Examples
import std.algorithm.comparison : equal;
import std.algorithm.iteration : map;
import std.algorithm.sorting : sort;
import std.array : array;
import std.path : buildPath, relativePath;
auto root = deleteme ~ "root";
scope(exit) root.rmdirRecurse;
root.mkdir;
root.buildPath("animals").mkdir;
root.buildPath("animals", "cat").mkdir;
alias removeRoot = (return scope e) => e.relativePath(root);
assert(root.dirEntries(SpanMode.depth).map!removeRoot.equal(
[buildPath("animals", "cat"), "animals"]));
assert(root.dirEntries(SpanMode.breadth).map!removeRoot.equal(
["animals", buildPath("animals", "cat")]));
root.buildPath("plants").mkdir;
assert(root.dirEntries(SpanMode.shallow).array.sort.map!removeRoot.equal(
["animals", "plants"]));
SpanMode,
(alias template) fd_remove_tree.symlink = std.file.symlink(RO, RL)(RO original, RL link) if ((isSomeFiniteCharInputRange!RO || isConvertibleToString!RO) && (isSomeFiniteCharInputRange!RL || isConvertibleToString!RL))This function is POSIX-Only.
Creates a symbolic link (symlink).
symlink, (alias) fd_remove_tree.tempDir = string std.file.tempDir() @trustedReturns the path to a directory for temporary files.
On POSIX platforms, it searches through the following list of directories
and returns the first one which is found to exist:
The directory given by the TMPDIR environment variable.
The directory given by the TEMP environment variable.
The directory given by the TMP environment variable.
/tmp/
/var/tmp/
/usr/tmp/
On all platforms, tempDir returns the current working directory on failure.
The return value of the function is cached, so the procedures described
below will only be performed the first time the function is called. All
subsequent runs will return the same string, regardless of whether
environment variables and directory structures have changed in the
meantime.
The POSIX tempDir algorithm is inspired by Python's
tempfile.tempdir.
tempDir, (alias template) fd_remove_tree.write = std.file.write(R)(R name, const void[] buffer) if ((isSomeFiniteCharInputRange!R || isSomeString!R) && !isConvertibleToString!R)Write buffer to file name.
Creates the file if it does not already exist.
write;
import (package) stdstd.(module) std.pathThis module is used to manipulate path strings.
All functions, with the exception of expandTilde (and in some
cases absolutePath and relativePath), are pure
string manipulation functions; they don't depend on any state outside
the program, nor do they perform any actual file system actions.
This has the consequence that the module does not make any distinction
between a path that points to a directory and a path that points to a
file, and it does not know whether or not the object pointed to by the
path actually exists in the file system.
To differentiate between these cases, use isDir and
exists.
Note that on Windows, both the backslash (\) and the slash (/)
are in principle valid directory separators. This module treats them
both on equal footing, but in cases where a new separator is
added, a backslash will be used. Furthermore, the buildNormalizedPath
function will replace all slashes with backslashes on that platform.
In general, the functions in this module assume that the input paths
are well-formed. (That is, they should not contain invalid characters,
they should follow the file system's path format, etc.) The result
of calling a function on an ill-formed path is undefined. When there
is a chance that a path or a file name is invalid (for instance, when it
has been input by the user), it may sometimes be desirable to use the
isValidFilename and isValidPath functions to check
this.
Most functions do not perform any memory allocations, and if a string is
returned, it is usually a slice of an input string. If a function
allocates, this is explicitly mentioned in the documentation.
Category Functions Normalization absolutePath asAbsolutePath asNormalizedPath asRelativePath buildNormalizedPath buildPath chainPath expandTilde Partitioning baseName dirName dirSeparator driveName pathSeparator pathSplitter relativePath rootName stripDrive Validation isAbsolute isDirSeparator isRooted isValidFilename isValidPath Extension defaultExtension extension setExtension stripExtension withDefaultExtension withExtension Other filenameCharCmp filenameCmp globMatch CaseSensitive
Source
std/path.d
path : (alias template) fd_remove_tree.buildPath = std.path.buildPath(Range)(scope Range segments) if (isInputRange!Range && !isInfinite!Range && isSomeString!(ElementType!Range))Combines one or more path segments.
This function takes a set of path segments, given as an input
range of string elements or as a set of string arguments,
and concatenates them with each other. Directory separators
are inserted between segments if necessary. If any of the
path segments are absolute (as defined by isAbsolute), the
preceding segments will be dropped.
On Windows, if one of the path segments are rooted, but not absolute
(e.g. \foo), all preceding path segments down to the previous
root will be dropped. (See below for an example.)
This function always allocates memory to hold the resulting path.
The variadic overload is guaranteed to only perform a single
allocation, as is the range version if paths is a forward
range.
buildPath;
import (package) stdstd.(module) std.stdioCategory Symbols File handles _popen File isFileHandle openNetwork stderr stdin stdout Reading chunks lines readf readfln readln Writing toFile write writef writefln writeln Misc KeepTerminator LockType StdioException
Standard I/O functions that extend core.stdc.stdio. core.stdc.stdio
is publically imported when importing std.stdio.
There are three layers of I/O:
The lowest layer is the operating system layer. The two main schemes are Windows and Posix.
C's stdio.h which unifies the two operating system schemes.
std.stdio, this module, unifies the various stdio.h implementations into
a high level package for D programs.
Source
std/stdio.d
stdio : (alias template) fd_remove_tree.writefln = std.stdio.writefln(alias fmt, A...)(A args) if (isSomeString!(typeof(fmt)))Equivalent to writef(fmt, args, '\n').
writefln, (alias template) fd_remove_tree.writeln = std.stdio.writeln(T...)(T args)Equivalent to write(args, '\n'). Calling writeln without
arguments is valid and just prints a newline to the standard
output.
Example
Reads stdin and writes it to stdout with an argument
counter.
import std.stdio;
void main()
{
string line;
for (size_t count = 0; (line = readln) !is null; count++)
{
writeln("Input ", count, ": ", line);
}
}
writeln;
import (package) stdstd.(module) std.stringString handling functions.
Category Functions Searching column indexOf indexOfAny indexOfNeither lastIndexOf lastIndexOfAny lastIndexOfNeither Comparison isNumeric Mutation capitalize Pruning and Filling center chomp chompPrefix chop detabber detab entab entabber leftJustify outdent rightJustify strip stripLeft stripRight wrap Substitution abbrev soundex soundexer succ tr translate Miscellaneous assumeUTF fromStringz lineSplitter representation splitLines toStringz Objects of types string, wstring, and dstring are value types and cannot be mutated element-by-element. For using mutation during building strings, use char[], wchar[], or dchar[]. The xxxstring types are preferable because they don't exhibit undesired aliasing, thus making code more robust.
The following functions are publicly imported:
Module Functions Publicly imported functions std.algorithm cmp, std,algorithm,comparison count, std,algorithm,searching endsWith, std,algorithm,searching startsWith, std,algorithm,searching std.array join, std,array replace, std,array replaceInPlace, std,array split, std,array empty, std,array std.format format, std,format sformat, std,format std.uni icmp, std,uni toLower, std,uni toLowerInPlace, std,uni toUpper, std,uni toUpperInPlace, std,uni There is a rich set of functions for string handling defined in other modules. Functions related to Unicode and ASCII are found in std.uni and std.ascii, respectively. Other functions that have a wider generality than just strings can be found in std.algorithm and std.range.
Source
std/string.d
string : (alias template) fd_remove_tree.fromStringz = std.string.fromStringz(Char)(return scope inout(Char)* cString) if (isSomeChar!Char)fromStringz, (alias) fd_remove_tree.toStringz = immutable(char)* std.string.toStringz(scope const(char)[] s) pure nothrow @trustedtoStringz;
// Neither is in druntime's core.sys.posix as of LDC 1.42.
extern (C) nothrow @nogc
{
(struct) core.sys.posix.dirent.DIRDIR* core.sys.posix.dirent.DIR* fd_remove_tree.fdopendir(int fd) nothrow @nogcfdopendir(int (parameter) int fdfd);
int int fd_remove_tree.unlinkat(int dirfd, const(char*) path, int flags) nothrow @nogcunlinkat(int (parameter) int dirfddirfd, const char* (parameter) const(char*) pathpath, int (parameter) int flagsflags);
}
// `AT_REMOVEDIR` differs per platform — the price of a portable `*at` layer.
version (linuxlinux)
enum (constant) int fd_remove_tree.AT_REMOVEDIR = 512AT_REMOVEDIR = 0x200;
else version (OSX)
enum AT_REMOVEDIR = 0x080;
else version (FreeBSD)
enum AT_REMOVEDIR = 0x800;
else
static assert(0, "AT_REMOVEDIR for this platform");
/// Removes everything inside the directory open at `dirfd`; the caller removes
/// the directory itself. `depth` bounds recursion so a pathological tree
/// cannot exhaust the stack or the fd table.
void void fd_remove_tree.removeContents(int dirfd, uint depth, ref uint removed)Removes everything inside the directory open at dirfd; the caller removes
the directory itself. depth bounds recursion so a pathological tree
cannot exhaust the stack or the fd table.
removeContents(int (parameter) int dirfddirfd, uint (parameter) uint depthdepth, ref uint (parameter) uint removedremoved)
{
bool std.exception.enforce!().enforce!bool(bool value, lazy const(char)[] msg = null, string file = __FILE__, ulong line = cast(ulong)__LINE__) pure @safeEnforces that the given value is true.
If the given value is false, an exception is thrown.
The
msg - error message as a string
dg - custom delegate that return a string and is only called if an exception occurred
ex - custom exception to be thrown. It is lazy and is only created if an exception occurred
enforce((parameter) uint depthdepth < 64, "tree too deep");
// `fdopendir` takes ownership of the fd it is given — and of its *file
// description*, whose offset `readdir` then moves. A `dup` would share that
// description with our own `dirfd`, so rustix re-opens the directory
// through the handle instead (`openat(fd, ".")`): a fresh description that
// still cannot be redirected, because `.` is resolved relative to the
// handle, not to a path.
const (local variable) const(int) listfdlistfd = openat(int core.sys.posix.fcntl.openat64(int, scope const(char*), int, ...) nothrow @nogcdirfd, ".", (constant) int core.sys.posix.fcntl.O_RDONLY = 0O_RDONLY | (constant) int core.sys.posix.fcntl.O_DIRECTORY = 65536O_DIRECTORY | (constant) int core.sys.posix.fcntl.O_NOFOLLOW = 131072O_NOFOLLOW | (constant) int core.sys.posix.fcntl.O_CLOEXEC = 524288O_CLOEXEC);
bool std.exception.enforce!().enforce!bool(bool value, lazy const(char)[] msg = null, string file = __FILE__, ulong line = cast(ulong)__LINE__) pure @safeEnforces that the given value is true.
If the given value is false, an exception is thrown.
The
msg - error message as a string
dg - custom delegate that return a string and is only called if an exception occurred
ex - custom exception to be thrown. It is lazy and is only created if an exception occurred
enforce((local variable) const(int) listfdlistfd >= 0, "reopen for listing failed");
(struct) core.sys.posix.dirent.DIRDIR* (local variable) core.sys.posix.dirent.DIR* dd = core.sys.posix.dirent.DIR* fd_remove_tree.fdopendir(int fd) nothrow @nogcfdopendir((local variable) const(int) listfdlistfd);
bool std.exception.enforce!().enforce!bool(bool value, lazy const(char)[] msg = null, string file = __FILE__, ulong line = cast(ulong)__LINE__) pure @safeEnforces that the given value is true.
If the given value is false, an exception is thrown.
The
msg - error message as a string
dg - custom delegate that return a string and is only called if an exception occurred
ex - custom exception to be thrown. It is lazy and is only created if an exception occurred
enforce((local variable) core.sys.posix.dirent.DIR* dd !is null, "fdopendir failed");
scope (exit) int core.sys.posix.dirent.closedir(core.sys.posix.dirent.DIR*) nothrow @nogcclosedir((local variable) core.sys.posix.dirent.DIR* dd);
while (true)
{
int core.stdc.errno.__errno_location() nothrow @nogc ref @trustederrno = 0;
(struct) core.sys.posix.dirent.direntdirent* (local variable) core.sys.posix.dirent.dirent* ee = readdir(core.sys.posix.dirent.dirent* core.sys.posix.dirent.readdir64(core.sys.posix.dirent.DIR*) nothrow @nogcd);
if ((local variable) core.sys.posix.dirent.dirent* ee is null)
break;
const (local variable) const(char[]) namename = (local variable) core.sys.posix.dirent.dirent* ee.(field) char[256] core.sys.posix.dirent.dirent.d_named_name.(constant) char* char[256].ptr = &(*e).d_nameptr.inout(char)[] std.string.fromStringz!char(return scope inout(char)* cString) pure nothrow @nogc @systemfromStringz;
if ((local variable) const(char[]) namename == "." || (local variable) const(char[]) namename == "..")
continue;
// Try as a directory first, WITHOUT following: a symlink to a
// directory fails here (`ENOTDIR` on Linux, `ELOOP` on older kernels
// and other systems — Rust std accepts both) and is unlinked below.
const (local variable) const(int) childchild = openat(int core.sys.posix.fcntl.openat64(int, scope const(char*), int, ...) nothrow @nogcdirfd, (local variable) const(char[]) namename.immutable(char)* std.string.toStringz(scope const(char)[] s) pure nothrow @trustedExamples
import core.stdc.string : strlen;
import std.conv : to;
auto p = toStringz("foo");
assert(strlen(p) == 3);
const(char)[] foo = "abbzxyzzy";
p = toStringz(foo[3 .. 5]);
assert(strlen(p) == 2);
string test = "";
p = toStringz(test);
assert(*p == 0);
test = "\0";
p = toStringz(test);
assert(*p == 0);
test = "foo\0";
p = toStringz(test);
assert(p[0] == 'f' && p[1] == 'o' && p[2] == 'o' && p[3] == 0);
const string test2 = "";
p = toStringz(test2);
assert(*p == 0);
assert(toStringz([]) is toStringz(""));
toStringz,
(constant) int core.sys.posix.fcntl.O_RDONLY = 0O_RDONLY | (constant) int core.sys.posix.fcntl.O_DIRECTORY = 65536O_DIRECTORY | (constant) int core.sys.posix.fcntl.O_NOFOLLOW = 131072O_NOFOLLOW | (constant) int core.sys.posix.fcntl.O_CLOEXEC = 524288O_CLOEXEC);
if ((local variable) const(int) childchild >= 0)
{
void fd_remove_tree.removeContents(int dirfd, uint depth, ref uint removed)Removes everything inside the directory open at dirfd; the caller removes
the directory itself. depth bounds recursion so a pathological tree
cannot exhaust the stack or the fd table.
removeContents((local variable) const(int) childchild, (parameter) uint depthdepth + 1, (parameter) uint removedremoved);
int core.sys.posix.unistd.close(int) nothrow @nogc @trustedclose((local variable) const(int) childchild);
bool std.exception.enforce!().enforce!bool(bool value, lazy const(char)[] msg = null, string file = __FILE__, ulong line = cast(ulong)__LINE__) pure @safeEnforces that the given value is true.
If the given value is false, an exception is thrown.
The
msg - error message as a string
dg - custom delegate that return a string and is only called if an exception occurred
ex - custom exception to be thrown. It is lazy and is only created if an exception occurred
enforce(int fd_remove_tree.unlinkat(int dirfd, const(char*) path, int flags) nothrow @nogcunlinkat((parameter) int dirfddirfd, (local variable) const(char[]) namename.immutable(char)* std.string.toStringz(scope const(char)[] s) pure nothrow @trustedExamples
import core.stdc.string : strlen;
import std.conv : to;
auto p = toStringz("foo");
assert(strlen(p) == 3);
const(char)[] foo = "abbzxyzzy";
p = toStringz(foo[3 .. 5]);
assert(strlen(p) == 2);
string test = "";
p = toStringz(test);
assert(*p == 0);
test = "\0";
p = toStringz(test);
assert(*p == 0);
test = "foo\0";
p = toStringz(test);
assert(p[0] == 'f' && p[1] == 'o' && p[2] == 'o' && p[3] == 0);
const string test2 = "";
p = toStringz(test2);
assert(*p == 0);
assert(toStringz([]) is toStringz(""));
toStringz, (constant) int fd_remove_tree.AT_REMOVEDIR = 512AT_REMOVEDIR) == 0,
string std.conv.text!(string, const(char)[])(string __param_0, const(char)[] __param_1) pure nothrow @safeConvenience functions for converting one or more arguments
of any type into text (the three character widths).
text("rmdir ", (local variable) const(char[]) namename));
}
else
bool std.exception.enforce!().enforce!bool(bool value, lazy const(char)[] msg = null, string file = __FILE__, ulong line = cast(ulong)__LINE__) pure @safeEnforces that the given value is true.
If the given value is false, an exception is thrown.
The
msg - error message as a string
dg - custom delegate that return a string and is only called if an exception occurred
ex - custom exception to be thrown. It is lazy and is only created if an exception occurred
enforce(int fd_remove_tree.unlinkat(int dirfd, const(char*) path, int flags) nothrow @nogcunlinkat((parameter) int dirfddirfd, (local variable) const(char[]) namename.immutable(char)* std.string.toStringz(scope const(char)[] s) pure nothrow @trustedExamples
import core.stdc.string : strlen;
import std.conv : to;
auto p = toStringz("foo");
assert(strlen(p) == 3);
const(char)[] foo = "abbzxyzzy";
p = toStringz(foo[3 .. 5]);
assert(strlen(p) == 2);
string test = "";
p = toStringz(test);
assert(*p == 0);
test = "\0";
p = toStringz(test);
assert(*p == 0);
test = "foo\0";
p = toStringz(test);
assert(p[0] == 'f' && p[1] == 'o' && p[2] == 'o' && p[3] == 0);
const string test2 = "";
p = toStringz(test2);
assert(*p == 0);
assert(toStringz([]) is toStringz(""));
toStringz, 0) == 0, string std.conv.text!(string, const(char)[])(string __param_0, const(char)[] __param_1) pure nothrow @safeConvenience functions for converting one or more arguments
of any type into text (the three character widths).
text("unlink ", (local variable) const(char[]) namename));
(parameter) uint removedremoved++;
}
}
int int D main()main()
{
const (local variable) const(string) basebase = string std.path.buildPath!char(const(char)[][] paths...) pure nothrow @safeCombines one or more path segments.
This function takes a set of path segments, given as an input
range of string elements or as a set of string arguments,
and concatenates them with each other. Directory separators
are inserted between segments if necessary. If any of the
path segments are absolute (as defined by isAbsolute), the
preceding segments will be dropped.
On Windows, if one of the path segments are rooted, but not absolute
(e.g. \foo), all preceding path segments down to the previous
root will be dropped. (See below for an example.)
This function always allocates memory to hold the resulting path.
The variadic overload is guaranteed to only perform a single
allocation, as is the range version if paths is a forward
range.
Examples
version (Posix)
{
assert(buildPath("foo", "bar", "baz") == "foo/bar/baz");
assert(buildPath("/foo/", "bar/baz") == "/foo/bar/baz");
assert(buildPath("/foo", "/bar") == "/bar");
}
version (Windows)
{
assert(buildPath("foo", "bar", "baz") == `foo\bar\baz`);
assert(buildPath(`c:\foo`, `bar\baz`) == `c:\foo\bar\baz`);
assert(buildPath("foo", `d:\bar`) == `d:\bar`);
assert(buildPath("foo", `\bar`) == `\bar`);
assert(buildPath(`c:\foo`, `\bar`) == `c:\bar`);
}
buildPath(string std.file.tempDir() @trustedReturns the path to a directory for temporary files.
On POSIX platforms, it searches through the following list of directories
and returns the first one which is found to exist:
The directory given by the TMPDIR environment variable.
The directory given by the TEMP environment variable.
The directory given by the TMP environment variable.
/tmp/
/var/tmp/
/usr/tmp/
On all platforms, tempDir returns the current working directory on failure.
The return value of the function is cached, so the procedures described
below will only be performed the first time the function is called. All
subsequent runs will return the same string, regardless of whether
environment variables and directory structures have changed in the
meantime.
The POSIX tempDir algorithm is inspired by Python's
tempfile.tempdir.
Examples
import std.ascii : letters;
import std.conv : to;
import std.path : buildPath;
import std.random : randomSample;
import std.utf : byCodeUnit;
// random id with 20 letters
auto id = letters.byCodeUnit.randomSample(20).to!string;
auto myFile = tempDir.buildPath(id ~ "my_tmp_file");
scope(exit) myFile.remove;
myFile.write("hello");
assert(myFile.readText == "hello");
tempDir, "fd-remove-tree-demo");
if ((local variable) const(string) basebase.bool std.file.exists!string(string name) nothrow @nogc @safeDetermine whether the given file (or directory) exists.
exists)
void std.file.rmdirRecurse(scope const(char)[] pathname) @safeRemove directory and all of its content and subdirectories,
recursively.
rmdirRecurse((local variable) const(string) basebase);
scope (exit) void std.file.rmdirRecurse(scope const(char)[] pathname) @safeRemove directory and all of its content and subdirectories,
recursively.
rmdirRecurse((local variable) const(string) basebase);
const (local variable) const(string) outsideoutside = string std.path.buildPath!char(const(char)[][] paths...) pure nothrow @safeCombines one or more path segments.
This function takes a set of path segments, given as an input
range of string elements or as a set of string arguments,
and concatenates them with each other. Directory separators
are inserted between segments if necessary. If any of the
path segments are absolute (as defined by isAbsolute), the
preceding segments will be dropped.
On Windows, if one of the path segments are rooted, but not absolute
(e.g. \foo), all preceding path segments down to the previous
root will be dropped. (See below for an example.)
This function always allocates memory to hold the resulting path.
The variadic overload is guaranteed to only perform a single
allocation, as is the range version if paths is a forward
range.
Examples
version (Posix)
{
assert(buildPath("foo", "bar", "baz") == "foo/bar/baz");
assert(buildPath("/foo/", "bar/baz") == "/foo/bar/baz");
assert(buildPath("/foo", "/bar") == "/bar");
}
version (Windows)
{
assert(buildPath("foo", "bar", "baz") == `foo\bar\baz`);
assert(buildPath(`c:\foo`, `bar\baz`) == `c:\foo\bar\baz`);
assert(buildPath("foo", `d:\bar`) == `d:\bar`);
assert(buildPath("foo", `\bar`) == `\bar`);
assert(buildPath(`c:\foo`, `\bar`) == `c:\bar`);
}
buildPath((local variable) const(string) basebase, "outside");
const (local variable) const(string) victimvictim = string std.path.buildPath!char(const(char)[][] paths...) pure nothrow @safeCombines one or more path segments.
This function takes a set of path segments, given as an input
range of string elements or as a set of string arguments,
and concatenates them with each other. Directory separators
are inserted between segments if necessary. If any of the
path segments are absolute (as defined by isAbsolute), the
preceding segments will be dropped.
On Windows, if one of the path segments are rooted, but not absolute
(e.g. \foo), all preceding path segments down to the previous
root will be dropped. (See below for an example.)
This function always allocates memory to hold the resulting path.
The variadic overload is guaranteed to only perform a single
allocation, as is the range version if paths is a forward
range.
Examples
version (Posix)
{
assert(buildPath("foo", "bar", "baz") == "foo/bar/baz");
assert(buildPath("/foo/", "bar/baz") == "/foo/bar/baz");
assert(buildPath("/foo", "/bar") == "/bar");
}
version (Windows)
{
assert(buildPath("foo", "bar", "baz") == `foo\bar\baz`);
assert(buildPath(`c:\foo`, `bar\baz`) == `c:\foo\bar\baz`);
assert(buildPath("foo", `d:\bar`) == `d:\bar`);
assert(buildPath("foo", `\bar`) == `\bar`);
assert(buildPath(`c:\foo`, `\bar`) == `c:\bar`);
}
buildPath((local variable) const(string) basebase, "victim");
void std.file.mkdirRecurse(scope const(char)[] pathname) @safeMake directory and all parent directories as needed.
Does nothing if the directory specified by
pathname already exists.
Examples
import std.path : buildPath;
auto dir = deleteme ~ "dir";
scope(exit) dir.rmdirRecurse;
dir.mkdir;
assert(dir.exists);
dir.mkdirRecurse; // does nothing
// creates all parent directories as needed
auto nested = dir.buildPath("a", "b", "c");
nested.mkdirRecurse;
assert(nested.exists);
import std.exception : assertThrown;
scope(exit) deleteme.remove;
deleteme.write("a");
// cannot make directory as it's already a file
assertThrown!FileException(deleteme.mkdirRecurse);
mkdirRecurse(string std.path.buildPath!char(const(char)[][] paths...) pure nothrow @safeCombines one or more path segments.
This function takes a set of path segments, given as an input
range of string elements or as a set of string arguments,
and concatenates them with each other. Directory separators
are inserted between segments if necessary. If any of the
path segments are absolute (as defined by isAbsolute), the
preceding segments will be dropped.
On Windows, if one of the path segments are rooted, but not absolute
(e.g. \foo), all preceding path segments down to the previous
root will be dropped. (See below for an example.)
This function always allocates memory to hold the resulting path.
The variadic overload is guaranteed to only perform a single
allocation, as is the range version if paths is a forward
range.
Examples
version (Posix)
{
assert(buildPath("foo", "bar", "baz") == "foo/bar/baz");
assert(buildPath("/foo/", "bar/baz") == "/foo/bar/baz");
assert(buildPath("/foo", "/bar") == "/bar");
}
version (Windows)
{
assert(buildPath("foo", "bar", "baz") == `foo\bar\baz`);
assert(buildPath(`c:\foo`, `bar\baz`) == `c:\foo\bar\baz`);
assert(buildPath("foo", `d:\bar`) == `d:\bar`);
assert(buildPath("foo", `\bar`) == `\bar`);
assert(buildPath(`c:\foo`, `\bar`) == `c:\bar`);
}
buildPath((local variable) const(string) outsideoutside));
void std.file.write!string(string name, const(void[]) buffer) @safeWrite buffer to file name.
Creates the file if it does not already exist.
Examples
scope(exit)
{
assert(exists(deleteme));
remove(deleteme);
}
int[] a = [ 0, 1, 1, 2, 3, 5, 8 ];
write(deleteme, a); // deleteme is the name of a temporary file
const bytes = read(deleteme);
const fileInts = () @trusted { return cast(int[]) bytes; }();
assert(fileInts == a);
write(string std.path.buildPath!char(const(char)[][] paths...) pure nothrow @safeCombines one or more path segments.
This function takes a set of path segments, given as an input
range of string elements or as a set of string arguments,
and concatenates them with each other. Directory separators
are inserted between segments if necessary. If any of the
path segments are absolute (as defined by isAbsolute), the
preceding segments will be dropped.
On Windows, if one of the path segments are rooted, but not absolute
(e.g. \foo), all preceding path segments down to the previous
root will be dropped. (See below for an example.)
This function always allocates memory to hold the resulting path.
The variadic overload is guaranteed to only perform a single
allocation, as is the range version if paths is a forward
range.
Examples
version (Posix)
{
assert(buildPath("foo", "bar", "baz") == "foo/bar/baz");
assert(buildPath("/foo/", "bar/baz") == "/foo/bar/baz");
assert(buildPath("/foo", "/bar") == "/bar");
}
version (Windows)
{
assert(buildPath("foo", "bar", "baz") == `foo\bar\baz`);
assert(buildPath(`c:\foo`, `bar\baz`) == `c:\foo\bar\baz`);
assert(buildPath("foo", `d:\bar`) == `d:\bar`);
assert(buildPath("foo", `\bar`) == `\bar`);
assert(buildPath(`c:\foo`, `\bar`) == `c:\bar`);
}
buildPath((local variable) const(string) outsideoutside, "precious.txt"), "keep me");
void std.file.mkdirRecurse(scope const(char)[] pathname) @safeMake directory and all parent directories as needed.
Does nothing if the directory specified by
pathname already exists.
Examples
import std.path : buildPath;
auto dir = deleteme ~ "dir";
scope(exit) dir.rmdirRecurse;
dir.mkdir;
assert(dir.exists);
dir.mkdirRecurse; // does nothing
// creates all parent directories as needed
auto nested = dir.buildPath("a", "b", "c");
nested.mkdirRecurse;
assert(nested.exists);
import std.exception : assertThrown;
scope(exit) deleteme.remove;
deleteme.write("a");
// cannot make directory as it's already a file
assertThrown!FileException(deleteme.mkdirRecurse);
mkdirRecurse(string std.path.buildPath!char(const(char)[][] paths...) pure nothrow @safeCombines one or more path segments.
This function takes a set of path segments, given as an input
range of string elements or as a set of string arguments,
and concatenates them with each other. Directory separators
are inserted between segments if necessary. If any of the
path segments are absolute (as defined by isAbsolute), the
preceding segments will be dropped.
On Windows, if one of the path segments are rooted, but not absolute
(e.g. \foo), all preceding path segments down to the previous
root will be dropped. (See below for an example.)
This function always allocates memory to hold the resulting path.
The variadic overload is guaranteed to only perform a single
allocation, as is the range version if paths is a forward
range.
Examples
version (Posix)
{
assert(buildPath("foo", "bar", "baz") == "foo/bar/baz");
assert(buildPath("/foo/", "bar/baz") == "/foo/bar/baz");
assert(buildPath("/foo", "/bar") == "/bar");
}
version (Windows)
{
assert(buildPath("foo", "bar", "baz") == `foo\bar\baz`);
assert(buildPath(`c:\foo`, `bar\baz`) == `c:\foo\bar\baz`);
assert(buildPath("foo", `d:\bar`) == `d:\bar`);
assert(buildPath("foo", `\bar`) == `\bar`);
assert(buildPath(`c:\foo`, `\bar`) == `c:\bar`);
}
buildPath((local variable) const(string) victimvictim, "sub", "deeper"));
void std.file.write!string(string name, const(void[]) buffer) @safeWrite buffer to file name.
Creates the file if it does not already exist.
Examples
scope(exit)
{
assert(exists(deleteme));
remove(deleteme);
}
int[] a = [ 0, 1, 1, 2, 3, 5, 8 ];
write(deleteme, a); // deleteme is the name of a temporary file
const bytes = read(deleteme);
const fileInts = () @trusted { return cast(int[]) bytes; }();
assert(fileInts == a);
write(string std.path.buildPath!char(const(char)[][] paths...) pure nothrow @safeCombines one or more path segments.
This function takes a set of path segments, given as an input
range of string elements or as a set of string arguments,
and concatenates them with each other. Directory separators
are inserted between segments if necessary. If any of the
path segments are absolute (as defined by isAbsolute), the
preceding segments will be dropped.
On Windows, if one of the path segments are rooted, but not absolute
(e.g. \foo), all preceding path segments down to the previous
root will be dropped. (See below for an example.)
This function always allocates memory to hold the resulting path.
The variadic overload is guaranteed to only perform a single
allocation, as is the range version if paths is a forward
range.
Examples
version (Posix)
{
assert(buildPath("foo", "bar", "baz") == "foo/bar/baz");
assert(buildPath("/foo/", "bar/baz") == "/foo/bar/baz");
assert(buildPath("/foo", "/bar") == "/bar");
}
version (Windows)
{
assert(buildPath("foo", "bar", "baz") == `foo\bar\baz`);
assert(buildPath(`c:\foo`, `bar\baz`) == `c:\foo\bar\baz`);
assert(buildPath("foo", `d:\bar`) == `d:\bar`);
assert(buildPath("foo", `\bar`) == `\bar`);
assert(buildPath(`c:\foo`, `\bar`) == `c:\bar`);
}
buildPath((local variable) const(string) victimvictim, "top.txt"), "x");
void std.file.write!string(string name, const(void[]) buffer) @safeWrite buffer to file name.
Creates the file if it does not already exist.
Examples
scope(exit)
{
assert(exists(deleteme));
remove(deleteme);
}
int[] a = [ 0, 1, 1, 2, 3, 5, 8 ];
write(deleteme, a); // deleteme is the name of a temporary file
const bytes = read(deleteme);
const fileInts = () @trusted { return cast(int[]) bytes; }();
assert(fileInts == a);
write(string std.path.buildPath!char(const(char)[][] paths...) pure nothrow @safeCombines one or more path segments.
This function takes a set of path segments, given as an input
range of string elements or as a set of string arguments,
and concatenates them with each other. Directory separators
are inserted between segments if necessary. If any of the
path segments are absolute (as defined by isAbsolute), the
preceding segments will be dropped.
On Windows, if one of the path segments are rooted, but not absolute
(e.g. \foo), all preceding path segments down to the previous
root will be dropped. (See below for an example.)
This function always allocates memory to hold the resulting path.
The variadic overload is guaranteed to only perform a single
allocation, as is the range version if paths is a forward
range.
Examples
version (Posix)
{
assert(buildPath("foo", "bar", "baz") == "foo/bar/baz");
assert(buildPath("/foo/", "bar/baz") == "/foo/bar/baz");
assert(buildPath("/foo", "/bar") == "/bar");
}
version (Windows)
{
assert(buildPath("foo", "bar", "baz") == `foo\bar\baz`);
assert(buildPath(`c:\foo`, `bar\baz`) == `c:\foo\bar\baz`);
assert(buildPath("foo", `d:\bar`) == `d:\bar`);
assert(buildPath("foo", `\bar`) == `\bar`);
assert(buildPath(`c:\foo`, `\bar`) == `c:\bar`);
}
buildPath((local variable) const(string) victimvictim, "sub", "deeper", "leaf.txt"), "x");
void std.file.symlink!(string, string)(string original, string link) @safeThis function is POSIX-Only.
Creates a symbolic link (symlink).
symlink((local variable) const(string) outsideoutside, string std.path.buildPath!char(const(char)[][] paths...) pure nothrow @safeCombines one or more path segments.
This function takes a set of path segments, given as an input
range of string elements or as a set of string arguments,
and concatenates them with each other. Directory separators
are inserted between segments if necessary. If any of the
path segments are absolute (as defined by isAbsolute), the
preceding segments will be dropped.
On Windows, if one of the path segments are rooted, but not absolute
(e.g. \foo), all preceding path segments down to the previous
root will be dropped. (See below for an example.)
This function always allocates memory to hold the resulting path.
The variadic overload is guaranteed to only perform a single
allocation, as is the range version if paths is a forward
range.
Examples
version (Posix)
{
assert(buildPath("foo", "bar", "baz") == "foo/bar/baz");
assert(buildPath("/foo/", "bar/baz") == "/foo/bar/baz");
assert(buildPath("/foo", "/bar") == "/bar");
}
version (Windows)
{
assert(buildPath("foo", "bar", "baz") == `foo\bar\baz`);
assert(buildPath(`c:\foo`, `bar\baz`) == `c:\foo\bar\baz`);
assert(buildPath("foo", `d:\bar`) == `d:\bar`);
assert(buildPath("foo", `\bar`) == `\bar`);
assert(buildPath(`c:\foo`, `\bar`) == `c:\bar`);
}
buildPath((local variable) const(string) victimvictim, "sub", "escape")); // the trap
const (local variable) const(int) fdfd = open(int core.sys.posix.fcntl.open64(scope const(char*), int, ...) nothrow @nogcvictim.immutable(char)* std.string.toStringz(scope const(char)[] s) pure nothrow @trustedExamples
import core.stdc.string : strlen;
import std.conv : to;
auto p = toStringz("foo");
assert(strlen(p) == 3);
const(char)[] foo = "abbzxyzzy";
p = toStringz(foo[3 .. 5]);
assert(strlen(p) == 2);
string test = "";
p = toStringz(test);
assert(*p == 0);
test = "\0";
p = toStringz(test);
assert(*p == 0);
test = "foo\0";
p = toStringz(test);
assert(p[0] == 'f' && p[1] == 'o' && p[2] == 'o' && p[3] == 0);
const string test2 = "";
p = toStringz(test2);
assert(*p == 0);
assert(toStringz([]) is toStringz(""));
toStringz, (constant) int core.sys.posix.fcntl.O_RDONLY = 0O_RDONLY | (constant) int core.sys.posix.fcntl.O_DIRECTORY = 65536O_DIRECTORY | (constant) int core.sys.posix.fcntl.O_NOFOLLOW = 131072O_NOFOLLOW | (constant) int core.sys.posix.fcntl.O_CLOEXEC = 524288O_CLOEXEC);
assert((local variable) const(int) fdfd >= 0);
uint (local variable) uint removedremoved;
void fd_remove_tree.removeContents(int dirfd, uint depth, ref uint removed)Removes everything inside the directory open at dirfd; the caller removes
the directory itself. depth bounds recursion so a pathological tree
cannot exhaust the stack or the fd table.
removeContents((local variable) const(int) fdfd, 0, (local variable) uint removedremoved);
int core.sys.posix.unistd.close(int) nothrow @nogc @trustedclose((local variable) const(int) fdfd);
void std.file.rmdirRecurse(scope const(char)[] pathname) @safeRemove directory and all of its content and subdirectories,
recursively.
rmdirRecurse((local variable) const(string) victimvictim); // the now-empty root, by path — it is ours
void std.stdio.writefln!(char, uint)(in char[] fmt, uint __param_1) @safeEquivalent to writef(fmt, args, '\n').
writefln("removed %d entries beneath victim/", (local variable) uint removedremoved);
void std.stdio.writefln!(char, bool)(in char[] fmt, bool __param_1) @safeEquivalent to writef(fmt, args, '\n').
writefln("victim/ exists afterwards: %s", (local variable) const(string) victimvictim.bool std.file.exists!string(string name) nothrow @nogc @safeDetermine whether the given file (or directory) exists.
exists);
void std.stdio.writefln!(char, bool)(in char[] fmt, bool __param_1) @safeEquivalent to writef(fmt, args, '\n').
writefln("outside/precious.txt survived: %s",
string std.path.buildPath!char(const(char)[][] paths...) pure nothrow @safeCombines one or more path segments.
This function takes a set of path segments, given as an input
range of string elements or as a set of string arguments,
and concatenates them with each other. Directory separators
are inserted between segments if necessary. If any of the
path segments are absolute (as defined by isAbsolute), the
preceding segments will be dropped.
On Windows, if one of the path segments are rooted, but not absolute
(e.g. \foo), all preceding path segments down to the previous
root will be dropped. (See below for an example.)
This function always allocates memory to hold the resulting path.
The variadic overload is guaranteed to only perform a single
allocation, as is the range version if paths is a forward
range.
Examples
version (Posix)
{
assert(buildPath("foo", "bar", "baz") == "foo/bar/baz");
assert(buildPath("/foo/", "bar/baz") == "/foo/bar/baz");
assert(buildPath("/foo", "/bar") == "/bar");
}
version (Windows)
{
assert(buildPath("foo", "bar", "baz") == `foo\bar\baz`);
assert(buildPath(`c:\foo`, `bar\baz`) == `c:\foo\bar\baz`);
assert(buildPath("foo", `d:\bar`) == `d:\bar`);
assert(buildPath("foo", `\bar`) == `\bar`);
assert(buildPath(`c:\foo`, `\bar`) == `c:\bar`);
}
buildPath((local variable) const(string) outsideoutside, "precious.txt").bool std.file.exists!string(string name) nothrow @nogc @safeDetermine whether the given file (or directory) exists.
exists);
return 0;
}