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.
#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);
deferfile_delete(path);
assert(write_entire_file(path, "hello\n"));
text, ok := read_entire_file(path);
assert(ok && text == "hello\n");
free(text);
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);
deferfile_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);
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);
deferfile_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
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);
deferfile_delete(path);
assert(write_entire_file(path, "abcdef"));
file, ok := file_open(path);
assert(ok);
deferfile_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));
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);
deferfile_delete(path);
assert(write_entire_file(path, "abcdef"));
file, ok := file_open(path);
assert(ok);
deferfile_close(*file);
length, length_ok := file_length(file);
assert(length_ok && length == 6);
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);
deferfile_delete(path);
file, ok := file_open(path, for_writing = true);
assert(ok);
deferfile_close(*file);
assert(file_write(*file, "data"));
assert(file_flush(file));
text, read_ok := read_entire_file(path); // another reader sees it alreadyassert(read_ok && text == "data");
free(text);
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);
deferfile_delete(path);
to := tprint("%.moved", path);
deferfile_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);
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);
deferfile_delete(path);
assert(write_entire_file(path, "x"));
assert(file_delete(path));
assert(!file_delete(path)); // already gone
Creates the hard link link to the existing file filepath, so both names are one file. Note the order: the new name
comes first. Fails if link exists, on a directory, or across volumes or on a file system without hard links.
#import"Extensions/Environment";
base := environment_variable("TMPDIR");
if base == "" base = environment_variable("TEMP");
if base == "" base = "/tmp";
path := tprint("%/jai-doc-link.bin", base);
deferfile_delete(path);
other := tprint("%.link", path);
deferfile_delete(other);
assert(write_entire_file(path, "shared"));
assert(file_link(other, path));
text, ok := read_entire_file(other);
assert(ok && text == "shared");
free(text);
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);
deferfile_delete(path);
directory := tprint("%.dir", path);
assert(make_directory_if_it_does_not_exist(directory));
assert(delete_directory(directory));
assert(!delete_directory(directory)); // gone
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.
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.
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.
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.
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);
deferfile_delete(path);
assert(write_entire_file(path, "abc"));
file, ok := file_open(path);
assert(ok);
deferfile_close(*file);
success, position := file_tell(file);
assert(success && position == 0);
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);
deferfile_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"));
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);
deferfile_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);
Releases what map_entire_file_start returned and leaves data empty: it unmaps the view on Windows and frees the
text elsewhere. Do not use the old data afterwards. A failure to unmap is logged and data keeps its value.
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.
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.