// Written in the D programming language.

/**
Utilities for manipulating files and scanning directories.

Macros:
 WIKI = Phobos/StdFile

Copyright: Copyright Digital Mars 2007 - 2009.
License:   <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>.
Authors:   $(WEB digitalmars.com, Walter Bright),
           $(WEB erdani.org, Andrei Alexandrescu)

         Copyright Digital Mars 2007 - 2009.
Distributed under the Boost Software License, Version 1.0.
   (See accompanying file LICENSE_1_0.txt or copy at
         http://www.boost.org/LICENSE_1_0.txt)
*/
module std.file;

import core.memory;
import core.stdc.stdio, core.stdc.stdlib, core.stdc.string,
    core.stdc.errno, std.algorithm, std.array, 
    std.contracts, std.conv, std.date, std.format, std.path, std.process,
    std.range, std.regexp, std.stdio, std.string, std.traits, std.typecons,
    std.typetuple, std.utf;
version (Win32)
{
    import core.sys.windows.windows, std.windows.charset,
        std.windows.syserror, std.__fileinit;
/*
 * 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)
 */
    version(Windows) alias std.windows.charset.toMBSz toMBSz;
    shared bool useWfuncs = true;        // initialized in std.__fileinit
}
version (Posix)
{
    import core.sys.posix.dirent, core.sys.posix.fcntl, core.sys.posix.sys.stat,
        core.sys.posix.sys.time, core.sys.posix.unistd, core.sys.posix.utime;
}

// @@@@ TEMPORARY - THIS SHOULD BE IN THE CORE @@@
// {{{
version (Posix)
{
    version (OSX)
    {
        struct struct_stat64        // distinguish it from the stat() function
        {
            uint st_dev;        /// device
            ushort st_mode;
            ushort st_nlink;        /// link count
            ulong st_ino;        /// file serial number
            uint st_uid;        /// user ID of file's owner
            uint st_gid;        /// user ID of group's owner
            uint st_rdev;        /// if device then device number

            int st_atime;
            uint st_atimensec;
            int st_mtime;
            uint st_mtimensec;
            int st_ctime;
            uint st_ctimensec;
            int st_birthtime;
            uint st_birthtimensec;

            ulong st_size;
            long st_blocks;        /// number of allocated 512 byte blocks
            int st_blksize;        /// optimal I/O block size

            ulong st_ino64;
            uint st_flags;
            uint st_gen;
            int st_lspare; /* RESERVED: DO NOT USE! */
            long st_qspare[2]; /* RESERVED: DO NOT USE! */
        }
    }
    else
    {
        struct struct_stat64        // distinguish it from the stat() function
        {
            ulong st_dev;        /// device
            uint __pad1;
            uint st_ino;        /// file serial number
            uint st_mode;        /// file mode
            uint st_nlink;        /// link count
            uint st_uid;        /// user ID of file's owner
            uint st_gid;        /// user ID of group's owner
            ulong st_rdev;        /// if device then device number
            uint __pad2;
            align(4) ulong st_size;
            int st_blksize;        /// optimal I/O block size
            ulong st_blocks;        /// number of allocated 512 byte blocks
            int st_atime;
            uint st_atimensec;
            int st_mtime;
            uint st_mtimensec;
            int st_ctime;
            uint st_ctimensec;

            ulong st_ino64;
        }
    }
    
    extern(C) int fstat64(int, struct_stat64*);
    extern(C) int stat64(in char*, struct_stat64*);
}
// }}}

/***********************************
 * Exception thrown for file I/O errors.
 */

class FileException : Exception
{
/**
OS error code.
 */ 
    immutable uint errno;
    
/**
Constructor taking the name of the file where error happened and the
error number ($(LUCKY GetLastError) in Windows, $(D getErrno) in
Posix).
 */ 
    this(in char[] name, in char[] message)
    {
        super(text(name, ": ", message));
        errno = 0;
    }

/**
Constructor taking the name of the file where error happened and the
error number ($(LUCKY GetLastError) in Windows, $(D getErrno) in
Posix).
 */ 
    version(Windows) this(string name, uint errno = GetLastError)
    {
        this(name, sysErrorString(errno));
        this.errno = errno;
    }

