diff options
| author | Andrew Kelley <superjoe30@gmail.com> | 2017-04-17 19:08:41 -0400 |
|---|---|---|
| committer | Andrew Kelley <superjoe30@gmail.com> | 2017-04-17 19:08:41 -0400 |
| commit | 216e14891ea5fa1a88804d9781ef779d448d1220 (patch) | |
| tree | 8cfa1b656aaa6b6c01bdba7c5b7aedb5916f5088 /std | |
| parent | 401eed8153d909eda4146b5a1815dee7130cf1c3 (diff) | |
| download | zig-216e14891ea5fa1a88804d9781ef779d448d1220.tar.gz zig-216e14891ea5fa1a88804d9781ef779d448d1220.zip | |
zig build system creates symlinks atomically
* add std.base64
* add std.os.rename
* add std.os.atomicSymLink
Diffstat (limited to 'std')
| -rw-r--r-- | std/base64.zig | 184 | ||||
| -rw-r--r-- | std/build.zig | 12 | ||||
| -rw-r--r-- | std/index.zig | 1 | ||||
| -rw-r--r-- | std/os/index.zig | 75 | ||||
| -rw-r--r-- | std/os/linux.zig | 4 |
5 files changed, 265 insertions, 11 deletions
diff --git a/std/base64.zig b/std/base64.zig new file mode 100644 index 0000000000..8f4f2fac78 --- /dev/null +++ b/std/base64.zig @@ -0,0 +1,184 @@ +const assert = @import("debug.zig").assert; +const mem = @import("mem.zig"); + +pub const standard_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + +pub fn encode(dest: []u8, source: []const u8) -> []u8 { + return encodeWithAlphabet(dest, source, standard_alphabet); +} + +pub fn decode(dest: []u8, source: []const u8) -> []u8 { + return decodeWithAlphabet(dest, source, standard_alphabet); +} + +pub fn encodeWithAlphabet(dest: []u8, source: []const u8, alphabet: []const u8) -> []u8 { + assert(alphabet.len == 65); + assert(dest.len >= calcEncodedSize(source.len)); + + var i: usize = 0; + var out_index: usize = 0; + while (i + 2 < source.len; i += 3) { + dest[out_index] = alphabet[(source[i] >> 2) & 0x3f]; + out_index += 1; + + dest[out_index] = alphabet[((source[i] & 0x3) <<% 4) | + ((source[i + 1] & 0xf0) >> 4)]; + out_index += 1; + + dest[out_index] = alphabet[((source[i + 1] & 0xf) <<% 2) | + ((source[i + 2] & 0xc0) >> 6)]; + out_index += 1; + + dest[out_index] = alphabet[source[i + 2] & 0x3f]; + out_index += 1; + } + + if (i < source.len) { + dest[out_index] = alphabet[(source[i] >> 2) & 0x3f]; + out_index += 1; + + if (i + 1 == source.len) { + dest[out_index] = alphabet[(source[i] & 0x3) <<% 4]; + out_index += 1; + + dest[out_index] = alphabet[64]; + out_index += 1; + } else { + dest[out_index] = alphabet[((source[i] & 0x3) <<% 4) | + ((source[i + 1] & 0xf0) >> 4)]; + out_index += 1; + + dest[out_index] = alphabet[(source[i + 1] & 0xf) <<% 2]; + out_index += 1; + } + + dest[out_index] = alphabet[64]; + out_index += 1; + } + + return dest[0...out_index]; +} + +pub fn decodeWithAlphabet(dest: []u8, source: []const u8, alphabet: []const u8) -> []u8 { + assert(alphabet.len == 65); + + var ascii6 = []u8{64} ** 256; + for (alphabet) |c, i| { + ascii6[c] = u8(i); + } + + return decodeWithAscii6BitMap(dest, source, ascii6[0...], alphabet[64]); +} + +pub fn decodeWithAscii6BitMap(dest: []u8, source: []const u8, ascii6: []const u8, pad_char: u8) -> []u8 { + assert(ascii6.len == 256); + assert(dest.len >= calcExactDecodedSizeWithPadChar(source, pad_char)); + + var src_index: usize = 0; + var dest_index: usize = 0; + var in_buf_len: usize = source.len; + + while (in_buf_len > 0 and source[in_buf_len - 1] == pad_char) { + in_buf_len -= 1; + } + + while (in_buf_len > 4) { + dest[dest_index] = ascii6[source[src_index + 0]] <<% 2 | + ascii6[source[src_index + 1]] >> 4; + dest_index += 1; + + dest[dest_index] = ascii6[source[src_index + 1]] <<% 4 | + ascii6[source[src_index + 2]] >> 2; + dest_index += 1; + + dest[dest_index] = ascii6[source[src_index + 2]] <<% 6 | + ascii6[source[src_index + 3]]; + dest_index += 1; + + src_index += 4; + in_buf_len -= 4; + } + + if (in_buf_len > 1) { + dest[dest_index] = ascii6[source[src_index + 0]] <<% 2 | + ascii6[source[src_index + 1]] >> 4; + dest_index += 1; + } + if (in_buf_len > 2) { + dest[dest_index] = ascii6[source[src_index + 1]] <<% 4 | + ascii6[source[src_index + 2]] >> 2; + dest_index += 1; + } + if (in_buf_len > 3) { + dest[dest_index] = ascii6[source[src_index + 2]] <<% 6 | + ascii6[source[src_index + 3]]; + dest_index += 1; + } + + return dest[0...dest_index]; +} + +pub fn calcEncodedSize(source_len: usize) -> usize { + return (((source_len * 4) / 3 + 3) / 4) * 4; +} + +/// Computes the upper bound of the decoded size based only on the encoded length. +/// To compute the exact decoded size, see ::calcExactDecodedSize +pub fn calcMaxDecodedSize(encoded_len: usize) -> usize { + return @divExact(encoded_len * 3, 4); +} + +/// Computes the number of decoded bytes there will be. This function must +/// be given the encoded buffer because there might be padding +/// bytes at the end ('=' in the standard alphabet) +pub fn calcExactDecodedSize(encoded: []const u8) -> usize { + return calcExactDecodedSizeWithAlphabet(encoded, standard_alphabet); +} + +pub fn calcExactDecodedSizeWithAlphabet(encoded: []const u8, alphabet: []const u8) -> usize { + assert(alphabet.len == 65); + return calcExactDecodedSizeWithPadChar(encoded, alphabet[64]); +} + +pub fn calcExactDecodedSizeWithPadChar(encoded: []const u8, pad_char: u8) -> usize { + var buf_len = encoded.len; + + while (buf_len > 0 and encoded[buf_len - 1] == pad_char) { + buf_len -= 1; + } + + return (buf_len * 3) / 4; +} + +test "base64" { + testBase64(); + comptime testBase64(); +} + +fn testBase64() { + testBase64Case("", ""); + testBase64Case("f", "Zg=="); + testBase64Case("fo", "Zm8="); + testBase64Case("foo", "Zm9v"); + testBase64Case("foob", "Zm9vYg=="); + testBase64Case("fooba", "Zm9vYmE="); + testBase64Case("foobar", "Zm9vYmFy"); +} + +fn testBase64Case(expected_decoded: []const u8, expected_encoded: []const u8) { + const calculated_decoded_len = calcExactDecodedSize(expected_encoded); + assert(calculated_decoded_len == expected_decoded.len); + + const calculated_encoded_len = calcEncodedSize(expected_decoded.len); + assert(calculated_encoded_len == expected_encoded.len); + + var buf: [100]u8 = undefined; + + const actual_decoded = decode(buf[0...], expected_encoded); + assert(actual_decoded.len == expected_decoded.len); + assert(mem.eql(u8, expected_decoded, actual_decoded)); + + const actual_encoded = encode(buf[0...], expected_decoded); + assert(actual_encoded.len == expected_encoded.len); + assert(mem.eql(u8, expected_encoded, actual_encoded)); +} diff --git a/std/build.zig b/std/build.zig index cdb3c7707c..ad42caa506 100644 --- a/std/build.zig +++ b/std/build.zig @@ -816,11 +816,9 @@ const CLibrary = struct { builder.spawnChild(cc, cc_args.toSliceConst()); // sym link for libfoo.so.1 to libfoo.so.1.2.3 - _ = os.deleteFile(builder.allocator, self.major_only_filename); - %%os.symLink(builder.allocator, self.out_filename, self.major_only_filename); + %%os.atomicSymLink(builder.allocator, self.out_filename, self.major_only_filename); // sym link for libfoo.so to libfoo.so.1 - _ = os.deleteFile(builder.allocator, self.name_only_filename); - %%os.symLink(builder.allocator, self.major_only_filename, self.name_only_filename); + %%os.atomicSymLink(builder.allocator, self.major_only_filename, self.name_only_filename); } } @@ -1029,10 +1027,8 @@ const InstallCLibraryStep = struct { self.builder.copyFile(self.lib.out_filename, self.dest_file); if (!self.lib.static) { - _ = os.deleteFile(self.builder.allocator, self.lib.major_only_filename); - %%os.symLink(self.builder.allocator, self.lib.out_filename, self.lib.major_only_filename); - _ = os.deleteFile(self.builder.allocator, self.lib.name_only_filename); - %%os.symLink(self.builder.allocator, self.lib.major_only_filename, self.lib.name_only_filename); + %%os.atomicSymLink(self.builder.allocator, self.lib.out_filename, self.lib.major_only_filename); + %%os.atomicSymLink(self.builder.allocator, self.lib.major_only_filename, self.lib.name_only_filename); } } }; diff --git a/std/index.zig b/std/index.zig index dce1bdb7a5..26518d6de9 100644 --- a/std/index.zig +++ b/std/index.zig @@ -1,3 +1,4 @@ +pub const base64 = @import("base64.zig"); pub const build = @import("build.zig"); pub const c = @import("c/index.zig"); pub const cstr = @import("cstr.zig"); diff --git a/std/os/index.zig b/std/os/index.zig index 1f15d441ff..c154f59ee3 100644 --- a/std/os/index.zig +++ b/std/os/index.zig @@ -26,6 +26,7 @@ const BufMap = @import("../buf_map.zig").BufMap; const cstr = @import("../cstr.zig"); const io = @import("../io.zig"); +const base64 = @import("../base64.zig"); error Unexpected; error SystemResources; @@ -35,9 +36,11 @@ error FileSystem; error IsDir; error FileNotFound; error FileBusy; -error LinkPathAlreadyExists; +error PathAlreadyExists; error SymLinkLoop; error ReadOnlyFileSystem; +error LinkQuotaExceeded; +error RenameAcrossMountPoints; /// Fills `buf` with random bytes. If linking against libc, this calls the /// appropriate OS-specific library call. Otherwise it uses the zig standard @@ -197,7 +200,7 @@ pub fn posixDup2(old_fd: i32, new_fd: i32) -> %void { if (err > 0) { return switch (err) { errno.EBUSY, errno.EINTR => continue, - errno.EMFILE => error.SystemResources, + errno.EMFILE => error.ProcessFdQuotaExceeded, errno.EINVAL => unreachable, else => error.Unexpected, }; @@ -406,7 +409,7 @@ pub fn symLink(allocator: &Allocator, existing_path: []const u8, new_path: []con errno.EFAULT, errno.EINVAL => unreachable, errno.EACCES, errno.EPERM => error.AccessDenied, errno.EDQUOT => error.DiskQuota, - errno.EEXIST => error.LinkPathAlreadyExists, + errno.EEXIST => error.PathAlreadyExists, errno.EIO => error.FileSystem, errno.ELOOP => error.SymLinkLoop, errno.ENAMETOOLONG => error.NameTooLong, @@ -419,6 +422,38 @@ pub fn symLink(allocator: &Allocator, existing_path: []const u8, new_path: []con } } +// here we replace the standard +/ with -_ so that it can be used in a file name +const b64_fs_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_="; + +pub fn atomicSymLink(allocator: &Allocator, existing_path: []const u8, new_path: []const u8) -> %void { + try (symLink(allocator, existing_path, new_path)) { + return; + } else |err| { + if (err != error.PathAlreadyExists) { + return err; + } + } + + var rand_buf: [12]u8 = undefined; + const tmp_path = %return allocator.alloc(u8, new_path.len + base64.calcEncodedSize(rand_buf.len)); + defer allocator.free(tmp_path); + mem.copy(u8, tmp_path[0...], new_path); + while (true) { + %return getRandomBytes(rand_buf[0...]); + _ = base64.encodeWithAlphabet(tmp_path[new_path.len...], rand_buf, b64_fs_alphabet); + try (symLink(allocator, existing_path, tmp_path)) { + return rename(allocator, tmp_path, new_path); + } else |err| { + if (err == error.PathAlreadyExists) { + continue; + } else { + return err; + } + } + } + +} + pub fn deleteFile(allocator: &Allocator, file_path: []const u8) -> %void { const buf = %return allocator.alloc(u8, file_path.len + 1); defer allocator.free(buf); @@ -459,3 +494,37 @@ pub fn copyFile(allocator: &Allocator, source_path: []const u8, dest_path: []con return; } } + +pub fn rename(allocator: &Allocator, old_path: []const u8, new_path: []const u8) -> %void { + const full_buf = %return allocator.alloc(u8, old_path.len + new_path.len + 2); + defer allocator.free(full_buf); + + const old_buf = full_buf; + mem.copy(u8, old_buf, old_path); + old_buf[old_path.len] = 0; + + const new_buf = full_buf[old_path.len + 1...]; + mem.copy(u8, new_buf, new_path); + new_buf[new_path.len] = 0; + + const err = posix.getErrno(posix.rename(old_buf.ptr, new_buf.ptr)); + if (err > 0) { + return switch (err) { + errno.EACCES, errno.EPERM => error.AccessDenied, + errno.EBUSY => error.FileBusy, + errno.EDQUOT => error.DiskQuota, + errno.EFAULT, errno.EINVAL => unreachable, + errno.EISDIR => error.IsDir, + errno.ELOOP => error.SymLinkLoop, + errno.EMLINK => error.LinkQuotaExceeded, + errno.ENAMETOOLONG => error.NameTooLong, + errno.ENOENT, errno.ENOTDIR => error.FileNotFound, + errno.ENOMEM => error.SystemResources, + errno.ENOSPC => error.NoSpaceLeft, + errno.EEXIST, errno.ENOTEMPTY => error.PathAlreadyExists, + errno.EROFS => error.ReadOnlyFileSystem, + errno.EXDEV => error.RenameAcrossMountPoints, + else => error.Unexpected, + }; + } +} diff --git a/std/os/linux.zig b/std/os/linux.zig index 2214fb8dd1..d79b8f045e 100644 --- a/std/os/linux.zig +++ b/std/os/linux.zig @@ -311,6 +311,10 @@ pub fn pwrite(fd: i32, buf: &const u8, count: usize, offset: usize) -> usize { arch.syscall4(arch.SYS_pwrite, usize(fd), usize(buf), count, offset) } +pub fn rename(old: &const u8, new: &const u8) -> usize { + arch.syscall2(arch.SYS_rename, usize(old), usize(new)) +} + pub fn open(path: &const u8, flags: usize, perm: usize) -> usize { arch.syscall3(arch.SYS_open, usize(path), flags, perm) } |
