Modules

File

moduleSource

Available on macOS, Linux, Windows, Wasm

#import "File";

Opening, reading, writing and managing files and directories by path.

A File is a handle from file_open; read_entire_file and write_entire_file do a whole file in one call, and map_entire_file_start borrows the content. Names are UTF-8 paths, with / as separator on every target (Windows accepts \ as well). Every procedure reports failure by its result and does not stop the program. Memory a procedure returns comes from context.allocator and the caller frees it. Walking directories, copying and metadata are in File_Utilities.

Example↗
#import "Extensions/Environment";
base := environment_variable("TMPDIR");
if base == "" base = environment_variable("TEMP");
if base == "" base = "/tmp";
path := tprint("%/jai-doc-file-module.txt", base);
defer file_delete(path);

assert(write_entire_file(path, "hello\n"));
text, ok := read_entire_file(path);
assert(ok && text == "hello\n");
free(text);

Procedures#

Native Windows#

(name: string, for_writing := false, keep_existing_content := false, log_errors := false) -> (File, bool)

Opens the file name and returns it with true, or a closed file and false when that fails.

#import "Extensions/Environment";
base := environment_variable("TMPDIR");
if base == "" base = environment_variable("TEMP");
if base == "" base = "/tmp";
path := tprint("%/jai-doc-file-open.txt", base);
defer file_delete(path);

_, found := file_open(path);
assert(!found); // reading needs an existing file

file, ok := file_open(path, for_writing = true);
assert(ok && file_write(*file, "hello"));
file_close(*file);

// Keeping the content starts at the first byte, so this overwrites it.
file, ok = file_open(path, for_writing = true, keep_existing_content = true);
assert(ok && file_write(*file, "J"));
file_close(*file);

text, read_ok := read_entire_file(path);
assert(read_ok && text == "Jello");
free(text);

Doesn't run in the browser (only on windows).

(file: *File)

Closes file and sets it to the closed state. A file that is already closed, or was never opened, is left alone, so closing twice is safe. A failure to close is logged and leaves the handle as it was.

#import "Extensions/Environment";
base := environment_variable("TMPDIR");
if base == "" base = environment_variable("TEMP");
if base == "" base = "/tmp";
path := tprint("%/jai-doc-close.bin", base);
defer file_delete(path);
assert(write_entire_file(path, "x"));
file, ok := file_open(path);
assert(ok && is_valid(file));
file_close(*file);
assert(!is_valid(file));
file_close(*file); // closing twice is fine

Doesn't run in the browser (only on windows).

(file: File) -> bool

Whether file is open: false for a zero File, for one file_open failed to open, and for one file_close closed.

file, ok := file_open("jai-doc-no-such-file.txt");
assert(!ok && !is_valid(file));

Doesn't run in the browser (only on windows).

(f: File, vdata: *void, bytes_to_read: s64) -> (success: bool, total_read: s64)

Reads up to bytes_to_read bytes from the current position into vdata, and returns (success, total_read).

#import "Extensions/Environment";
base := environment_variable("TMPDIR");
if base == "" base = environment_variable("TEMP");
if base == "" base = "/tmp";
path := tprint("%/jai-doc-file-read.bin", base);
defer file_delete(path);

assert(write_entire_file(path, "abcdef"));
file, ok := file_open(path);
assert(ok);
defer file_close(*file);