    version(Posix) this(in char[] name, uint errno = .getErrno)
    {
        auto s = strerror(errno);
        this(name, to!string(s));
        this.errno = errno;
    }
}

private T cenforce(T, string file = __FILE__, uint line = __LINE__)
(T condition, lazy const(char)[] name)
{
    if (!condition)
    {
        throw new FileException(
            text("In ", file, "(", line, "), data file ", name), .getErrno);
    }
    return condition;
}

/* **********************************
 * Basic File operations.
 */

/********************************************
Read entire contents of file $(D name).

Returns: Untyped array of bytes _read.

Throws: $(D FileException) on error.
 */

version(Windows) void[] read(in char[] name, size_t upTo = size_t.max)
{
    alias TypeTuple!(GENERIC_READ,
            FILE_SHARE_READ, (SECURITY_ATTRIBUTES*).init, OPEN_EXISTING,
            FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
            HANDLE.init)
        defaults;
    auto h = useWfuncs
        ? CreateFileW(std.utf.toUTF16z(name), defaults)
        : CreateFileA(toMBSz(name), defaults);
    
    cenforce(h != INVALID_HANDLE_VALUE, name);
    scope(exit) cenforce(CloseHandle(h), name);
    auto size = GetFileSize(h, null);
    cenforce(size != INVALID_FILE_SIZE, name);
    size = min(upTo, size);
    auto buf = GC.malloc(size, GC.BlkAttr.NO_SCAN)[0 .. size];
    scope(failure) delete buf;
    
    DWORD numread = void;
    cenforce(ReadFile(h,buf.ptr, size, &numread, null) == 1
            && numread == size, name);
    return buf[0 .. size];
}

version(Posix) void[] read(in char[] name, in size_t upTo = size_t.max)
{
    // A few internal configuration parameters {
    enum size_t
        minInitialAlloc = 1024 * 4,
        maxInitialAlloc = size_t.max / 2,
        sizeIncrement = 1024 * 16,
        maxSlackMemoryAllowed = 1024;
    // }
    
    immutable fd = core.sys.posix.fcntl.open(toStringz(name),
            core.sys.posix.fcntl.O_RDONLY);
    cenforce(fd != -1, name);
    scope(exit) core.sys.posix.unistd.close(fd);
    
    struct_stat64 statbuf = void;
    cenforce(fstat64(fd, &statbuf) == 0, name);
    //cenforce(core.sys.posix.sys.stat.fstat(fd, &statbuf) == 0, name);
    
    immutable initialAlloc = statbuf.st_size
        ? min(statbuf.st_size + 1, maxInitialAlloc)
        : minInitialAlloc;
    auto result = GC.malloc(initialAlloc, GC.BlkAttr.NO_SCAN)
        [0 .. initialAlloc];
    scope(failure) delete result;
    size_t size = 0;
    
    for (;;)
    {
        immutable actual = core.sys.posix.unistd.read(fd, result.ptr + size,
                min(result.length, upTo) - size);
        cenforce(actual != -1, name);
        if (actual == 0) break;
        size += actual;
        if (size < result.length) continue;
        immutable newAlloc = size + sizeIncrement;
        result = GC.realloc(result.ptr, newAlloc, GC.BlkAttr.NO_SCAN)
            [0 .. newAlloc];
    }
    
    return result.length - size >= maxSlackMemoryAllowed
        ? GC.realloc(result.ptr, size, GC.BlkAttr.NO_SCAN)[0 .. size]
        : result[0 .. size];
}

unittest
{
    write("std.file.deleteme", "1234");
    assert(read("std.file.deleteme", 2) == "12");
}

