forked from lix-project/lix
* A command to query the paths referenced by an fstate expression.
* Use a temporary directory for build actions.
This commit is contained in:
parent
a279137327
commit
40274c1f4f
30
src/fix.cc
30
src/fix.cc
|
@ -13,29 +13,6 @@ typedef ATerm Expr;
|
||||||
static Expr evalFile(string fileName);
|
static Expr evalFile(string fileName);
|
||||||
|
|
||||||
|
|
||||||
static bool isFState(Expr e, string & path)
|
|
||||||
{
|
|
||||||
char * s1, * s2, * s3;
|
|
||||||
Expr e1, e2;
|
|
||||||
if (ATmatch(e, "Path(<str>, <term>, [<list>])", &s1, &e1, &e2)) {
|
|
||||||
path = s1;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (ATmatch(e, "Derive(<str>, <str>, [<list>], <str>, [<list>])",
|
|
||||||
&s1, &s2, &e1, &s3, &e2))
|
|
||||||
{
|
|
||||||
path = s3;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (ATmatch(e, "Include(<str>)", &s1))
|
|
||||||
{
|
|
||||||
string fn = queryPathByHash(parseHash(s1));
|
|
||||||
return isFState(evalFile(fn), path);
|
|
||||||
}
|
|
||||||
else return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static Expr substExpr(string x, Expr rep, Expr e)
|
static Expr substExpr(string x, Expr rep, Expr e)
|
||||||
{
|
{
|
||||||
char * s;
|
char * s;
|
||||||
|
@ -113,8 +90,7 @@ static Expr evalExpr(Expr e)
|
||||||
ATmatch(e, "Function([<list>], <term>)", &e1, &e2))
|
ATmatch(e, "Function([<list>], <term>)", &e1, &e2))
|
||||||
return e;
|
return e;
|
||||||
|
|
||||||
string dummy;
|
if (fstatePath(e) != "") return e; /* !!! hack */
|
||||||
if (isFState(e, dummy)) return e;
|
|
||||||
|
|
||||||
/* Application. */
|
/* Application. */
|
||||||
if (ATmatch(e, "App(<term>, [<list>])", &e1, &e2)) {
|
if (ATmatch(e, "App(<term>, [<list>])", &e1, &e2)) {
|
||||||
|
@ -165,8 +141,8 @@ static Expr evalExpr(Expr e)
|
||||||
string key = it->first;
|
string key = it->first;
|
||||||
ATerm value = it->second;
|
ATerm value = it->second;
|
||||||
|
|
||||||
string path;
|
string path = fstatePath(value);
|
||||||
if (isFState(value, path)) {
|
if (path != "") {
|
||||||
ins = ATinsert(ins, value);
|
ins = ATinsert(ins, value);
|
||||||
env = ATinsert(env, ATmake("(<str>, <str>)",
|
env = ATinsert(env, ATmake("(<str>, <str>)",
|
||||||
key.c_str(), path.c_str()));
|
key.c_str(), path.c_str()));
|
||||||
|
|
148
src/fstate.cc
148
src/fstate.cc
|
@ -1,4 +1,5 @@
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <set>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
@ -17,17 +18,20 @@
|
||||||
typedef map<string, string> Environment;
|
typedef map<string, string> Environment;
|
||||||
|
|
||||||
|
|
||||||
/* Return true iff the given path exists. */
|
class AutoDelete
|
||||||
bool pathExists(const string & path)
|
|
||||||
{
|
{
|
||||||
int res;
|
string path;
|
||||||
struct stat st;
|
public:
|
||||||
res = stat(path.c_str(), &st);
|
|
||||||
if (!res) return true;
|
AutoDelete(const string & p) : path(p)
|
||||||
if (errno != ENOENT)
|
{
|
||||||
throw SysError(format("getting status of %1%") % path);
|
}
|
||||||
return false;
|
|
||||||
}
|
~AutoDelete()
|
||||||
|
{
|
||||||
|
deletePath(path);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/* Run a program. */
|
/* Run a program. */
|
||||||
|
@ -36,9 +40,19 @@ static void runProgram(const string & program, Environment env)
|
||||||
/* Create a log file. */
|
/* Create a log file. */
|
||||||
string logFileName = nixLogDir + "/run.log";
|
string logFileName = nixLogDir + "/run.log";
|
||||||
/* !!! auto-pclose on exit */
|
/* !!! auto-pclose on exit */
|
||||||
FILE * logFile = popen(("tee " + logFileName + " >&2").c_str(), "w"); /* !!! escaping */
|
FILE * logFile = popen(("tee -a " + logFileName + " >&2").c_str(), "w"); /* !!! escaping */
|
||||||
if (!logFile)
|
if (!logFile)
|
||||||
throw SysError(format("unable to create log file %1%") % logFileName);
|
throw SysError(format("creating log file `%1%'") % logFileName);
|
||||||
|
|
||||||
|
/* Create a temporary directory where the build will take
|
||||||
|
place. */
|
||||||
|
static int counter = 0;
|
||||||
|
string tmpDir = (format("/tmp/nix-%1%-%2%") % getpid() % counter++).str();
|
||||||
|
|
||||||
|
if (mkdir(tmpDir.c_str(), 0777) == -1)
|
||||||
|
throw SysError(format("creating directory `%1%'") % tmpDir);
|
||||||
|
|
||||||
|
AutoDelete delTmpDir(tmpDir);
|
||||||
|
|
||||||
/* Fork a child to build the package. */
|
/* Fork a child to build the package. */
|
||||||
pid_t pid;
|
pid_t pid;
|
||||||
|
@ -51,31 +65,8 @@ static void runProgram(const string & program, Environment env)
|
||||||
|
|
||||||
try { /* child */
|
try { /* child */
|
||||||
|
|
||||||
#if 0
|
if (chdir(tmpDir.c_str()) == -1)
|
||||||
/* Try to use a prebuilt. */
|
throw SysError(format("changing into to `%1%'") % tmpDir);
|
||||||
string prebuiltHashS, prebuiltFile;
|
|
||||||
if (queryDB(nixDB, dbPrebuilts, hash, prebuiltHashS)) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
prebuiltFile = getFile(parseHash(prebuiltHashS));
|
|
||||||
} catch (Error e) {
|
|
||||||
cerr << "cannot obtain prebuilt (ignoring): " << e.what() << endl;
|
|
||||||
goto build;
|
|
||||||
}
|
|
||||||
|
|
||||||
cerr << "substituting prebuilt " << prebuiltFile << endl;
|
|
||||||
|
|
||||||
int res = system(("tar xfj " + prebuiltFile + " 1>&2").c_str()); // !!! escaping
|
|
||||||
if (WEXITSTATUS(res) != 0)
|
|
||||||
/* This is a fatal error, because path may now
|
|
||||||
have clobbered. */
|
|
||||||
throw Error("cannot unpack " + prebuiltFile);
|
|
||||||
|
|
||||||
_exit(0);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// build:
|
|
||||||
|
|
||||||
/* Fill in the environment. We don't bother freeing
|
/* Fill in the environment. We don't bother freeing
|
||||||
the strings, since we'll exec or die soon
|
the strings, since we'll exec or die soon
|
||||||
|
@ -157,15 +148,7 @@ Hash hashTerm(ATerm t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
struct RStatus
|
ATerm termFromHash(const Hash & hash)
|
||||||
{
|
|
||||||
/* !!! the comparator of this hash should match the semantics of
|
|
||||||
the file system */
|
|
||||||
// map<string, Hash> paths;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
static ATerm termFromHash(const Hash & hash)
|
|
||||||
{
|
{
|
||||||
string path = queryPathByHash(hash);
|
string path = queryPathByHash(hash);
|
||||||
ATerm t = ATreadFromNamedFile(path.c_str());
|
ATerm t = ATreadFromNamedFile(path.c_str());
|
||||||
|
@ -188,7 +171,7 @@ Hash writeTerm(ATerm t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static FState realise(RStatus & status, FState fs)
|
static FState realise(FState fs)
|
||||||
{
|
{
|
||||||
char * s1, * s2, * s3;
|
char * s1, * s2, * s3;
|
||||||
Content content;
|
Content content;
|
||||||
|
@ -212,7 +195,7 @@ static FState realise(RStatus & status, FState fs)
|
||||||
/* Fall through. */
|
/* Fall through. */
|
||||||
|
|
||||||
if (ATmatch(fs, "Include(<str>)", &s1)) {
|
if (ATmatch(fs, "Include(<str>)", &s1)) {
|
||||||
return realise(status, termFromHash(parseHash(s1)));
|
return realise(termFromHash(parseHash(s1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (ATmatch(fs, "Path(<str>, <term>, [<list>])", &s1, &content, &refs)) {
|
else if (ATmatch(fs, "Path(<str>, <term>, [<list>])", &s1, &content, &refs)) {
|
||||||
|
@ -227,7 +210,7 @@ static FState realise(RStatus & status, FState fs)
|
||||||
/* Realise referenced paths. */
|
/* Realise referenced paths. */
|
||||||
ATermList refs2 = ATempty;
|
ATermList refs2 = ATempty;
|
||||||
while (!ATisEmpty(refs)) {
|
while (!ATisEmpty(refs)) {
|
||||||
refs2 = ATinsert(refs2, realise(status, ATgetFirst(refs)));
|
refs2 = ATinsert(refs2, realise(ATgetFirst(refs)));
|
||||||
refs = ATgetNext(refs);
|
refs = ATgetNext(refs);
|
||||||
}
|
}
|
||||||
refs2 = ATreverse(refs2);
|
refs2 = ATreverse(refs2);
|
||||||
|
@ -278,7 +261,7 @@ static FState realise(RStatus & status, FState fs)
|
||||||
/* Realise inputs. */
|
/* Realise inputs. */
|
||||||
ATermList ins2 = ATempty;
|
ATermList ins2 = ATempty;
|
||||||
while (!ATisEmpty(ins)) {
|
while (!ATisEmpty(ins)) {
|
||||||
ins2 = ATinsert(ins2, realise(status, ATgetFirst(ins)));
|
ins2 = ATinsert(ins2, realise(ATgetFirst(ins)));
|
||||||
ins = ATgetNext(ins);
|
ins = ATgetNext(ins);
|
||||||
}
|
}
|
||||||
ins2 = ATreverse(ins2);
|
ins2 = ATreverse(ins2);
|
||||||
|
@ -335,6 +318,67 @@ static FState realise(RStatus & status, FState fs)
|
||||||
|
|
||||||
FState realiseFState(FState fs)
|
FState realiseFState(FState fs)
|
||||||
{
|
{
|
||||||
RStatus status;
|
return realise(fs);
|
||||||
return realise(status, fs);
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string fstatePath(FState fs)
|
||||||
|
{
|
||||||
|
char * s1, * s2, * s3;
|
||||||
|
FState e1, e2;
|
||||||
|
if (ATmatch(fs, "Path(<str>, <term>, [<list>])", &s1, &e1, &e2))
|
||||||
|
return s1;
|
||||||
|
else if (ATmatch(fs, "Derive(<str>, <str>, [<list>], <str>, [<list>])",
|
||||||
|
&s1, &s2, &e1, &s3, &e2))
|
||||||
|
return s3;
|
||||||
|
else if (ATmatch(fs, "Include(<str>)", &s1))
|
||||||
|
return fstatePath(termFromHash(parseHash(s1)));
|
||||||
|
else
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
typedef set<string> StringSet;
|
||||||
|
|
||||||
|
|
||||||
|
void fstateRefs2(FState fs, StringSet & paths)
|
||||||
|
{
|
||||||
|
char * s1, * s2, * s3;
|
||||||
|
FState e1, e2;
|
||||||
|
ATermList refs, ins;
|
||||||
|
|
||||||
|
if (ATmatch(fs, "Path(<str>, <term>, [<list>])", &s1, &e1, &refs)) {
|
||||||
|
paths.insert(s1);
|
||||||
|
|
||||||
|
while (!ATisEmpty(refs)) {
|
||||||
|
fstateRefs2(ATgetFirst(refs), paths);
|
||||||
|
refs = ATgetNext(refs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (ATmatch(fs, "Derive(<str>, <str>, [<list>], <str>, [<list>])",
|
||||||
|
&s1, &s2, &ins, &s3, &e2))
|
||||||
|
{
|
||||||
|
paths.insert(s3);
|
||||||
|
|
||||||
|
while (!ATisEmpty(ins)) {
|
||||||
|
fstateRefs2(ATgetFirst(ins), paths);
|
||||||
|
ins = ATgetNext(ins);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (ATmatch(fs, "Include(<str>)", &s1))
|
||||||
|
fstateRefs2(termFromHash(parseHash(s1)), paths);
|
||||||
|
|
||||||
|
else throw badTerm("bad fstate expression", fs);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Strings fstateRefs(FState fs)
|
||||||
|
{
|
||||||
|
StringSet paths;
|
||||||
|
fstateRefs2(fs, paths);
|
||||||
|
Strings paths2(paths.size());
|
||||||
|
copy(paths.begin(), paths.end(), paths2.begin());
|
||||||
|
return paths2;
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,9 +60,17 @@ typedef ATerm FState;
|
||||||
typedef ATerm Content;
|
typedef ATerm Content;
|
||||||
|
|
||||||
|
|
||||||
/* Realise a $f$-normalised expression in the file system. */
|
/* Realise an fstate expression in the file system. This requires
|
||||||
|
execution of all Derive() nodes. */
|
||||||
FState realiseFState(FState fs);
|
FState realiseFState(FState fs);
|
||||||
|
|
||||||
|
/* Return the path of an fstate expression. An empty string is
|
||||||
|
returned if the term is not a valid fstate expression. (!!!) */
|
||||||
|
string fstatePath(FState fs);
|
||||||
|
|
||||||
|
/* Return the paths referenced by fstate expression. */
|
||||||
|
Strings fstateRefs(FState fs);
|
||||||
|
|
||||||
/* Return a canonical textual representation of an expression. */
|
/* Return a canonical textual representation of an expression. */
|
||||||
string printTerm(ATerm t);
|
string printTerm(ATerm t);
|
||||||
|
|
||||||
|
@ -73,6 +81,9 @@ Error badTerm(const format & f, ATerm t);
|
||||||
/* Hash an aterm. */
|
/* Hash an aterm. */
|
||||||
Hash hashTerm(ATerm t);
|
Hash hashTerm(ATerm t);
|
||||||
|
|
||||||
|
/* Read an aterm from disk, given its hash. */
|
||||||
|
ATerm termFromHash(const Hash & hash);
|
||||||
|
|
||||||
/* Write an aterm to the Nix store directory, and return its hash. */
|
/* Write an aterm to the Nix store directory, and return its hash. */
|
||||||
Hash writeTerm(ATerm t);
|
Hash writeTerm(ATerm t);
|
||||||
|
|
||||||
|
|
90
src/nix.cc
90
src/nix.cc
|
@ -21,9 +21,10 @@ static ArgType argType = atpUnknown;
|
||||||
|
|
||||||
Operations:
|
Operations:
|
||||||
|
|
||||||
--install / -i: realise a Nix expression
|
--install / -i: realise an fstate
|
||||||
--delete / -d: delete paths from the Nix store
|
--delete / -d: delete paths from the Nix store
|
||||||
--add / -A: copy a path to the Nix store
|
--add / -A: copy a path to the Nix store
|
||||||
|
--query / -q: query information
|
||||||
|
|
||||||
--dump: dump a path as a Nix archive
|
--dump: dump a path as a Nix archive
|
||||||
--restore: restore a path from a Nix archive
|
--restore: restore a path from a Nix archive
|
||||||
|
@ -39,6 +40,11 @@ static ArgType argType = atpUnknown;
|
||||||
--file / -f: by file name
|
--file / -f: by file name
|
||||||
--hash / -h: by hash
|
--hash / -h: by hash
|
||||||
|
|
||||||
|
Query flags:
|
||||||
|
|
||||||
|
--path / -p: query the path of an fstate
|
||||||
|
--refs / -r: query paths referenced by an fstate
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
--verbose / -v: verbose operation
|
--verbose / -v: verbose operation
|
||||||
|
@ -54,10 +60,8 @@ static void getArgType(Strings & flags)
|
||||||
{
|
{
|
||||||
string arg = *it;
|
string arg = *it;
|
||||||
ArgType tp;
|
ArgType tp;
|
||||||
if (arg == "--hash" || arg == "-h")
|
if (arg == "--hash" || arg == "-h") tp = atpHash;
|
||||||
tp = atpHash;
|
else if (arg == "--file" || arg == "-f") tp = atpPath;
|
||||||
else if (arg == "--file" || arg == "-f")
|
|
||||||
tp = atpPath;
|
|
||||||
else { it++; continue; }
|
else { it++; continue; }
|
||||||
if (argType != atpUnknown)
|
if (argType != atpUnknown)
|
||||||
throw UsageError("only one argument type specified may be specified");
|
throw UsageError("only one argument type specified may be specified");
|
||||||
|
@ -69,6 +73,20 @@ static void getArgType(Strings & flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static Hash argToHash(const string & arg)
|
||||||
|
{
|
||||||
|
if (argType == atpHash)
|
||||||
|
return parseHash(arg);
|
||||||
|
else if (argType == atpPath) {
|
||||||
|
string path;
|
||||||
|
Hash hash;
|
||||||
|
addToStore(arg, path, hash);
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
else abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Realise (or install) paths from the given Nix fstate
|
/* Realise (or install) paths from the given Nix fstate
|
||||||
expressions. */
|
expressions. */
|
||||||
static void opInstall(Strings opFlags, Strings opArgs)
|
static void opInstall(Strings opFlags, Strings opArgs)
|
||||||
|
@ -78,20 +96,11 @@ static void opInstall(Strings opFlags, Strings opArgs)
|
||||||
|
|
||||||
for (Strings::iterator it = opArgs.begin();
|
for (Strings::iterator it = opArgs.begin();
|
||||||
it != opArgs.end(); it++)
|
it != opArgs.end(); it++)
|
||||||
{
|
realiseFState(termFromHash(argToHash(*it)));
|
||||||
Hash hash;
|
|
||||||
if (argType == atpHash)
|
|
||||||
hash = parseHash(*it);
|
|
||||||
else if (argType == atpPath) {
|
|
||||||
string path;
|
|
||||||
addToStore(*it, path, hash);
|
|
||||||
}
|
|
||||||
FState fs = ATmake("Include(<str>)", ((string) hash).c_str());
|
|
||||||
realiseFState(fs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Delete a path in the Nix store directory. */
|
||||||
static void opDelete(Strings opFlags, Strings opArgs)
|
static void opDelete(Strings opFlags, Strings opArgs)
|
||||||
{
|
{
|
||||||
if (!opFlags.empty()) throw UsageError("unknown flag");
|
if (!opFlags.empty()) throw UsageError("unknown flag");
|
||||||
|
@ -120,6 +129,51 @@ static void opAdd(Strings opFlags, Strings opArgs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Perform various sorts of queries. */
|
||||||
|
static void opQuery(Strings opFlags, Strings opArgs)
|
||||||
|
{
|
||||||
|
enum { qPath, qRefs, qUnknown } query = qPath;
|
||||||
|
|
||||||
|
for (Strings::iterator it = opFlags.begin();
|
||||||
|
it != opFlags.end(); )
|
||||||
|
{
|
||||||
|
string arg = *it;
|
||||||
|
if (arg == "--path" || arg == "-p") query = qPath;
|
||||||
|
else if (arg == "--refs" || arg == "-r") query = qRefs;
|
||||||
|
else { it++; continue; }
|
||||||
|
it = opFlags.erase(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
getArgType(opFlags);
|
||||||
|
if (!opFlags.empty()) throw UsageError("unknown flag");
|
||||||
|
|
||||||
|
for (Strings::iterator it = opArgs.begin();
|
||||||
|
it != opArgs.end(); it++)
|
||||||
|
{
|
||||||
|
Hash hash = argToHash(*it);
|
||||||
|
|
||||||
|
switch (query) {
|
||||||
|
|
||||||
|
case qPath:
|
||||||
|
cout << format("%s\n") %
|
||||||
|
(string) fstatePath(termFromHash(hash));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case qRefs: {
|
||||||
|
Strings refs = fstateRefs(termFromHash(hash));
|
||||||
|
for (Strings::iterator j = refs.begin();
|
||||||
|
j != refs.end(); j++)
|
||||||
|
cout << format("%s\n") % *j;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* A sink that writes dump output to stdout. */
|
/* A sink that writes dump output to stdout. */
|
||||||
struct StdoutSink : DumpSink
|
struct StdoutSink : DumpSink
|
||||||
{
|
{
|
||||||
|
@ -208,8 +262,10 @@ void run(Strings args)
|
||||||
op = opInstall;
|
op = opInstall;
|
||||||
else if (arg == "--delete" || arg == "-d")
|
else if (arg == "--delete" || arg == "-d")
|
||||||
op = opDelete;
|
op = opDelete;
|
||||||
else if (arg == "--add")
|
else if (arg == "--add" || arg == "-A")
|
||||||
op = opAdd;
|
op = opAdd;
|
||||||
|
else if (arg == "--query" || arg == "-q")
|
||||||
|
op = opQuery;
|
||||||
else if (arg == "--dump")
|
else if (arg == "--dump")
|
||||||
op = opDump;
|
op = opDump;
|
||||||
else if (arg == "--restore")
|
else if (arg == "--restore")
|
||||||
|
|
12
src/util.cc
12
src/util.cc
|
@ -66,6 +66,18 @@ string baseNameOf(string path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool pathExists(const string & path)
|
||||||
|
{
|
||||||
|
int res;
|
||||||
|
struct stat st;
|
||||||
|
res = stat(path.c_str(), &st);
|
||||||
|
if (!res) return true;
|
||||||
|
if (errno != ENOENT)
|
||||||
|
throw SysError(format("getting status of %1%") % path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void deletePath(string path)
|
void deletePath(string path)
|
||||||
{
|
{
|
||||||
struct stat st;
|
struct stat st;
|
||||||
|
|
|
@ -60,6 +60,8 @@ string dirOf(string path);
|
||||||
the final `/'. */
|
the final `/'. */
|
||||||
string baseNameOf(string path);
|
string baseNameOf(string path);
|
||||||
|
|
||||||
|
/* Return true iff the given path exists. */
|
||||||
|
bool pathExists(const string & path);
|
||||||
|
|
||||||
/* Delete a path; i.e., in the case of a directory, it is deleted
|
/* Delete a path; i.e., in the case of a directory, it is deleted
|
||||||
recursively. Don't use this at home, kids. */
|
recursively. Don't use this at home, kids. */
|
||||||
|
|
Loading…
Reference in a new issue