buffer: [4] u8;
success, count := file_read(file, buffer.data, buffer.count);
assert(success && count == 4 && buffer[0] == #char "a");
success, count = file_read(file, buffer.data, buffer.count);
assert(success && count == 2); // short: the end of the file
success, count = file_read(file, buffer.data, buffer.count);
assert(success && count == 0); // at the end

Doesn't run in the browser (only on windows).

(f: File) -> (s64, bool)

The position of the next read or write, in bytes from the start of the file, and whether it could be read. A closed file gives (0, false).

#import "Extensions/Environment";
base := environment_variable("TMPDIR");
if base == "" base = environment_variable("TEMP");
if base == "" base = "/tmp";
path := tprint("%/jai-doc-position.bin", base);
defer file_delete(path);
assert(write_entire_file(path, "abcdef"));
file, ok := file_open(path);
assert(ok);
defer file_close(*file);
buffer: [2] u8;
file_read(file, buffer.data, 2);
position, position_ok := file_current_position(file);
assert(position_ok && position == 2);

Doesn't run in the browser (only on windows).

(f: File, pos: s64) -> bool

Moves to byte pos counted from the start of the file. Returns false for a closed file or a negative pos.

#import "Extensions/Environment";
base := environment_variable("TMPDIR");
if base == "" base = environment_variable("TEMP");
if base == "" base = "/tmp";
path := tprint("%/jai-doc-set-position.bin", base);
defer file_delete(path);
assert(write_entire_file(path, "abcdef"));
file, ok := file_open(path);
assert(ok);
defer file_close(*file);
assert(file_set_position(file, 4));
rest, rest_ok := read_entire_file(file);
assert(rest_ok && rest == "ef");
free(rest);

Doesn't run in the browser (only on windows).

(file: File, pos: s64, type: Seek_Type) -> success: bool

Moves by pos bytes relative to .START, .CURRENT or .END of the file. A negative pos goes backwards. Returns false for a closed file or when the result would be before the start. Going past the end is allowed.

#import "Extensions/Environment";
base := environment_variable("TMPDIR");
if base == "" base = environment_variable("TEMP");
if base == "" base = "/tmp";
path := tprint("%/jai-doc-file-seek.bin", base);
defer file_delete(path);

assert(write_entire_file(path, "abcdef"));
file, ok := file_open(path);
assert(ok);
defer file_close(*file);

assert(file_seek(file, -2, .END));
tail, tail_ok := read_entire_file(file);
assert(tail_ok && tail == "ef");
free(tail);

assert(file_seek(file, 1, .START) && file_seek(file, 2, .CURRENT));
position, position_ok := file_current_position(file);
assert(position_ok && position == 3);
assert(!file_seek(file, -1, .START));

Doesn't run in the browser (only on windows).

(file: File) -> (s64, bool)

The size of the file in bytes and whether it could be read. The position does not change.

#import "Extensions/Environment";
base := environment_variable("TMPDIR");
if base == "" base = environment_variable("TEMP");
if base == "" base = "/tmp";
path := tprint("%/jai-doc-length.bin", base);
defer file_delete(path);
assert(write_entire_file(path, "abcdef"));
file, ok := file_open(path);
assert(ok);
defer file_close(*file);
length, length_ok := file_length(file);
assert(length_ok && length == 6);

Doesn't run in the browser (only on windows).

(file: File) -> bool

Asks the system to write the file's buffers to the device (FlushFileBuffers); returns false for a closed file or on an error.

#import "Extensions/Environment";
base := environment_variable("TMPDIR");
if base == "" base = environment_variable("TEMP");
if base == "" base = "/tmp";
path := tprint("%/jai-doc-flush.bin", base);
defer file_delete(path);
file, ok := file_open(path, for_writing = true);
assert(ok);
defer file_close(*file);
assert(file_write(*file, "data"));
assert(file_flush(file));
text, read_ok := read_entire_file(path); // another reader sees it already
assert(read_ok && text == "data");
free(text);

Doesn't run in the browser (only on windows).

(name_old: string, name_new: string) -> bool

Renames the file name_old to name_new, which may be on another volume. Returns false if that fails, including when name_new already exists: delete it first, or see File_Utilities.atomic_file_replace_or_just_rename_if_destination_file_does_not_exist.

#import "Extensions/Environment";
base := environment_variable("TMPDIR");
if base == "" base = environment_variable("TEMP");
if base == "" base = "/tmp";
path := tprint("%/jai-doc-move.bin", base);
defer file_delete(path);
to := tprint("%.moved", path);
defer file_delete(to);
assert(write_entire_file(path, "content"));
assert(file_move(path, to));
text, ok := read_entire_file(to);
assert(ok && text == "content");
free(text);
_, still_there := file_open(path);
assert(!still_there);

Doesn't run in the browser (only on windows).

(filename: string) -> bool

Deletes the file filename; false if it does not exist, is a directory (see delete_directory) or is read-only. Files this module opened can be deleted while open.

#import "Extensions/Environment";
base := environment_variable("TMPDIR");
if base == "" base = environment_variable("TEMP");
if base == "" base = "/tmp";
path := tprint("%/jai-doc-delete.bin", base);
defer file_delete(path);
assert(write_entire_file(path, "x"));
assert(file_delete(path));
assert(!file_delete(path)); // already gone

Doesn't run in the browser (only on windows).

(dirname: string) -> bool

Removes the directory dirname if it is empty. Returns false if it is missing, not empty, or not a directory. To remove a folder with its content, see File_Utilities.delete_directory_tree.

#import "Extensions/Environment";
base := environment_variable("TMPDIR");
if base == "" base = environment_variable("TEMP");
if base == "" base = "/tmp";
path := tprint("%/jai-doc-rmdir.bin", base);
defer file_delete(path);
directory := tprint("%.dir", path);
assert(make_directory_if_it_does_not_exist(directory));
assert(delete_directory(directory));
assert(!delete_directory(directory)); // gone

Doesn't run in the browser (only on windows).

(name: string, recursive := false) -> directory_now_exists: bool

Makes sure the directory name exists and returns whether it does afterwards: an existing directory is a success, a file of that name or (without recursive) a missing parent is a failure. With recursive, missing parents are created too. Both / and \ work as separators.

#import "Extensions/Environment";
base := environment_variable("TMPDIR");
if base == "" base = environment_variable("TEMP");
if base == "" base = "/tmp";
root := tprint("%/jai-doc-make-directory", base);
leaf := tprint("%/a/b", root);
defer {
    delete_directory(leaf);
    delete_directory(tprint("%/a", root));
    delete_directory(root);
}

assert(!make_directory_if_it_does_not_exist(leaf)); // the parents are missing
assert(make_directory_if_it_does_not_exist(leaf, recursive = true));
assert(make_directory_if_it_does_not_exist(leaf)); // already there

Doesn't run in the browser (only on windows).

(relative_path: string) -> (string, success: bool)

The absolute form of relative_path, in a new string from context.allocator that the caller frees, and whether it worked.

path, ok := get_absolute_path(".");
assert(ok && path.count > 0);
free(path);

Doesn't run in the browser (only on windows).

map_fileWindows onlySource
(name: string, log_errors := false) -> (File_Map_Info, bool)

Maps the whole file name for reading and returns ok; an empty file succeeds with empty data. Give the result to munmap_file. A failure is logged through context.logger when log_errors is true, and nothing needs releasing then.

munmap_fileWindows onlySource
(file_map: *File_Map_Info) -> bool

Releases a File_Map_Info: unmaps the view, closes the mapping and the file. Safe to call again on the same record; returns false if the system refuses.

() -> string

The path of the machine-wide application data folder (ProgramData), created if missing, in a new UTF-8 string the caller frees; "" on failure.

() -> string

The path of the current user's Saved Games folder, created if missing, in a new UTF-8 string the caller frees; "" on failure.

file_get_canonical_path2 overloadsWindows onlySource
(file: File) -> string
(filepath: string) -> string

The final path of an open file, with links followed, in a new string that the caller frees; "" when file is closed or the system cannot say. Windows spells it with the \\?\ prefix and \ separators.

File#

(file: File) -> (success: bool, position: s64)

The current position of file as (success, position).

#import "Extensions/Environment";
base := environment_variable("TMPDIR");
if base == "" base = environment_variable("TEMP");
if base == "" base = "/tmp";
path := tprint("%/jai-doc-tell.bin", base);
defer file_delete(path);
assert(write_entire_file(path, "abc"));
file, ok := file_open(path);
assert(ok);
defer file_close(*file);
success, position := file_tell(file);
assert(success && position == 0);
↗
file_write2 overloadsSource
(file: *File, data: string) -> bool
(file: *File, builder: *String_Builder, do_reset := true) -> bool

Writes all of data to file. Returns false when the file is not open or the write fails or stops short.

#import "Extensions/Environment";
base := environment_variable("TMPDIR");
if base == "" base = environment_variable("TEMP");
if base == "" base = "/tmp";
path := tprint("%/jai-doc-write-string.bin", base);
defer file_delete(path);
file, ok := file_open(path, for_writing = true);
assert(ok);
assert(file_write(*file, "hello "));
assert(file_write(*file, "world"));
file_close(*file);
text, read_ok := read_entire_file(path);
assert(read_ok && text == "hello world");
free(text);
↗
(f: File, zero_terminated := false) -> (string, bool)
(name: string, zero_terminated := false, log_errors := false) -> (string, bool)

Reads the rest of an open file, from its current position to its end, into a new string.

#import "Extensions/Environment";
base := environment_variable("TMPDIR");
if base == "" base = environment_variable("TEMP");
if base == "" base = "/tmp";
path := tprint("%/jai-doc-read-rest.bin", base);
defer file_delete(path);

assert(write_entire_file(path, "abcdef"));
file, ok := file_open(path);
assert(ok);
defer file_close(*file);

assert(file_set_position(file, 2));
rest, read_ok := read_entire_file(file, zero_terminated = true);
assert(read_ok && rest == "cdef" && rest.data[rest.count] == 0);
free(rest);
↗
(name: string, data: string) -> bool
(name: string, data: *void, count: int) -> bool
(name: string, builder: *String_Builder, do_reset := true) -> bool

Replaces the file name with data: it is created if missing and cut to nothing otherwise. Returns false when it cannot be opened, written or flushed.

#import "Extensions/Environment";
base := environment_variable("TMPDIR");
if base == "" base = environment_variable("TEMP");
if base == "" base = "/tmp";
path := tprint("%/jai-doc-write-entire.txt", base);
defer file_delete(path);

assert(write_entire_file(path, "a long first version"));
assert(write_entire_file(path, "short"));
text, ok := read_entire_file(path);
assert(ok && text == "short");
free(text);
assert(!write_entire_file(tprint("%/jai-doc-no-such-dir/file", base), "x"));
↗
(name: string, log_errors := false) -> (Map_File_Info, bool)

Gives a file's whole content as Map_File_Info.data; returns ok as false and empty data when the file cannot be read.

#import "Extensions/Environment";
base := environment_variable("TMPDIR");
if base == "" base = environment_variable("TEMP");
if base == "" base = "/tmp";
path := tprint("%/jai-doc-map-file.txt", base);
defer file_delete(path);

assert(write_entire_file(path, "mapped text"));
info, ok := map_entire_file_start(path);
assert(ok && info.data == "mapped text");
map_entire_file_end(*info);
assert(info.data.count == 0);
↗

Types#

:: struct

An open file. It holds the operating system's HANDLE and nothing else, so a copy of a File is the same open file, not a second one. Get one from file_open, and give it back with file_close exactly once.

handle: W.HANDLE
file, ok := file_open("jai-doc-no-such-file.txt");
assert(!ok && !is_valid(file));

Doesn't run in the browser (only on windows).

:: struct

A whole file's bytes, as map_entire_file_start returns them: data is the content, and the record must go back to map_entire_file_end.

data: string
File_Map_InfostructWindows onlySource
:: struct

A read-only view of a whole file, from map_file: data is its content, mapping the mapping object and file the open file. Give it back to munmap_file. Writing to data faults.

file: File
mapping: W.HANDLE
data: string

Constants#

:: OS == .WINDOWS

Whether map_entire_file_start maps the file into memory (Windows) or reads it into allocated memory (every other target).

:: 0x7fffffffffffffff

The largest s64, kept for programs that compare against it. No procedure of this module returns it. Unix targets only.

:: 0x001a

The legacy folder id of the roaming application data folder, for Windows APIs that take one. Windows only.

:: 0x00008000

The known-folder flag that creates the folder when it is missing, as get_path_os_program_data and get_path_os_saved_games ask for. Windows only.