version (linux) unittest
{    
    // A file with "zero" length that doesn't have 0 length at all
    auto s = std.file.readText("/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. No
width conversion is performed; if the width of the characters in file
$(D name) is different from the width of elements of $(D S),
validation will fail.

Returns: Array of characters read.

Throws: $(D FileException) on file error, $(D UtfException) on UTF
decoding error.

Example:

----
enforce(system("echo abc>deleteme") == 0);
scope(exit) remove("deleteme");
enforce(chomp(readText("deleteme")) == "abc");
----
 */

S readText(S = string)(in char[] name)
{
    auto result = cast(S) read(name);
    std.utf.validate(result);
    return result;
}

unittest
{
    enforce(std.process.system("echo abc>deleteme") == 0);
    scope(exit) remove("deleteme");
    enforce(chomp(readText("deleteme")) == "abc");
}

/*********************************************
 * Write $(D buffer) to file $(D name).
 * Throws: $(D FileException) on error.
 */

version(Windows) void write(in char[] name, const void[] buffer)
{
    alias TypeTuple!(GENERIC_WRITE, 0, null, CREATE_ALWAYS,
            FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
            HANDLE.init)
        defaults;
    auto h = useWfuncs
        ? CreateFileW(std.utf.toUTF16z(name), defaults)
        : CreateFileA(toMBSz(name), defaults);
   
    cenforce(h != INVALID_HANDLE_VALUE, name);
    scope(exit) cenforce(CloseHandle(h), name);
    DWORD numwritten;
    cenforce(WriteFile(h, buffer.ptr, buffer.length, &numwritten, null) == 1
            && buffer.length == numwritten,
            name);
}

version(Posix) void write(in char[] name, in void[] buffer)
{
    return writeImpl(name, buffer, O_CREAT | O_WRONLY | O_TRUNC);
}

/*********************************************
 * Append $(D buffer) to file $(D name).
 * Throws: $(D FileException) on error.
 */

version(Windows) void append(in char[] name, in void[] buffer)
{
    alias TypeTuple!(GENERIC_WRITE,0,null,OPEN_ALWAYS,
            FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,HANDLE.init)
        defaults;
    
    auto h = useWfuncs
        ? CreateFileW(std.utf.toUTF16z(name), defaults)
        : CreateFileA(toMBSz(name), defaults);
    
    cenforce(h != INVALID_HANDLE_VALUE, name);
    scope(exit) cenforce(CloseHandle(h), name);
    DWORD numwritten;
    cenforce(SetFilePointer(h, 0, null, FILE_END) != INVALID_SET_FILE_POINTER
            && WriteFile(h,buffer.ptr,buffer.length,&numwritten,null) == 1
            && buffer.length == numwritten,
            name);
}

version(Posix) void append(in char[] name, in void[] buffer)
{
    return writeImpl(name, buffer, O_APPEND | O_WRONLY | O_CREAT);
}

// Posix implementation helper for write and append

version(Posix) private void writeImpl(in char[] name,
        in void[] buffer, in uint mode)
{
    invariant fd = core.sys.posix.fcntl.open(toStringz(name),
            mode, 0660);
    cenforce(fd != -1, name);
    {
        scope(failure) core.sys.posix.unistd.close(fd);
        invariant size = buffer.length;
        cenforce(
            core.sys.posix.unistd.write(fd, buffer.ptr, size) == size,
            name);
    }
    cenforce(core.sys.posix.unistd.close(fd) == 0, name);
}

/***************************************************
 * Rename file $(D from) to $(D to).
 * Throws: $(D FileException) on error.
 */

version(Windows) void rename(in char[] from, in char[] to)
{
    enforce(useWfuncs
            ? MoveFileW(std.utf.toUTF16z(from), std.utf.toUTF16z(to))
            : MoveFileA(toMBSz(from), toMBSz(to)),
            new FileException(
                text("Attempting to rename file ", from, " to ",
                        to)));
}

version(Posix) void rename(in char[] from, in char[] to)
{
    cenforce(std.c.stdio.rename(toStringz(from), toStringz(to)) == 0, to);
}

/***************************************************
Delete file $(D name).
Throws: $(D FileException) on error.
 */

version(Windows) void remove(in char[] name)
{
    cenforce(useWfuncs
            ? DeleteFileW(std.utf.toUTF16z(name))
            : DeleteFileA(toMBSz(name)),
            name);
}

version(Posix) void remove(in char[] name)
{
    cenforce(std.c.stdio.remove(toStringz(name)) == 0, name);
}

/***************************************************
Get size of file $(D name).

Throws: $(D FileException) on error (e.g., file not found).
 */

version(Windows) ulong getSize(in char[] name)
{
    HANDLE findhndl = void;
    uint resulth = void;
    uint resultl = void;
    
    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;
    }
    
    cenforce(findhndl != cast(HANDLE)-1 && FindClose(findhndl), name);
    return (cast(ulong) resulth << 32) + resultl;
}

version(Posix) ulong getSize(in char[] name)
{
    struct_stat64 statbuf = void;
    cenforce(stat64(toStringz(name), &statbuf) == 0, name);
    return statbuf.st_size;
}
    
unittest
{
    version(Windows)
        auto deleteme = std.path.join(std.process.getenv("TEMP"), "deleteme");
    else
        auto deleteme = "/tmp/deleteme";
    scope(exit) if (exists(deleteme)) remove(deleteme);
    // create a file of size 1
    write(deleteme, "a");
    assert(getSize(deleteme) == 1);
    // create a file of size 3
    write(deleteme, "abc");
    assert(getSize(deleteme) == 3);
}
    
/*************************
 * Get creation/access/modified times of file $(D name).
 * Throws: $(D FileException) on error.
 */

version(Windows) void getTimes(in char[] name,
        out d_time ftc, out d_time fta, out d_time ftm)
{
    HANDLE findhndl = void;
    
    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.idup);
    }
    FindClose(findhndl);
}

