Newer
Older
ddhx / src / os / file.d
@dd86k dd86k on 27 Dec 2022 8 KB file: fix on linux
/// File handling.
///
/// This exists because 32-bit runtimes suffer from the 32-bit file size limit.
/// Despite the MS C runtime having _open and _fseeki64, the DM C runtime does
/// not have these functions.
/// 
/// Copyright: dd86k <dd@dax.moe>
/// License: MIT
/// Authors: $(LINK2 https://github.com/dd86k, dd86k)
module os.file;

version (Windows)
{
    import core.sys.windows.winnt :
        DWORD, HANDLE, LARGE_INTEGER, FALSE, TRUE,
        GENERIC_ALL, GENERIC_READ, GENERIC_WRITE;
    import core.sys.windows.winbase :
        GetLastError,
        CreateFileA, CreateFileW,
        SetFilePointerEx, GetFileSizeEx,
        ReadFile, ReadFileEx,
        WriteFile,
        FlushFileBuffers,
        CloseHandle,
        OPEN_ALWAYS, OPEN_EXISTING, INVALID_HANDLE_VALUE,
        FILE_BEGIN, FILE_CURRENT, FILE_END;
    
    private alias OSHANDLE = HANDLE;
    private alias SEEK_SET = FILE_BEGIN;
    private alias SEEK_CUR = FILE_CURRENT;
    private alias SEEK_END = FILE_END;
    
    private enum OFLAG_OPENONLY = OPEN_EXISTING;
}
else version (Posix)
{
    import core.sys.posix.unistd;
    import core.sys.posix.sys.types;
    import core.sys.posix.sys.stat;
    import core.sys.posix.fcntl;
    import core.stdc.errno;
    import core.stdc.stdio : SEEK_SET, SEEK_CUR, SEEK_END;
    
    // BLKGETSIZE64 missing from dmd 2.098.1 and ldc 1.24.0
    // ldc 1.24 missing core.sys.linux.fs
    // source musl 1.2.0 and glibc 2.25 has roughly same settings.
    
    private enum _IOC_NRBITS = 8;
    private enum _IOC_TYPEBITS = 8;
    private enum _IOC_SIZEBITS = 14;
    private enum _IOC_NRSHIFT = 0;
    private enum _IOC_TYPESHIFT = _IOC_NRSHIFT+_IOC_NRBITS;
    private enum _IOC_SIZESHIFT = _IOC_TYPESHIFT+_IOC_TYPEBITS;
    private enum _IOC_DIRSHIFT = _IOC_SIZESHIFT+_IOC_SIZEBITS;
    private enum _IOC_READ = 2;
    private enum _IOC(int dir,int type,int nr,size_t size) =
        (dir  << _IOC_DIRSHIFT) |
        (type << _IOC_TYPESHIFT) |
        (nr   << _IOC_NRSHIFT) |
        (size << _IOC_SIZESHIFT);
    //TODO: _IOR!(0x12,114,size_t.sizeof) results in ulong.max
    //      I don't know why, so I'm casting it to int to let it compile.
    //      Fix later.
    private enum _IOR(int type,int nr,size_t size) =
        cast(int)_IOC!(_IOC_READ,type,nr,size);
    
    private enum BLKGETSIZE64 = cast(int)_IOR!(0x12,114,size_t.sizeof);
    private alias BLOCKSIZE = BLKGETSIZE64;
    
    private extern (C) int ioctl(int,long,...);
    
    private alias OSHANDLE = int;
} else {
    static assert(0, "Implement file I/O");
}

//TODO: FileType GetType(string)
//      Pipe, device, etc.

import std.string : toStringz;
import std.utf : toUTF16z;

/// File seek origin.
enum Seek
{
    start    = SEEK_SET,    /// Seek since start of file.
    current    = SEEK_CUR,    /// Seek since current position in file.
    end    = SEEK_END    /// Seek since end of file.
}

/// Open file flags
enum OFlags
{
    exists  = 1,        /// File must exist.
    read    = 1 << 1,   /// Read access.
    write   = 1 << 2,   /// Write access.
    readWrite = read | write,   /// Read and write access.
    share   = 1 << 5,   /// [TODO] Share file with read access to other programs.
}

//TODO: Set file size (to extend or truncate file, allocate size)
//    useful when writing all changes to file
//    Win32: Seek + SetEndOfFile
//    others: ftruncate
struct OSFile {
    private OSHANDLE handle;
    bool eof, err;
    
    void cleareof()
    {
        eof = false;
    }
    void clearerr()
    {
        err = false;
    }
    int syscode()
    {
        version (Windows)
            return GetLastError();
        else
            return errno;
    }
    
