phobos/std/file.d

1791 lines
41 KiB
D

// Written in the D programming language.
/**
Utilities for manipulating files and scanning directories.
Authors:
$(WEB digitalmars.com, Walter Bright), $(WEB erdani.org, Andrei
Alexandrescu)
Macros:
WIKI = Phobos/StdFile
*/
/*
* Copyright (C) 2001-2004 by Digital Mars, www.digitalmars.com
* Written by Walter Bright, Christopher E. Miller, Andre Fornacon
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* o The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* o Altered source versions must be plainly marked as such, and must not
* be misrepresented as being the original software.
* o This notice may not be removed or altered from any source
* distribution.
*/
module std.file;
private import core.memory;
private import std.c.stdio;
private import std.c.stdlib;
private import core.stdc.errno;
private import std.path;
private import std.string;
private import std.regexp;
private import std.c.string;
private import std.traits;
private import std.conv;
private import std.contracts;
private import std.utf;
version (unittest) {
private import std.stdio; // for testing only
}
/* =========================== Win32 ======================= */
version (Win32)
{
private import std.c.windows.windows;
private import std.utf;
private import std.windows.syserror;
private import std.windows.charset;
private import std.date;
private import std.__fileinit;
bool useWfuncs = true; // initialized in std.__fileinit
/***********************************
* Exception thrown for file I/O errors.
*/
class FileException : Exception
{
uint errno; // operating system error code
this(string name)
{
this(name, "file I/O");
}
this(string name, string message)
{
super(name ~ ": " ~ message);
}
this(string name, uint errno)
{
this(name, sysErrorString(errno));
this.errno = errno;
}
}
/* **********************************
* Basic File operations.
*/
/********************************************
* Read file $(D name), return array of bytes read.
*
* Throws: $(D FileException) on error.
*/
void[] read(in string name)
{
DWORD numread;
HANDLE h;
if (useWfuncs)
{
const(wchar*) namez = std.utf.toUTF16z(name);
h = CreateFileW(namez,GENERIC_READ,FILE_SHARE_READ,null,OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,cast(HANDLE)null);
}
else
{
const(char*) namez = toMBSz(name);
h = CreateFileA(namez,GENERIC_READ,FILE_SHARE_READ,null,OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,cast(HANDLE)null);
}
if (h == INVALID_HANDLE_VALUE)
goto err1;
auto size = GetFileSize(h, null);
if (size == INVALID_FILE_SIZE)
goto err2;
auto buf = GC.malloc(size, GC.BlkAttr.NO_SCAN)[0 .. size];
if (ReadFile(h,buf.ptr,size,&numread,null) != 1)
goto err2;
if (numread != size)
goto err2;
if (!CloseHandle(h))
goto err;
return buf[0 .. size];
err2:
CloseHandle(h);
err:
delete buf;
err1:
throw new FileException(name, GetLastError());
}
/*********************************************
* Write buffer[] to file name[].
* Throws: $(D FileException) on error.
*/
void write(in string name, const void[] buffer)
{
HANDLE h;
DWORD numwritten;
if (useWfuncs)
{
const(wchar*) namez = std.utf.toUTF16z(name);
h = CreateFileW(namez,GENERIC_WRITE,0,null,CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,cast(HANDLE)null);
}
else
{
const(char*) namez = toMBSz(name);
h = CreateFileA(namez,GENERIC_WRITE,0,null,CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,cast(HANDLE)null);
}
if (h == INVALID_HANDLE_VALUE)
goto err;
if (WriteFile(h,buffer.ptr,buffer.length,&numwritten,null) != 1)
goto err2;
if (buffer.length != numwritten)
goto err2;
if (!CloseHandle(h))
goto err;
return;
err2:
CloseHandle(h);
err:
throw new FileException(name, GetLastError());
}
/*********************************************
* Append buffer[] to file name[].
* Throws: $(D FileException) on error.
*/
void append(in string name, in void[] buffer)
{
HANDLE h;
DWORD numwritten;
if (useWfuncs)
{
const(wchar*) namez = std.utf.toUTF16z(name);
h = CreateFileW(namez,GENERIC_WRITE,0,null,OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,cast(HANDLE)null);
}
else
{
const(char*) namez = toMBSz(name);
h = CreateFileA(namez,GENERIC_WRITE,0,null,OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,cast(HANDLE)null);
}
if (h == INVALID_HANDLE_VALUE)
goto err;
SetFilePointer(h, 0, null, FILE_END);
if (WriteFile(h,buffer.ptr,buffer.length,&numwritten,null) != 1)
goto err2;
if (buffer.length != numwritten)
goto err2;
if (!CloseHandle(h))
goto err;
return;
err2:
CloseHandle(h);
err:
throw new FileException(name, GetLastError());
}
/***************************************************
* Rename file from[] to to[].
* Throws: $(D FileException) on error.
*/
void rename(in string from, in string to)
{
BOOL result = void;
if (useWfuncs)
result = MoveFileW(std.utf.toUTF16z(from), std.utf.toUTF16z(to));
else
result = MoveFileA(toMBSz(from), toMBSz(to));
if (!result)
throw new FileException(to, GetLastError());
}
/***************************************************
* Delete file name[].
* Throws: $(D FileException) on error.
*/
void remove(in string name)
{
BOOL result = void;
if (useWfuncs)
result = DeleteFileW(std.utf.toUTF16z(name));
else
result = DeleteFileA(toMBSz(name));
if (!result)
throw new FileException(name, GetLastError());
}
/***************************************************
* Get size of file name[].
* Throws: $(D FileException) on error.
*/
ulong getSize(in string name)
{
HANDLE findhndl;
uint resulth;
uint resultl;
if (useWfuncs)
{
WIN32_FIND_DATAW filefindbuf;
findhndl = FindFirstFileW(std.utf.toUTF16z(name), &filefindbuf);
resulth = filefindbuf.nFileSizeHigh;
resultl = filefindbuf.nFileSizeLow;
}
else
{
WIN32_FIND_DATA filefindbuf;
findhndl = FindFirstFileA(toMBSz(name), &filefindbuf);
resulth = filefindbuf.nFileSizeHigh;
resultl = filefindbuf.nFileSizeLow;
}
if (findhndl == cast(HANDLE)-1)
{
throw new FileException(name, GetLastError());
}
FindClose(findhndl);
return (cast(ulong)resulth << 32) + resultl;
}
/*************************
* Get creation/access/modified times of file $(D name).
* Throws: $(D FileException) on error.
*/
void getTimes(in string name, out d_time ftc, out d_time fta, out d_time ftm)
{
HANDLE findhndl;
if (useWfuncs)
{
WIN32_FIND_DATAW filefindbuf;
findhndl = FindFirstFileW(std.utf.toUTF16z(name), &filefindbuf);
ftc = std.date.FILETIME2d_time(&filefindbuf.ftCreationTime);
fta = std.date.FILETIME2d_time(&filefindbuf.ftLastAccessTime);
ftm = std.date.FILETIME2d_time(&filefindbuf.ftLastWriteTime);
}
else
{
WIN32_FIND_DATA filefindbuf;
findhndl = FindFirstFileA(toMBSz(name), &filefindbuf);
ftc = std.date.FILETIME2d_time(&filefindbuf.ftCreationTime);
fta = std.date.FILETIME2d_time(&filefindbuf.ftLastAccessTime);
ftm = std.date.FILETIME2d_time(&filefindbuf.ftLastWriteTime);
}
if (findhndl == cast(HANDLE)-1)
{
throw new FileException(name, GetLastError());
}
FindClose(findhndl);
}
/***************************************************
* Does file name[] (or directory) exist?
* Return 1 if it does, 0 if not.
*/
bool exists(in string name)
{
uint result;
if (useWfuncs)
// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/base/getfileattributes.asp
result = GetFileAttributesW(std.utf.toUTF16z(name));
else
result = GetFileAttributesA(toMBSz(name));
return result != 0xFFFFFFFF;
}
/***************************************************
* Get file name[] attributes.
* Throws: $(D FileException) on error.
*/
uint getAttributes(string name)
{
uint result;
if (useWfuncs)
result = GetFileAttributesW(std.utf.toUTF16z(name));
else
result = GetFileAttributesA(toMBSz(name));
if (result == 0xFFFFFFFF)
{
throw new FileException(name, GetLastError());
}
return result;
}
/****************************************************
* Is name[] a file?
* Throws: $(D FileException) if name[] doesn't exist.
*/
bool isfile(in string name)
{
return (getAttributes(name) & FILE_ATTRIBUTE_DIRECTORY) == 0;
}
/****************************************************
* Is name[] a directory?
* Throws: $(D FileException) if name[] doesn't exist.
*/
bool isdir(in string name)
{
return (getAttributes(name) & FILE_ATTRIBUTE_DIRECTORY) != 0;
}
/****************************************************
* Change directory to pathname[].
* Throws: $(D FileException) on error.
*/
void chdir(in string pathname)
{ BOOL result;
if (useWfuncs)
result = SetCurrentDirectoryW(std.utf.toUTF16z(pathname));
else
result = SetCurrentDirectoryA(toMBSz(pathname));
if (!result)
{
throw new FileException(pathname, GetLastError());
}
}
/****************************************************
* Make directory pathname[].
* Throws: $(D FileException) on error.
*/
void mkdir(in string pathname)
{ BOOL result = void;
if (useWfuncs)
result = CreateDirectoryW(std.utf.toUTF16z(pathname), null);
else
result = CreateDirectoryA(toMBSz(pathname), null);
if (!result)
{
throw new FileException(pathname, GetLastError());
}
}
/****************************************************
* Make directory and all parent directories as needed.
*/
void mkdirRecurse(string pathname)
{
invariant left = dirname(pathname);
exists(left) || mkdirRecurse(left);
mkdir(pathname);
}
/****************************************************
* Remove directory pathname[].
* Throws: $(D FileException) on error.
*/
void rmdir(in string pathname)
{ BOOL result = void;
if (useWfuncs)
result = RemoveDirectoryW(std.utf.toUTF16z(pathname));
else
result = RemoveDirectoryA(toMBSz(pathname));
if (!result)
{
throw new FileException(pathname, GetLastError());
}
}
/****************************************************
* Remove directory and all of its content and subdirectories,
* recursively.
*/
void rmdirRecurse(string pathname)
{
// all children, recursively depth-first
foreach (DirEntry e; dirEntries(pathname, SpanMode.depth))
{
e.isdir ? rmdir(e.name) : remove(e.name);
}
// the dir itself
rmdir(pathname);
}
unittest
{
auto d = r"c:\deleteme\a\b\c\d\e\f\g";
mkdirRecurse(d);
rmdirRecurse(r"c:\deleteme");
enforce(!exists(r"c:\deleteme"));
}
/****************************************************
* Get current directory.
* Throws: $(D FileException) on error.
*/
string getcwd()
{
if (useWfuncs)
{
wchar c;
auto len = GetCurrentDirectoryW(0, &c);
if (!len)
goto Lerr;
auto dir = new wchar[len];
len = GetCurrentDirectoryW(len, dir.ptr);
if (!len)
goto Lerr;
return std.utf.toUTF8(dir[0 .. len]); // leave off terminating 0
}
else
{
char c;
auto len = GetCurrentDirectoryA(0, &c);
if (!len)
goto Lerr;
auto dir = new char[len];
len = GetCurrentDirectoryA(len, dir.ptr);
if (!len)
goto Lerr;
return cast(string)dir[0 .. len]; // leave off terminating 0
}
Lerr:
throw new FileException("getcwd", GetLastError());
}
/***************************************************
* Directory Entry
*/
struct DirEntry
{
string name; /// file or directory name
ulong size = ~0UL; /// size of file in bytes
d_time creationTime = d_time_nan; /// time of file creation
d_time lastAccessTime = d_time_nan; /// time file was last accessed
d_time lastWriteTime = d_time_nan; /// time file was last written to
uint attributes; // Windows file attributes OR'd together
void init(string path, in WIN32_FIND_DATA *fd)
{
wchar[] wbuf;
size_t clength;
size_t wlength;
size_t n;
clength = std.c.string.strlen(fd.cFileName.ptr);
// Convert cFileName[] to unicode
wlength = MultiByteToWideChar(0,0,fd.cFileName.ptr,clength,null,0);
if (wlength > wbuf.length)
wbuf.length = wlength;
n = MultiByteToWideChar(0,0,fd.cFileName.ptr,clength,cast(wchar*)wbuf,wlength);
assert(n == wlength);
// toUTF8() returns a new buffer
name = std.path.join(path, std.utf.toUTF8(wbuf[0 .. wlength]));
size = (cast(ulong)fd.nFileSizeHigh << 32) | fd.nFileSizeLow;
creationTime = std.date.FILETIME2d_time(&fd.ftCreationTime);
lastAccessTime = std.date.FILETIME2d_time(&fd.ftLastAccessTime);
lastWriteTime = std.date.FILETIME2d_time(&fd.ftLastWriteTime);
attributes = fd.dwFileAttributes;
}
void init(string path, in WIN32_FIND_DATAW *fd)
{
size_t clength = std.string.wcslen(fd.cFileName.ptr);
name = std.path.join(path, std.utf.toUTF8(fd.cFileName[0 .. clength]));
size = (cast(ulong)fd.nFileSizeHigh << 32) | fd.nFileSizeLow;
creationTime = std.date.FILETIME2d_time(&fd.ftCreationTime);
lastAccessTime = std.date.FILETIME2d_time(&fd.ftLastAccessTime);
lastWriteTime = std.date.FILETIME2d_time(&fd.ftLastWriteTime);
attributes = fd.dwFileAttributes;
}
/****
* Return !=0 if DirEntry is a directory.
*/
uint isdir()
{
return attributes & FILE_ATTRIBUTE_DIRECTORY;
}
/****
* Return !=0 if DirEntry is a file.
*/
uint isfile()
{
return !(attributes & FILE_ATTRIBUTE_DIRECTORY);
}
}
/***************************************************
* Return contents of directory pathname[].
* The names in the contents do not include the pathname.
* Throws: $(D FileException) on error
* Example:
* This program lists all the files and subdirectories in its
* path argument.
* ----
* import std.stdio;
* import std.file;
*
* void main(string[] args)
* {
* auto dirs = std.file.listdir(args[1]);
*
* foreach (d; dirs)
* writefln(d);
* }
* ----
*/
string[] listdir(string pathname)
{
string[] result;
bool listing(string filename)
{
result ~= filename;
return true; // continue
}
listdir(pathname, &listing);
return result;
}
/*****************************************************
* Return all the files in the directory and its subdirectories
* that match pattern or regular expression r.
* Params:
* pathname = Directory name
* pattern = String with wildcards, such as $(RED "*.d"). The supported
* wildcard strings are described under fnmatch() in
* $(LINK2 std_path.html, std.path).
* r = Regular expression, for more powerful _pattern matching.
* Example:
* This program lists all the files with a "d" extension in
* the path passed as the first argument.
* ----
* import std.stdio;
* import std.file;
*
* void main(string[] args)
* {
* auto d_source_files = std.file.listdir(args[1], "*.d");
*
* foreach (d; d_source_files)
* writefln(d);
* }
* ----
* A regular expression version that searches for all files with "d" or
* "obj" extensions:
* ----
* import std.stdio;
* import std.file;
* import std.regexp;
*
* void main(string[] args)
* {
* auto d_source_files = std.file.listdir(args[1], RegExp(r"\.(d|obj)$"));
*
* foreach (d; d_source_files)
* writefln(d);
* }
* ----
*/
string[] listdir(string pathname, string pattern)
{ string[] result;
bool callback(DirEntry* de)
{
if (de.isdir)
listdir(de.name, &callback);
else
{ if (std.path.fnmatch(de.name, pattern))
result ~= de.name;
}
return true; // continue
}
listdir(pathname, &callback);
return result;
}
/** Ditto */
string[] listdir(string pathname, RegExp r)
{ string[] result;
bool callback(DirEntry* de)
{
if (de.isdir)
listdir(de.name, &callback);
else
{ if (r.test(de.name))
result ~= de.name;
}
return true; // continue
}
listdir(pathname, &callback);
return result;
}
/******************************************************
* For each file and directory name in pathname[],
* pass it to the callback delegate.
*
* Note:
*
* This function is being phased out. New code should use $(D_PARAM
* dirEntries) (see below).
*
* Params:
* callback = Delegate that processes each
* filename in turn. Returns true to
* continue, false to stop.
* Example:
* This program lists all the files in its
* path argument, including the path.
* ----
* import std.stdio;
* import std.path;
* import std.file;
*
* void main(string[] args)
* {
* auto pathname = args[1];
* string[] result;
*
* bool listing(string filename)
* {
* result ~= std.path.join(pathname, filename);
* return true; // continue
* }
*
* listdir(pathname, &listing);
*
* foreach (name; result)
* writefln("%s", name);
* }
* ----
*/
void listdir(in string pathname, bool delegate(string filename) callback)
{
bool listing(DirEntry* de)
{
return callback(std.path.getBaseName(de.name));
}
listdir(pathname, &listing);
}
/******************************************************
* For each file and directory DirEntry in pathname[],
* pass it to the callback delegate.
*
* Note:
*
* This function is being phased out. New code should use $(D_PARAM
* dirEntries) (see below).
*
* Params:
* callback = Delegate that processes each
* DirEntry in turn. Returns true to
* continue, false to stop.
* Example:
* This program lists all the files in its
* path argument and all subdirectories thereof.
* ----
* import std.stdio;
* import std.file;
*
* void main(string[] args)
* {
* bool callback(DirEntry* de)
* {
* if (de.isdir)
* listdir(de.name, &callback);
* else
* writefln(de.name);
* return true;
* }
*
* listdir(args[1], &callback);
* }
* ----
*/
void listdir(in string pathname, bool delegate(DirEntry* de) callback)
{
string c;
HANDLE h;
DirEntry de;
c = std.path.join(pathname, "*.*");
if (useWfuncs)
{
WIN32_FIND_DATAW fileinfo;
h = FindFirstFileW(std.utf.toUTF16z(c), &fileinfo);
if (h != INVALID_HANDLE_VALUE)
{
try
{
do
{
// Skip "." and ".."
if (std.string.wcscmp(fileinfo.cFileName.ptr, ".") == 0 ||
std.string.wcscmp(fileinfo.cFileName.ptr, "..") == 0)
continue;
de.init(pathname, &fileinfo);
if (!callback(&de))
break;
} while (FindNextFileW(h,&fileinfo) != FALSE);
}
finally
{
FindClose(h);
}
}
}
else
{
WIN32_FIND_DATA fileinfo;
h = FindFirstFileA(toMBSz(c), &fileinfo);
if (h != INVALID_HANDLE_VALUE) // should we throw exception if invalid?
{
try
{
do
{
// Skip "." and ".."
if (std.c.string.strcmp(fileinfo.cFileName.ptr, ".") == 0 ||
std.c.string.strcmp(fileinfo.cFileName.ptr, "..") == 0)
continue;
de.init(pathname, &fileinfo);
if (!callback(&de))
break;
} while (FindNextFileA(h,&fileinfo) != FALSE);
}
finally
{
FindClose(h);
}
}
}
}
/******************************************
* Since Win 9x does not support the "W" API's, first convert
* to wchar, then convert to multibyte using the current code
* page.
* (Thanks to yaneurao for this)
* Deprecated: use std.windows.charset.toMBSz instead.
*/
const(char)* toMBSz(in string s)
{
return std.windows.charset.toMBSz(s);
}
/***************************************************
* Copy a file from[] to[].
*/
void copy(in string from, in string to)
{
invariant result = useWfuncs
? CopyFileW(std.utf.toUTF16z(from), std.utf.toUTF16z(to), false)
: CopyFileA(toMBSz(from), toMBSz(to), false);
if (!result)
throw new FileException(to, GetLastError);
}
}
/* =========================== Posix ======================= */
version (Posix)
{
private import std.date;
private import std.c.linux.linux;
/***********************************
*/
class FileException : Exception
{
uint errno; // operating system error code
this(string name)
{
this(name, "file I/O");
}
this(string name, string message)
{
super(name ~ ": " ~ message);
}
this(string name, uint errno)
{
char[1024] buf = void;
auto s = strerror_r(errno, buf.ptr, buf.length);
this(name, std.string.toString(s));
this.errno = errno;
}
}
private T cenforce(T)(T condition, lazy const(char)[] name)
{
if (!condition) throw new FileException(name.idup, errno);
return condition;
}
/********************************************
* Read a file.
* Returns:
* array of bytes read
*/
void[] read(string name)
{
invariant fd = std.c.linux.linux.open(toStringz(name), O_RDONLY);
cenforce(fd != -1, name);
scope(exit) std.c.linux.linux.close(fd);
struct_stat statbuf = void;
cenforce(std.c.linux.linux.fstat(fd, &statbuf) == 0, name);
void[] buf;
auto size = statbuf.st_size;
if (size == 0)
{ /* The size could be 0 if the file is a device or a procFS file,
* so we just have to try reading it.
*/
int readsize = 1024;
while (1)
{
buf = GC.realloc(buf.ptr, size + readsize, GC.BlkAttr.NO_SCAN)[0 .. cast(int)size + readsize];
enforce(buf, "Out of memory");
scope(failure) delete buf;
auto toread = readsize;
while (toread)
{
auto numread = std.c.linux.linux.read(fd, buf.ptr + size, toread);
cenforce(numread != -1, name);
size += numread;
if (numread == 0)
{ if (size == 0) // it really was 0 size
delete buf; // don't need the buffer
return buf[0 .. size]; // end of file
}
toread -= numread;
}
}
}
else
{
buf = GC.malloc(size, GC.BlkAttr.NO_SCAN)[0 .. size];
enforce(buf, "Out of memory");
scope(failure) delete buf;
cenforce(std.c.linux.linux.read(fd, buf.ptr, size) == size, name);
return buf[0 .. size];
}
}
unittest
{
version (linux)
{ // A file with "zero" length that doesn't have 0 length at all
char[] s = cast(char[])std.file.read("/proc/sys/kernel/osrelease");
assert(s.length > 0);
//writefln("'%s'", s);
}
}
/********************************************
* Read and validates (using $(XREF utf, validate)) a text file. $(D
* S) can be a type of array of characters of any width and constancy.
*
* Returns: array of characters read
*
* Throws: $(D FileException) on file error, $(D UtfException) on UTF
* decoding error.
*
*/
S readText(S = string)(in string name)
{
auto result = cast(S) read(name);
std.utf.validate(result);
return result;
}
// Implementation helper for write and append
private void writeImpl(in string name, in void[] buffer, in uint mode)
{
invariant fd = std.c.linux.linux.open(toStringz(name),
mode, 0660);
cenforce(fd != -1, name);
{
scope(failure) std.c.linux.linux.close(fd);
invariant size = buffer.length;
cenforce(std.c.linux.linux.write(fd, buffer.ptr, size) == size, name);
}
cenforce(std.c.linux.linux.close(fd) == 0, name);
}
/*********************************************
* Write a file.
*/
void write(in string name, in void[] buffer)
{
return writeImpl(name, buffer, O_CREAT | O_WRONLY | O_TRUNC);
}
/*********************************************
* Append to a file.
*/
void append(in string name, in void[] buffer)
{
return writeImpl(name, buffer, O_APPEND | O_WRONLY | O_CREAT);
}
/***************************************************
* Rename a file.
*/
void rename(in string from, in string to)
{
cenforce(std.c.stdio.rename(toStringz(from), toStringz(to)) == 0, to);
}
/***************************************************
* Delete a file.
*/
void remove(in string name)
{
cenforce(std.c.stdio.remove(toStringz(name)) == 0, name);
}
/***************************************************
* Get file size.
*/
ulong getSize(in string name)
{
struct_stat statbuf = void;
cenforce(std.c.linux.linux.stat(toStringz(name), &statbuf) == 0, name);
return statbuf.st_size;
}
unittest
{
scope(exit) system("rm -f /tmp/deleteme");
// create a file of size 1
assert(system("echo > /tmp/deleteme") == 0);
assert(getSize("/tmp/deleteme") == 1);
// create a file of size 3
assert(system("echo ab > /tmp/deleteme") == 0);
assert(getSize("/tmp/deleteme") == 3);
}
/***************************************************
* Get file attributes.
*/
uint getAttributes(in string name)
{
struct_stat statbuf = void;
cenforce(std.c.linux.linux.stat(toStringz(name), &statbuf) == 0, name);
return statbuf.st_mode;
}
/*************************
* Get creation/access/modified times of file $(D name).
* Throws: $(D FileException) on error.
*/
void getTimes(in string name, out d_time ftc, out d_time fta, out d_time ftm)
{
struct_stat statbuf = void;
cenforce(std.c.linux.linux.stat(toStringz(name), &statbuf) == 0, name);
version (linux)
{
ftc = cast(d_time) statbuf.st_ctime * std.date.TicksPerSecond;
fta = cast(d_time) statbuf.st_atime * std.date.TicksPerSecond;
ftm = cast(d_time) statbuf.st_mtime * std.date.TicksPerSecond;
}
else version (OSX)
{ // BUG: should add in tv_nsec field
ftc = cast(d_time)statbuf.st_ctimespec.tv_sec * std.date.TicksPerSecond;
fta = cast(d_time)statbuf.st_atimespec.tv_sec * std.date.TicksPerSecond;
ftm = cast(d_time)statbuf.st_mtimespec.tv_sec * std.date.TicksPerSecond;
}
else
{
static assert(0);
}
}
/*************************
* Set access/modified times of file $(D name).
* Throws: $(D FileException) on error.
*/
void setTimes(in string name, d_time fta, d_time ftm)
{
version (linux)
{
version (none) // does not compile
{
// utimbuf times = {
// cast(__time_t) (fta / std.date.TicksPerSecond),
// cast(__time_t) (ftm / std.date.TicksPerSecond) };
// enforce(utime(toStringz(name), &times) == 0);
timeval[2] t = void;
t[0].tv_sec = fta / std.date.TicksPerSecond;
t[0].tv_usec = cast(long) ((cast(double) fta / std.date.TicksPerSecond)
* 1_000_000) % 1_000_000;
t[1].tv_sec = ftm / std.date.TicksPerSecond;
t[1].tv_usec = cast(long) ((cast(double) ftm / std.date.TicksPerSecond)
* 1_000_000) % 1_000_000;
enforce(utime(toStringz(name), t.ptr) == 0);
}
else
{
assert(0);
}
}
else
{
if (true) enforce(false, "Not implemented");
}
}
unittest
{
system("touch deleteme") == 0 || assert(false);
scope(exit) remove("deleteme");
d_time ftc1, fta1, ftm1;
getTimes("deleteme", ftc1, fta1, ftm1);
setTimes("deleteme", fta1 + 1000, ftm1 + 1000);
d_time ftc2, fta2, ftm2;
getTimes("deleteme", ftc2, fta2, ftm2);
assert(fta1 + 1000 == fta2);
assert(ftm1 + 1000 == ftm2);
}
/**
Returns the time of the last modification of file $(D name). If the
file does not exist, throws a $(D FileException).
*/
d_time lastModified(in string name)
{
struct_stat statbuf = void;
cenforce(std.c.linux.linux.stat(toStringz(name), &statbuf) == 0, name);
version (linux)
return cast(d_time) statbuf.st_mtime * std.date.TicksPerSecond;
else version (OSX)
return cast(d_time)statbuf.st_mtimespec.tv_sec * std.date.TicksPerSecond;
else
static assert(0);
}
/**
Returns the time of the last modification of file $(D name). If the
file does not exist, returns $(D returnIfMissing).
A frequent usage pattern occurs in build automation tools such as
$(WEB www.gnu.org/software/make, make) or $(WEB
en.wikipedia.org/wiki/Apache_Ant, ant). To check whether file $(D
target) must be rebuilt from file $(D source) (i.e., $(D target) is
older than $(D source) or does not exist), use the comparison below.
----------------------------
if (lastModified(source) >= lastModified(target, d_time.min))
{
... must (re)build ...
}
else
{
... target's up-to-date ...
}
----------------------------
The code above throws a $(D FileException) if $(D source) does not
exist (as it should). On the other hand, the $(D d_time.min) default
makes a non-existing $(D target) seem infinitely old so the test
correctly prompts building it.
*/
d_time lastModified(string name, d_time returnIfMissing)
{
struct_stat statbuf = void;
version (linux)
{
return std.c.linux.linux.stat(toStringz(name), &statbuf) != 0
? returnIfMissing
: cast(d_time) statbuf.st_mtime * std.date.TicksPerSecond;
}
else version (OSX)
{
return std.c.linux.linux.stat(toStringz(name), &statbuf) != 0
? returnIfMissing
: cast(d_time)statbuf.st_mtimespec.tv_sec * std.date.TicksPerSecond;
}
else
{
assert(0);
}
}
unittest
{
system("touch deleteme") == 0 || assert(false);
scope(exit) remove("deleteme");
assert(lastModified("deleteme") >
lastModified("this file does not exist", d_time.min));
assert(lastModified("deleteme") > lastModified(__FILE__));
}
/****************************************************
* Does file/directory exist?
*/
bool exists(in char[] name)
{
return access(toStringz(name), 0) == 0;
}
unittest
{
assert(exists("."));
assert(!exists("this file does not exist"));
system("touch deleteme") == 0 || assert(false);
scope(exit) remove("deleteme");
assert(exists("deleteme"));
}
/****************************************************
* Is name a file?
*/
bool isfile(in string name)
{
return (getAttributes(name) & S_IFMT) == S_IFREG; // regular file
}
/****************************************************
* Is name a directory?
*/
bool isdir(in string name)
{
return (getAttributes(name) & S_IFMT) == S_IFDIR;
}
/****************************************************
* Change directory.
*/
void chdir(string pathname)
{
cenforce(std.c.linux.linux.chdir(toStringz(pathname)) == 0, pathname);
}
/****************************************************
* Make directory.
*/
void mkdir(in char[] pathname)
{
cenforce(std.c.linux.linux.mkdir(toStringz(pathname), 0777) == 0, pathname);
}
/****************************************************
* Make directory and all parent directories as needed.
*/
void mkdirRecurse(in char[] pathname)
{
auto left = dirname(pathname);
exists(left) || mkdirRecurse(left);
mkdir(pathname);
}
/****************************************************
* Remove directory.
*/
void rmdir(string pathname)
{
cenforce(std.c.linux.linux.rmdir(toStringz(pathname)) == 0, pathname);
}
/****************************************************
Remove directory and all of its content and subdirectories,
recursively.
*/
void rmdirRecurse(string pathname)
{
// all children, recursively depth-first
foreach (DirEntry e; dirEntries(pathname, SpanMode.depth))
{
e.isdir ? rmdir(e.name) : remove(e.name);
}
// the dir itself
rmdir(pathname);
}
unittest
{
auto d = "/tmp/deleteme/a/b/c/d/e/f/g";
enforce(collectException(mkdir(d)));
mkdirRecurse(d);
rmdirRecurse("/tmp/deleteme");
enforce(!exists("/tmp/deleteme"));
}
/****************************************************
* Get current directory.
*/
string getcwd()
{
auto p = cenforce(std.c.linux.linux.getcwd(null, 0),
"cannot get cwd");
scope(exit) std.c.stdlib.free(p);
return p[0 .. std.c.string.strlen(p)].idup;
}
/***************************************************
* Directory Entry
*/
struct DirEntry
{
string name; /// file or directory name
ulong _size = ~0UL; // size of file in bytes
d_time _creationTime = d_time_nan; // time of file creation
d_time _lastAccessTime = d_time_nan; // time file was last accessed
d_time _lastWriteTime = d_time_nan; // time file was last written to
ubyte d_type;
struct_stat statbuf;
bool didstat; // done lazy evaluation of stat()
void init(string path, dirent *fd)
{
invariant len = std.c.string.strlen(fd.d_name.ptr);
name = std.path.join(path, fd.d_name[0 .. len].idup);
d_type = fd.d_type;
didstat = false;
}
bool isdir()
{
return (d_type & DT_DIR) != 0;
}
bool isfile()
{
return (d_type & DT_REG) != 0;
}
ulong size()
{
ensureStatDone;
return _size;
}
d_time creationTime()
{
ensureStatDone;
return _creationTime;
}
d_time lastAccessTime()
{
ensureStatDone;
return _lastAccessTime;
}
d_time lastWriteTime()
{
ensureStatDone;
return _lastWriteTime;
}
/* This is to support lazy evaluation, because doing stat's is
* expensive and not always needed.
*/
void ensureStatDone()
{
if (didstat) return;
enforce(std.c.linux.linux.stat(toStringz(name), &statbuf) == 0,
"Failed to stat file `"~name~"'");
_size = cast(ulong)statbuf.st_size;
version (linux)
{
_creationTime = cast(d_time)statbuf.st_ctime * std.date.TicksPerSecond;
_lastAccessTime = cast(d_time)statbuf.st_atime * std.date.TicksPerSecond;
_lastWriteTime = cast(d_time)statbuf.st_mtime * std.date.TicksPerSecond;
}
else version (OSX)
{
_creationTime = cast(d_time)statbuf.st_ctimespec.tv_sec * std.date.TicksPerSecond;
_lastAccessTime = cast(d_time)statbuf.st_atimespec.tv_sec * std.date.TicksPerSecond;
_lastWriteTime = cast(d_time)statbuf.st_mtimespec.tv_sec * std.date.TicksPerSecond;
}
else
{
static assert(0);
}
didstat = true;
}
}
/***************************************************
* Return contents of directory.
*/
string[] listdir(string pathname)
{
string[] result;
bool listing(string filename)
{
result ~= filename;
return true; // continue
}
listdir(pathname, &listing);
return result;
}
string[] listdir(string pathname, string pattern)
{ string[] result;
bool callback(DirEntry* de)
{
if (de.isdir)
listdir(de.name, &callback);
else
{ if (std.path.fnmatch(de.name, pattern))
result ~= de.name;
}
return true; // continue
}
listdir(pathname, &callback);
return result;
}
string[] listdir(string pathname, RegExp r)
{ string[] result;
bool callback(DirEntry* de)
{
if (de.isdir)
listdir(de.name, &callback);
else
{ if (r.test(de.name))
result ~= de.name;
}
return true; // continue
}
listdir(pathname, &callback);
return result;
}
void listdir(string pathname, bool delegate(string filename) callback)
{
bool listing(DirEntry* de)
{
return callback(std.path.getBaseName(de.name));
}
listdir(pathname, &listing);
}
void listdir(string pathname, bool delegate(DirEntry* de) callback)
{
auto h = cenforce(opendir(toStringz(pathname)), pathname);
scope(exit) closedir(h);
DirEntry de;
for (dirent* fdata; (fdata = readdir(h)) != null; )
{
// Skip "." and ".."
if (!std.c.string.strcmp(fdata.d_name.ptr, ".") ||
!std.c.string.strcmp(fdata.d_name.ptr, ".."))
continue;
de.init(pathname, fdata);
if (!callback(&de))
break;
}
}
/***************************************************
* Copy a file. File timestamps are preserved.
*/
void copy(in string from, in string to)
{
version (all)
{
invariant fd = std.c.linux.linux.open(toStringz(from), O_RDONLY);
cenforce(fd != -1, from);
scope(exit) std.c.linux.linux.close(fd);
struct_stat statbuf = void;
cenforce(std.c.linux.linux.fstat(fd, &statbuf) == 0, from);
auto toz = toStringz(to);
invariant fdw = std.c.linux.linux.open(toz,
O_CREAT | O_WRONLY | O_TRUNC, 0660);
cenforce(fdw != -1, from);
scope(failure) std.c.stdio.remove(toz);
{
scope(failure) std.c.linux.linux.close(fdw);
auto BUFSIZ = 4096u * 16;
auto buf = std.c.stdlib.malloc(BUFSIZ);
if (!buf)
{
BUFSIZ = 4096;
buf = enforce(std.c.stdlib.malloc(BUFSIZ), "Out of memory");
}
scope(exit) std.c.stdlib.free(buf);
for (size_t size = statbuf.st_size; size; )
{
invariant toxfer = (size > BUFSIZ) ? BUFSIZ : size;
cenforce(std.c.linux.linux.read(fd, buf, toxfer) == toxfer
&& std.c.linux.linux.write(fdw, buf, toxfer) == toxfer,
from);
assert(size >= toxfer);
size -= toxfer;
}
}
cenforce(std.c.linux.linux.close(fdw) != -1, from);
utimbuf utim = void;
version (linux)
{
utim.actime = cast(__time_t)statbuf.st_atime;
utim.modtime = cast(__time_t)statbuf.st_mtime;
}
else version (OSX)
{
utim.actime = cast(__time_t)statbuf.st_atimespec.tv_sec;
utim.modtime = cast(__time_t)statbuf.st_mtimespec.tv_sec;
}
else
{
static assert(0);
}
cenforce(utime(toz, &utim) != -1, from);
}
else
{
void[] buffer;
buffer = read(from);
write(to, buffer);
delete buffer;
}
}
}
unittest
{
//printf("std.file.unittest\n");
void[] buf;
buf = new void[10];
(cast(byte[])buf)[] = 3;
write("unittest_write.tmp", buf);
void buf2[] = read("unittest_write.tmp");
assert(buf == buf2);
copy("unittest_write.tmp", "unittest_write2.tmp");
buf2 = read("unittest_write2.tmp");
assert(buf == buf2);
remove("unittest_write.tmp");
if (exists("unittest_write.tmp"))
assert(0);
remove("unittest_write2.tmp");
if (exists("unittest_write2.tmp"))
assert(0);
}
unittest
{
listdir (".", delegate bool (DirEntry * de)
{
auto s = std.string.format("%s : c %s, w %s, a %s", de.name,
toUTCString (de.creationTime),
toUTCString (de.lastWriteTime),
toUTCString (de.lastAccessTime));
return true;
}
);
}
/**
* Dictates directory spanning policy for $(D_PARAM dirEntries) (see below).
*/
enum SpanMode
{
/** Only spans one directory. */
shallow,
/** Spans the directory depth-first, i.e. the content of any
subdirectory is spanned before that subdirectory itself. Useful
e.g. when recursively deleting files. */
depth,
/** Spans the directory breadth-first, i.e. the content of any
subdirectory is spanned right after that subdirectory itself. */
breadth,
}
struct DirIterator
{
string pathname;
SpanMode mode;
private int doIt(D)(D dg, DirEntry * de)
{
alias ParameterTypeTuple!(D) Parms;
static if (is(Parms[0] : string))
{
return dg(de.name);
}
else static if (is(Parms[0] : DirEntry))
{
return dg(*de);
}
else
{
static assert(false, "Dunno how to enumerate directory entries"
" against type " ~ Parms[0].stringof);
}
}
int opApply(D)(D dg)
{
int result = 0;
string[] worklist = [ pathname ]; // used only in breadth-first traversal
bool callback(DirEntry* de)
{
switch (mode)
{
case SpanMode.shallow:
result = doIt(dg, de);
break;
case SpanMode.breadth:
result = doIt(dg, de);
if (!result && de.isdir)
{
worklist ~= de.name;
}
break;
default:
assert(mode == SpanMode.depth);
if (de.isdir)
{
listdir(de.name, &callback);
}
if (!result)
{
result = doIt(dg, de);
}
break;
}
return result == 0;
}
// consume the worklist
while (worklist.length)
{
auto listThis = worklist[$ - 1];
worklist.length = worklist.length - 1;
listdir(listThis, &callback);
}
return result;
}
}
/**
* Iterates a directory using foreach. The iteration variable can be
* of type $(D_PARAM string) if only the name is needed, or $(D_PARAM
* DirEntry) if additional details are needed. The span mode dictates
* the how the directory is traversed. The name of the directory entry
* includes the $(D_PARAM path) prefix.
*
* Example:
*
* ----
// Iterate a directory in depth
foreach (string name; dirEntries("destroy/me", SpanMode.depth))
{
remove(name);
}
// Iterate a 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);
}
* ----
*/
DirIterator dirEntries(string path, SpanMode mode)
{
DirIterator result;
result.pathname = path;
result.mode = mode;
return result;
}
unittest
{
version (linux)
{
assert(system("mkdir --parents dmd-testing") == 0);
scope(exit) system("rm -rf dmd-testing");
assert(system("mkdir --parents dmd-testing/somedir") == 0);
assert(system("touch dmd-testing/somefile") == 0);
assert(system("touch dmd-testing/somedir/somedeepfile") == 0);
foreach (string name; dirEntries("dmd-testing", SpanMode.shallow))
{
}
foreach (string name; dirEntries("dmd-testing", SpanMode.depth))
{
//writeln(name);
}
foreach (string name; dirEntries("dmd-testing", SpanMode.breadth))
{
//writeln(name);
}
foreach (DirEntry e; dirEntries("dmd-testing", SpanMode.breadth))
{
//writeln(e.name);
}
}
}