version(Posix) void getTimes(in char[] name,
        out d_time ftc, out d_time fta, out d_time ftm)
{
    struct_stat64 statbuf = void;
    cenforce(stat64(toStringz(name), &statbuf) == 0, name);
    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;
}

/*
Get creation/access/modified times of file $(D name) as a tuple.

Throws: $(D FileException) on error.
 */

// Tuple!(d_time, "ftc", d_time, "fta", d_time, "ftm") getTimes(in char[] name)
// {
//     typeof(return) result = void;
//     getTimes(name, result.ftc, result.fta, result.ftm);
//     return result;
// }

// unittest
// {
//     auto t = getTimes(".").ftm;
// }

/**
   Returns the time of the last modification of file $(D name). If the
   file does not exist, throws a $(D FileException).
*/

version(Windows) d_time lastModified(in char[] name)
{
    d_time dummy = void, ftm = void;
    getTimes(name, dummy, dummy, ftm);
    return ftm;
}

version(Posix) d_time lastModified(in char[] name)
{
    struct_stat64 statbuf = void;
    cenforce(stat64(toStringz(name), &statbuf) == 0, name);
    return cast(d_time) statbuf.st_mtime * std.date.ticksPerSecond;
}
    
/**
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 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. The code 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.

----
if (lastModified(source) >= lastModified(target, d_time.min))
{
    // must (re)build
}
else
{
    // target is up-to-date
}
----
*/

version(Windows) d_time lastModified(in char[] name, d_time returnIfMissing)
{
    if (!exists(name)) return returnIfMissing;
    d_time dummy = void, ftm = void;
    getTimes(name, dummy, dummy, ftm);
    return ftm;
}

