| /** |
| * Read a file from disk and store it in memory. |
| * |
| * Copyright: Copyright (C) 1999-2022 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; |
| } |
| } |
| } |
| } |