blob: 1fb105682ea39f92aadca13ab2a3c6be18c83ea0 [file] [log] [blame]
/**
* Read a file from disk and store it in memory.
*
* Copyright: Copyright (C) 1999-2023 by The D Language Foundation, All Rights Reserved
* Authors: Walter Bright, https://www.digitalmars.com
* License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
* Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/root/file.d, root/_file.d)
* Documentation: https://dlang.org/phobos/dmd_root_file.html
* Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/root/file.d
*/
module dmd.root.file;
import core.stdc.errno;
import core.stdc.stdio;
import core.stdc.stdlib;
import core.stdc.string : strerror;
import core.sys.posix.fcntl;
import core.sys.posix.unistd;
import core.sys.windows.winbase;
import core.sys.windows.winnt;
import dmd.root.filename;
import dmd.root.rmem;
import dmd.root.string;
import dmd.common.file;
import dmd.common.string;
nothrow:
/// Owns a (rmem-managed) buffer.
struct Buffer
{
ubyte[] data;
nothrow:
this(this) @disable;
~this() pure nothrow
{
mem.xfree(data.ptr);
}
/// Transfers ownership of the buffer to the caller.
ubyte[] extractSlice() pure nothrow @nogc @safe
{
auto result = data;
data = null;
return result;
}
}
///
struct File
{
///
static struct ReadResult
{
bool success;
Buffer buffer;
/// Transfers ownership of the buffer to the caller.
ubyte[] extractSlice() pure nothrow @nogc @safe
{
return buffer.extractSlice();
}
/// ditto
/// Include the null-terminator at the end of the buffer in the returned array.
ubyte[] extractDataZ() @nogc nothrow pure
{
auto result = buffer.extractSlice();
return result.ptr[0 .. result.length + 1];
}
}
nothrow:
/// Read the full content of a file.
static ReadResult read(const(char)[] name)
{
ReadResult result;
version (Posix)
{
size_t size;
stat_t buf;
ssize_t numread;
//printf("File::read('%s')\n",name);
int fd = name.toCStringThen!(slice => open(slice.ptr, O_RDONLY));
if (fd == -1)
{
//perror("\topen error");
return result;
}
//printf("\tfile opened\n");
if (fstat(fd, &buf))
{
//perror("\tfstat error");
close(fd);
return result;
}
size = cast(size_t)buf.st_size;
ubyte* buffer = cast(ubyte*)mem.xmalloc_noscan(size + 4);
numread = .read(fd, buffer, size);
if (numread != size)
{
//perror("\tread error");
goto err2;
}
if (close(fd) == -1)
{
//perror("\tclose error");
goto err;
}
// Always store a wchar ^Z past end of buffer so scanner has a
// sentinel, although ^Z got obselete, so fill with two 0s and add
// two more so lexer doesn't read pass the buffer.
buffer[size .. size + 4] = 0;
result.success = true;
result.buffer.data = buffer[0 .. size];
return result;
err2:
close(fd);
err:
mem.xfree(buffer);
return result;
}
else version (Windows)
{
DWORD size;
DWORD numread;
// work around Windows file path length limitation
// (see documentation for extendedPathThen).
HANDLE h = name.extendedPathThen!
(p => CreateFileW(p.ptr,
GENERIC_READ,
FILE_SHARE_READ,
null,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
null));
if (h == INVALID_HANDLE_VALUE)
return result;
size = GetFileSize(h, null);
ubyte* buffer = cast(ubyte*)mem.xmalloc_noscan(size + 4);
if (ReadFile(h, buffer, size, &numread, null) != TRUE)
goto err2;
if (numread != size)
goto err2;
if (!CloseHandle(h))
goto err;
// Always store a wchar ^Z past end of buffer so scanner has a
// sentinel, although ^Z got obselete, so fill with two 0s and add
// two more so lexer doesn't read pass the buffer.
buffer[size .. size + 4] = 0;
result.success = true;
result.buffer.data = buffer[0 .. size];
return result;
err2:
CloseHandle(h);
err:
mem.xfree(buffer);
return result;
}
else
{
assert(0);
}
}
/// Write a file, returning `true` on success.
static bool write(const(char)* name, const void[] data)
{
import dmd.common.file : writeFile;
return writeFile(name, data);
}
///ditto
static bool write(const(char)[] name, const void[] data)
{
return name.toCStringThen!((fname) => write(fname.ptr, data));
}
/// Delete a file.
extern (C++) static void remove(const(char)* name)
{
version (Posix)
{
.remove(name);
}
else version (Windows)
{
name.toDString.extendedPathThen!(p => DeleteFileW(p.ptr));
}
else
{
static assert(0);
}
}
/***************************************************
* Update file
*
* If the file exists and is identical to what is to be written,
* merely update the timestamp on the file.
* Otherwise, write the file.
*
* The idea is writes are much slower than reads, and build systems
* often wind up generating identical files.
* Params:
* name = name of file to update
* data = updated contents of file
* Returns:
* `true` on success
*/
static bool update(const(char)* namez, const void[] data)
{
enum log = false;
if (log) printf("update %s\n", namez);
if (data.length != File.size(namez))
return write(namez, data); // write new file
if (log) printf("same size\n");
/* The file already exists, and is the same size.
* Read it in, and compare for equality.
*/
//if (FileMapping!(const ubyte)(namez)[] != data[])
return write(namez, data); // contents not same, so write new file
//if (log) printf("same contents\n");
/* Contents are identical, so set timestamp of existing file to current time
*/
//return touch(namez);
}
///ditto
static bool update(const(char)[] name, const void[] data)
{
return name.toCStringThen!(fname => update(fname.ptr, data));
}
/// Size of a file in bytes.
/// Params: namez = null-terminated filename
/// Returns: `ulong.max` on any error, the length otherwise.
static ulong size(const char* namez)
{
version (Posix)
{
stat_t buf;
if (stat(namez, &buf) == 0)
return buf.st_size;
}
else version (Windows)
{
const nameStr = namez.toDString();
import core.sys.windows.windows;
WIN32_FILE_ATTRIBUTE_DATA fad = void;
// Doesn't exist, not a regular file, different size
if (nameStr.extendedPathThen!(p => GetFileAttributesExW(p.ptr, GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, &fad)) != 0)
return (ulong(fad.nFileSizeHigh) << 32UL) | fad.nFileSizeLow;
}
else static assert(0);
// Error cases go here.
return ulong.max;
}
}
private
{
version (linux) version (PPC)
{
// https://issues.dlang.org/show_bug.cgi?id=22823
// Define our own version of stat_t, as older versions of the compiler
// had the st_size field at the wrong offset on PPC.
alias stat_t_imported = core.sys.posix.sys.stat.stat_t;
static if (stat_t_imported.st_size.offsetof != 48)
{
extern (C) nothrow @nogc:
struct stat_t
{
ulong[6] __pad1;
ulong st_size;
ulong[6] __pad2;
}
version (CRuntime_Glibc)
{
int fstat64(int, stat_t*) @trusted;
alias fstat = fstat64;
int stat64(const scope char*, stat_t*) @system;
alias stat = stat64;
}
else
{
int fstat(int, stat_t*) @trusted;
int stat(const scope char*, stat_t*) @system;
}
}
}
}