version(Posix) d_time lastModified(in char[] name, d_time returnIfMissing)
{
    struct_stat64 statbuf = void;
    return stat64(toStringz(name), &statbuf) != 0
        ? returnIfMissing
        : cast(d_time) statbuf.st_mtime * std.date.ticksPerSecond;
}

unittest
{
    std.process.system("echo a>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 (or directory) $(D name) exist?
 */

version(Windows) bool exists(in char[] name)
{
    auto result = useWfuncs
// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/
// fileio/base/getfileattributes.asp
        ? GetFileAttributesW(std.utf.toUTF16z(name))
        : GetFileAttributesA(toMBSz(name));
    return result != 0xFFFFFFFF;
}

version(Posix) bool exists(in char[] name)
{
    return access(toStringz(name), 0) == 0;
}
    
unittest
{
    assert(exists("."));
    assert(!exists("this file does not exist"));
    std.process.system("echo a >deleteme") == 0 || assert(false);
    scope(exit) remove("deleteme");
    assert(exists("deleteme"));
}

/***************************************************
Get file $(D name) attributes.

Throws: $(D FileException) on error.
 */

version(Windows) uint getAttributes(in char[] name)
{
    auto result = useWfuncs
        ? GetFileAttributesW(std.utf.toUTF16z(name))
        : GetFileAttributesA(toMBSz(name));
    enforce(result != uint.max,
            new FileException(name.idup));
    return result;
}

version(Posix) uint getAttributes(in char[] name)
{
    struct_stat64 statbuf = void;
    cenforce(stat64(toStringz(name), &statbuf) == 0, name);
    return statbuf.st_mode;
}

/****************************************************
 * Is $(D name) a directory?
 * Throws: $(D FileException) if $(D name) doesn't exist.
 */

version(Windows) bool isdir(in char[] name)
{
    return (getAttributes(name) & FILE_ATTRIBUTE_DIRECTORY) != 0;
}

version(Posix) bool isdir(in char[] name)
{
    return (getAttributes(name) & S_IFMT) == S_IFDIR;
}

/****************************************************
 * Is $(D name) a file?
 * Throws: $(D FileException) if $(D name) doesn't exist.
 */

version(Windows) bool isfile(in char[] name)
{
    return !isdir(name);
}

version(Posix) bool isfile(in char[] name)
{
    return (getAttributes(name) & S_IFMT) == S_IFREG;        // regular file
}
    
/****************************************************
 * Change directory to $(D pathname).
 * Throws: $(D FileException) on error.
 */

version(Windows) void chdir(in char[] pathname)
{
    enforce(useWfuncs
            ? SetCurrentDirectoryW(std.utf.toUTF16z(pathname))
            : SetCurrentDirectoryA(toMBSz(pathname)),
            new FileException(pathname.idup));
}

version(Posix) void chdir(in char[] pathname)
{
    cenforce(core.sys.posix.unistd.chdir(toStringz(pathname)) == 0,
            pathname);
}
    
/****************************************************
Make directory $(D pathname).

Throws: $(D FileException) on error.
 */

version(Windows) void mkdir(in char[] pathname)
{
    enforce(useWfuncs
            ? CreateDirectoryW(std.utf.toUTF16z(pathname), null)
            : CreateDirectoryA(toMBSz(pathname), null),
            new FileException(pathname.idup));
}

version(Posix) void mkdir(in char[] pathname)
{
    cenforce(core.sys.posix.sys.stat.mkdir(toStringz(pathname), 0777) == 0,
            pathname);
}

/****************************************************
 * Make directory and all parent directories as needed.
 */

void mkdirRecurse(in char[] pathname)
{
    const left = dirname(pathname);
    if (!exists(left))
    {
        version (Windows)
        {   /* Prevent infinite recursion if left is "d:\" and
             * drive d does not exist.
             */
            if (left.length >= 3 && left[$ - 2] == ':')
                throw new FileException(left.idup);
        }
        mkdirRecurse(left);
    }
    mkdir(pathname);
}

/****************************************************
Remove directory $(D pathname).

Throws: $(D FileException) on error.
 */

version(Windows) void rmdir(in char[] pathname)
{
    cenforce(useWfuncs
            ? RemoveDirectoryW(std.utf.toUTF16z(pathname))
            : RemoveDirectoryA(toMBSz(pathname)),
            pathname);
}

version(Posix) void rmdir(in char[] pathname)
{
    cenforce(core.sys.posix.unistd.rmdir(toStringz(pathname)) == 0,
            pathname);
}

/****************************************************
 * Get current directory.
 * Throws: $(D FileException) on error.
 */

version(Windows) string getcwd()
{
    // A bit odd API: calling GetCurrentDirectory(0, null) returns
    // length including the \0, whereas calling with non-zero
    // params returns length excluding the \0.
    if (useWfuncs)
    {
        auto dir =
            new wchar[enforce(GetCurrentDirectoryW(0, null), "getcwd")];
        dir = dir[0 .. GetCurrentDirectoryW(dir.length, dir.ptr)];
        cenforce(dir.length, "getcwd");
        return to!string(dir);
    }
    else
    {
        auto dir =
            new char[enforce(GetCurrentDirectoryA(0, null), "getcwd")];
        dir = dir[0 .. GetCurrentDirectoryA(dir.length, dir.ptr)];
        cenforce(dir.length, "getcwd");
        return assumeUnique(dir);
    }
}

version(Posix) string getcwd()
{
    auto p = cenforce(core.sys.posix.unistd.getcwd(null, 0),
            "cannot get cwd");
    scope(exit) std.c.stdlib.free(p);
    return p[0 .. std.c.string.strlen(p)].idup;
}

unittest
{
    auto s = getcwd();
    assert(s.length);
}

/***************************************************
 * Directory Entry
 */

version(Windows) 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(in char[] path, in WIN32_FIND_DATA *fd)
    {
        auto clength = std.c.string.strlen(fd.cFileName.ptr);
            
        // Convert cFileName[] to unicode
        const wlength = MultiByteToWideChar(0, 0, fd.cFileName.ptr,
                clength, null,0);
        auto wbuf = new wchar[wlength];
        const n = MultiByteToWideChar(0, 0, fd.cFileName.ptr, clength,
                wbuf.ptr, 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(in char[] 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 $(D true) if DirEntry is a directory.
     */
    bool isdir() const
    {
        return (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
    }
        
    /****
     * Return !=0 if DirEntry is a file.
     */
    bool isfile() const
    {
        return !isdir;
    }
}

version(Posix) 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_stat64 statbuf;
    bool didstat;                        // done lazy evaluation of stat()
        
    void init(in char[] path, core.sys.posix.dirent.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() const
    {
        return (d_type & DT_DIR) != 0;
    }
        
    bool isfile() const
    {
        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(stat64(toStringz(name), &statbuf) == 0,
                "Failed to stat file `"~name~"'");
        _size = statbuf.st_size;
        _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;
        didstat = true;
    }
}

/******************************************************
 * 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);
 * }
 * ----
 */

version(Windows) void listdir(in char[] pathname,
        bool delegate(DirEntry* de) callback)
{
    DirEntry de;
    auto c = std.path.join(pathname, "*.*");
    if (useWfuncs)
    {
        WIN32_FIND_DATAW fileinfo;
        
        auto h = FindFirstFileW(std.utf.toUTF16z(c), &fileinfo);
        if (h == INVALID_HANDLE_VALUE)
            return;
        scope(exit) FindClose(h);
        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);
    }
    else
    {
        WIN32_FIND_DATA fileinfo;
        
        auto h = FindFirstFileA(toMBSz(c), &fileinfo);
        if (h == INVALID_HANDLE_VALUE)
            return;
        
        scope(exit) FindClose(h);
        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);
    }
}

version(Posix) void listdir(in char[] 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 file $(D from) to file $(D to). File timestamps are preserved.
 */

version(Windows) void copy(in char[] from, in char[] 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.idup);
}

version(Posix) void copy(in char[] from, in char[] to)
{
    invariant fd = core.sys.posix.fcntl.open(toStringz(from), O_RDONLY);
    cenforce(fd != -1, from);
    scope(exit) core.sys.posix.unistd.close(fd);
        
    struct_stat64 statbuf = void;
    cenforce(fstat64(fd, &statbuf) == 0, from);
    //cenforce(core.sys.posix.sys.stat.fstat(fd, &statbuf) == 0, from);
        
    auto toz = toStringz(to);
    invariant fdw = core.sys.posix.fcntl.open(toz,
            O_CREAT | O_WRONLY | O_TRUNC, 0660);
    cenforce(fdw != -1, from);
    scope(failure) std.c.stdio.remove(toz);
    {
        scope(failure) core.sys.posix.unistd.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 (auto size = statbuf.st_size; size; )
        {
            invariant toxfer = (size > BUFSIZ) ? BUFSIZ : cast(size_t) size;
            cenforce(
                core.sys.posix.unistd.read(fd, buf, toxfer) == toxfer
                && core.sys.posix.unistd.write(fdw, buf, toxfer) == toxfer,
                from);
            assert(size >= toxfer);
            size -= toxfer;
        }
    }
        
    cenforce(core.sys.posix.unistd.close(fdw) != -1, from);
        
    utimbuf utim = void;
    utim.actime = cast(time_t)statbuf.st_atime;
    utim.modtime = cast(time_t)statbuf.st_mtime;
        
    cenforce(utime(toz, &utim) != -1, from);
}

/*************************
 * Set access/modified times of file $(D name).
 * Throws: $(D FileException) on error.
 */

version(Windows) void setTimes(in char[] name, d_time fta, d_time ftm)
{
    const ta = d_time2FILETIME(fta);
    const tm = d_time2FILETIME(ftm);
    alias TypeTuple!(GENERIC_WRITE, 0, null, OPEN_EXISTING,
            FILE_ATTRIBUTE_NORMAL, HANDLE.init)
        defaults;
    auto h = useWfuncs
        ? CreateFileW(std.utf.toUTF16z(name), defaults)
        : CreateFileA(toMBSz(name), defaults);
    cenforce(h != INVALID_HANDLE_VALUE, name);
    scope(exit) cenforce(CloseHandle(h), name);

    cenforce(SetFileTime(h, null, &ta, &tm), name);
}

version(Posix) void setTimes(in char[] name, d_time fta, d_time ftm)
{
    timeval[2] t = void;
    t[0].tv_sec = to!int(fta / std.date.ticksPerSecond);
    t[0].tv_usec = cast(int)
        (cast(long) ((cast(double) fta / std.date.ticksPerSecond)
                * 1_000_000) % 1_000_000);
    t[1].tv_sec = to!int(ftm / std.date.ticksPerSecond);
    t[1].tv_usec = cast(int)
        (cast(long) ((cast(double) ftm / std.date.ticksPerSecond)
                * 1_000_000) % 1_000_000);
    enforce(utimes(toStringz(name), t) == 0);
}
/+
unittest
{
    system("echo a>deleteme") == 0 || assert(false);
    scope(exit) remove("deleteme");
    d_time ftc1, fta1, ftm1;
    getTimes("deleteme", ftc1, fta1, ftm1);
    enforce(collectException(setTimes("nonexistent", fta1, ftm1)));
    setTimes("deleteme", fta1 + 1000, ftm1 + 1000);
    d_time ftc2, fta2, ftm2;
    getTimes("deleteme", ftc2, fta2, ftm2);
    assert(fta1 + 1000 == fta2, text(fta1 + 1000, "!=", fta2));
    assert(ftm1 + 1000 == ftm2);
}
+/
    
/****************************************************
Remove directory and all of its content and subdirectories,
recursively.
 */

void rmdirRecurse(in char[] pathname)
{
    // all children, recursively depth-first
    foreach (DirEntry e; dirEntries(pathname.idup, SpanMode.depth))
    {
        e.isdir ? rmdir(e.name) : remove(e.name);
    }
    // the dir itself
    rmdir(pathname);
}

version(Windows) unittest
{
    auto d = r"\deleteme\a\b\c\d\e\f\g";
    mkdirRecurse(d);
    rmdirRecurse(r"\deleteme");
    enforce(!exists(r"\deleteme"));
}

version(Posix) 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"));
}

unittest
{
    void[] buf;

    buf = new void[10];
    (cast(byte[])buf)[] = 3;
    if (exists("unittest_write.tmp")) remove("unittest_write.tmp");
    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");
    assert(!exists("unittest_write.tmp"));
    remove("unittest_write2.tmp");
    assert(!exists("unittest_write2.tmp"));
}

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] : const(char)[]))
        {
            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;
        // worklist used only in breadth-first traversal
        string[] worklist = [ pathname ]; 

        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(std.process.system("mkdir --parents dmd-testing") == 0);
        scope(exit) std.process.system("rm -rf dmd-testing");
        assert(std.process.system("mkdir --parents dmd-testing/somedir") == 0);
        assert(std.process.system("touch dmd-testing/somefile") == 0);
        assert(std.process.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);
        }
    }
}

