Skip to content

Workspace Guard manifest

View source on GitHub

workspace_guard/src/manifest.fl
module manifest;
@imports
:: root::model
:: lib::std::containers::collections
:: lib::std::encoding::hex
:: lib::std::io::buffered
:: lib::std::io::errors as io_errors
:: lib::std::io::file
:: lib::std::io::filesystem
:: lib::std::text::strings
:: lib::std::text::utf8;
const stringv HEADER = "FLECHA-WORKSPACE-SNAPSHOT\t1\n";
const usize WRITE_CAPACITY = 8192;
export error write(
stringv destination,
&const collections::Array<model::Entry> entries
) {
present, existsError := filesystem::exists(destination);
if existsError != none {
return existsError;
}
if present {
return io_errors::IoError.AlreadyExists;
}
options := build file::OpenOptions {
self.write = true;
self.createNew = true;
};
output, openError := file::File::open(destination, options);
if openError != none {
return openError;
}
error result = none;
{
adapter := output.writer();
writer, writerError :=
buffered::BufferedWriter::withCapacity(adapter, WRITE_CAPACITY);
if writerError != none {
result = writerError;
} else {
result = write_contents(&writer, entries);
}
}
if result == none {
result = output.flush();
}
closeError := output.close();
if result == none {
result = closeError;
}
if result != none {
error _ = filesystem::remove_file(destination);
}
return result;
}
error write_contents(
&buffered::BufferedWriter writer,
&const collections::Array<model::Entry> entries
) {
error writeError = writer->writeAll(HEADER.cast<[]const u8>);
if writeError != none {
return writeError;
}
values := entries->view();
loop usize index = 0; index < values.len(); index++ {
pathHex, pathError := hex::encode(values[index].path());
if pathError != none {
return pathError;
}
sizeText, sizeError :=
strings::int_to_string<u64>(values[index].byteSize());
if sizeError != none {
return sizeError;
}
writeError = writer->writeAll(
values[index].hash().cast<[]const u8>
);
if writeError == none {
writeError = writer->writeAll("\t".cast<[]const u8>);
}
if writeError == none {
writeError = writer->writeAll(sizeText.view().cast<[]const u8>);
}
if writeError == none {
writeError = writer->writeAll("\t".cast<[]const u8>);
}
if writeError == none {
writeError = writer->writeAll(pathHex.view().cast<[]const u8>);
}
if writeError == none {
writeError = writer->writeAll("\n".cast<[]const u8>);
}
if writeError != none {
return writeError;
}
}
return writer->flush();
}
export (collections::Array<model::Entry>, error) read(stringv sourcePath) {
contents, readError := filesystem::read_file(sourcePath);
if readError != none {
return (collections::Array<model::Entry>::init(), readError);
}
return parse(contents.view());
}
(collections::Array<model::Entry>, error) parse(stringv contents) {
entries := collections::Array<model::Entry>::init();
if contents.len() < HEADER.len() or
contents.substr(0, HEADER.len()) != HEADER {
return (entries, model::GuardError.InvalidManifest);
}
u32 offset = HEADER.len();
while offset < contents.len() {
u32 end = offset;
while end < contents.len() and contents[end] != '\n' {
end++;
}
if end == contents.len() or end == offset {
return (entries, model::GuardError.InvalidManifest);
}
entry, entryError := parse_line(contents.substr(offset, end - offset));
if entryError != none {
return (entries, entryError);
}
if !entries.empty() {
{
values := entries.view();
if !model::text_less(
values[values.len() - 1].path(),
entry.path()
) {
return (entries, model::GuardError.InvalidManifest);
}
}
}
pushError := entries.push(entry);
if pushError != none {
return (entries, pushError);
}
offset = end + 1;
}
return (entries, none);
}
(model::Entry, error) parse_line(stringv line) {
u32 first = line.len();
u32 second = line.len();
loop u32 index = 0; index < line.len(); index++ {
if line[index] != '\t' {
proceed;
}
if first == line.len() {
first = index;
} else if second == line.len() {
second = index;
} else {
return (none, model::GuardError.InvalidManifest);
}
}
if first == 0 or second <= first + 1 or second + 1 >= line.len() {
return (none, model::GuardError.InvalidManifest);
}
digestText := line.substr(0, first);
sizeText := line.substr(first + 1, second - first - 1);
pathText := line.substr(second + 1, line.len() - second - 1);
digestBytes, digestDecodeError := hex::decode_to_bytes(digestText);
if digestDecodeError != none or digestBytes.len() != 32 {
return (none, model::GuardError.InvalidManifest);
}
canonicalDigest, digestEncodeError := hex::encode_bytes(digestBytes[:]);
if digestEncodeError != none {
return (none, digestEncodeError);
}
if canonicalDigest.view() != digestText {
return (none, model::GuardError.InvalidManifest);
}
size, sizeParseError := strings::stringv_to_int<u64>(sizeText);
if sizeParseError != none {
return (none, model::GuardError.InvalidManifest);
}
canonicalSize, sizeFormatError := strings::int_to_string<u64>(size);
if sizeFormatError != none {
return (none, sizeFormatError);
}
if canonicalSize.view() != sizeText {
return (none, model::GuardError.InvalidManifest);
}
pathValue, pathDecodeError := hex::decode(pathText);
if pathDecodeError != none or utf8::validate(pathValue.view()) != none or
!model::valid_relative_path(pathValue.view()) {
return (none, model::GuardError.InvalidManifest);
}
canonicalPath, pathEncodeError := hex::encode(pathValue.view());
if pathEncodeError != none {
return (none, pathEncodeError);
}
if canonicalPath.view() != pathText {
return (none, model::GuardError.InvalidManifest);
}
return (model::Entry::init(pathValue, canonicalDigest, size), none);
}