    //TODO: Share file.
    //      By default, at least on Windows, files aren't shared. Enabling
    //      sharing would allow refreshing view (manually) when a program
    //      writes to file.
    bool open(string path, int flags = OFlags.readWrite)
    {
        version (Windows)
        {
            uint dwAccess;
            uint dwCreation;
            
            if (flags & OFlags.exists)  dwCreation |= OPEN_EXISTING;
            else                        dwCreation |= OPEN_ALWAYS;
            if (flags & OFlags.read)    dwAccess |= GENERIC_READ;
            if (flags & OFlags.write)   dwAccess |= GENERIC_WRITE;
            
            // NOTE: toUTF16z/tempCStringW
            //       Phobos internally uses tempCStringW from std.internal
            //       but I doubt it's meant for us to use so...
            //       Legacy baggage?
            handle = CreateFileW(
                path.toUTF16z,  // lpFileName
                dwAccess,       // dwDesiredAccess
                0,              // dwShareMode
                null,           // lpSecurityAttributes
                dwCreation,     // dwCreationDisposition
                0,              // dwFlagsAndAttributes
                null,           // hTemplateFile
            );
            return err = handle == INVALID_HANDLE_VALUE;
        }
        else version (Posix)
        {
            int oflags;
            if (!(flags & OFlags.exists)) oflags |= O_CREAT;
            if ((flags & OFlags.readWrite) == OFlags.readWrite)
                oflags |= O_RDWR;
            else if (flags & OFlags.write)
                oflags |= O_WRONLY;
            else if (flags & OFlags.read)
                oflags |= O_RDONLY;
            handle = .open(path.toStringz, oflags);
            return err = handle == -1;
        }
    }
    
    long seek(Seek origin, long pos)
    {
        version (Windows)
        {
            LARGE_INTEGER i = void;
            i.QuadPart = pos;
            err = SetFilePointerEx(handle, i, &i, origin) == FALSE;
            return i.QuadPart;
        }
        else version (OSX)
        {
            // NOTE: Darwin has set off_t as long
            //       and doesn't have lseek64
            pos = lseek(handle, pos, origin);
            err = pos == -1;
            return pos;
        }
        else version (Posix) // Should cover glibc and musl
        {
            pos = lseek64(handle, pos, origin);
            err = pos == -1;
            return pos;
        }
    }
    
    long tell()
    {
        version (Windows)
        {
            LARGE_INTEGER i; // .init
            SetFilePointerEx(handle, i, &i, FILE_CURRENT);
            return i.QuadPart;
        }
        else version (OSX)
        {
            return lseek(handle, 0, SEEK_CUR);
        }
        else version (Posix)
        {
            return lseek64(handle, 0, SEEK_CUR);
        }
    }
    
    long size()
    {
        version (Windows)
        {
            LARGE_INTEGER li = void;
            err = GetFileSizeEx(handle, &li) == 0;
            return err ? -1 : li.QuadPart;
        }
        else version (Posix)
        {
            stat_t stats = void;
            if (fstat(handle, &stats) == -1)
                return -1;
            // NOTE: fstat(2) sets st_size to 0 on block devices
            switch (stats.st_mode & S_IFMT) {
            case S_IFREG: // File
            case S_IFLNK: // Link
                return stats.st_size;
            case S_IFBLK: // Block devices (like a disk)
                //TODO: BSD variants
                long s = void;
                return ioctl(handle, BLOCKSIZE, &s) == -1 ? -1 : s;
            default:
                return -1;
            }
        }
    }
    
    ubyte[] read(ubyte[] buffer)
    {
        return read(buffer.ptr, buffer.length);
    }
    
    ubyte[] read(ubyte *buffer, size_t size)
    {
        version (Windows)
        {
            uint len = cast(uint)size;
            err = ReadFile(handle, buffer, len, &len, null) == FALSE;
            if (err) return null;
            eof = len < size;
            return buffer[0..len];
        }
        else version (Posix)
        {
            ssize_t len = .read(handle, buffer, size);
            if ((err = len < 0) == true) return null;
            eof = len < size;
            return buffer[0..len];
        }
    }
    
    size_t write(ubyte[] data)
    {
        return write(data.ptr, data.length);
    }
    
    size_t write(ubyte *data, size_t size)
    {
        version (Windows)
        {
            uint len = cast(uint)size;
            err = WriteFile(handle, data, len, &len, null) == FALSE;
            return len; // 0 on error anyway
        }
        else version (Posix)
        {
            ssize_t len = .write(handle, data, size);
            err = len < 0;
            return err ? 0 : len;
        }
    }
    
    void flush()
    {
        version (Windows)
        {
            FlushFileBuffers(handle);
        }
        else version (Posix)
        {
            .fsync(handle);
        }
    }
    
    void close()
    {
        version (Windows)
        {
            CloseHandle(handle);
        }
        else version (Posix)
        {
            .close(handle);
        }
    }
}