/**
Reads an entire file into an array.

Example:
----
// Load file; each line is an int followed by comma, whitespace and a
// double.
auto a = slurp!(int, double)("filename", "%s, %s");
----
 */
Select!(Types.length == 1, Types[0][], Tuple!(Types)[])
slurp(Types...)(string filename, in char[] format)
{
    typeof(return) result;
    auto app = appender(&result);
    ElementType!(typeof(return)) toAdd;
    auto f = File(filename);
    scope(exit) f.close;
    foreach (line; f.byLine())
    {
        formattedRead(line, format, &toAdd);
        enforce(line.empty,
                text("Trailing characters at the end of line: `", line,
                        "'"));
        app.put(toAdd);
    }
    return result;
}

unittest
{
    // Tuple!(int, double)[] x;
    // auto app = appender(&x);
    write("deleteme", "12 12.25\n345 1.125");
    scope(exit) remove("deleteme");
    auto a = slurp!(int, double)("deleteme", "%s %s");
    assert(a.length == 2);
    assert(a[0] == tuple(12, 12.25));
    assert(a[1] == tuple(345, 1.125));
}

/***************************************************
 * 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(in char[] pathname)
{
    Appender!(string[]) result;
    
    bool listing(string filename)
    {
        result.put(filename);
        return true; // continue
    }
    
    listdir(pathname, &listing);
    return result.data;
}

unittest
{
    assert(listdir(".").length > 0);
}

/*****************************************************
 * 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(in char[] pathname, in char[] pattern)
{
    Appender!(string[]) result;
    
    bool callback(DirEntry* de)
    {
        if (de.isdir)
            listdir(de.name, &callback);
        else
        {
            if (std.path.fnmatch(de.name, pattern))
                result.put(de.name);
        }
        return true; // continue
    }
    
    listdir(pathname, &callback);
    return result.data;
}
    
/** Ditto */
    
string[] listdir(in char[] pathname, RegExp r)
{
    Appender!(string[]) result;
    
    bool callback(DirEntry* de)
    {
        if (de.isdir)
            listdir(de.name, &callback);
        else
        {
            if (r.test(de.name))
                result.put(de.name);
        }
        return true; // continue
    }
    
    listdir(pathname, &callback);
    return result.data;
}
    
/******************************************************
 * 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 char[] pathname, bool delegate(string filename) callback)
{
    bool listing(DirEntry* de)
    {
        return callback(std.path.getBaseName(de.name));
    }
    
    listdir(pathname, &listing);
}
