authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2017-04-17 19:08:41-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2017-04-17 19:08:41-04:00
log216e14891ea5fa1a88804d9781ef779d448d1220
tree8cfa1b656aaa6b6c01bdba7c5b7aedb5916f5088
parent401eed8153d909eda4146b5a1815dee7130cf1c3

zig build system creates symlinks atomically

* add std.base64 * add std.os.rename * add std.os.atomicSymLink

6 files changed, 266 insertions(+), 11 deletions(-)

CMakeLists.txt+1
...@@ -204,6 +204,7 @@ install(TARGETS zig DESTINATION bin)...@@ -204,6 +204,7 @@ install(TARGETS zig DESTINATION bin)
204204
205install(FILES ${C_HEADERS} DESTINATION ${C_HEADERS_DEST})205install(FILES ${C_HEADERS} DESTINATION ${C_HEADERS_DEST})
206206
207install(FILES "${CMAKE_SOURCE_DIR}/std/base64.zig" DESTINATION "${ZIG_STD_DEST}")
207install(FILES "${CMAKE_SOURCE_DIR}/std/buf_map.zig" DESTINATION "${ZIG_STD_DEST}")208install(FILES "${CMAKE_SOURCE_DIR}/std/buf_map.zig" DESTINATION "${ZIG_STD_DEST}")
208install(FILES "${CMAKE_SOURCE_DIR}/std/buf_set.zig" DESTINATION "${ZIG_STD_DEST}")209install(FILES "${CMAKE_SOURCE_DIR}/std/buf_set.zig" DESTINATION "${ZIG_STD_DEST}")
209install(FILES "${CMAKE_SOURCE_DIR}/std/build.zig" DESTINATION "${ZIG_STD_DEST}")210install(FILES "${CMAKE_SOURCE_DIR}/std/build.zig" DESTINATION "${ZIG_STD_DEST}")
std/base64.zig created+184
...@@ -0,0 +1,184 @@
1const assert = @import("debug.zig").assert;
2const mem = @import("mem.zig");
3
4pub const standard_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
5
6pub fn encode(dest: []u8, source: []const u8) -> []u8 {
7 return encodeWithAlphabet(dest, source, standard_alphabet);
8}
9
10pub fn decode(dest: []u8, source: []const u8) -> []u8 {
11 return decodeWithAlphabet(dest, source, standard_alphabet);
12}
13
14pub fn encodeWithAlphabet(dest: []u8, source: []const u8, alphabet: []const u8) -> []u8 {
15 assert(alphabet.len == 65);
16 assert(dest.len >= calcEncodedSize(source.len));
17
18 var i: usize = 0;
19 var out_index: usize = 0;
20 while (i + 2 < source.len; i += 3) {
21 dest[out_index] = alphabet[(source[i] >> 2) & 0x3f];
22 out_index += 1;
23
24 dest[out_index] = alphabet[((source[i] & 0x3) <<% 4) |
25 ((source[i + 1] & 0xf0) >> 4)];
26 out_index += 1;
27
28 dest[out_index] = alphabet[((source[i + 1] & 0xf) <<% 2) |
29 ((source[i + 2] & 0xc0) >> 6)];
30 out_index += 1;
31
32 dest[out_index] = alphabet[source[i + 2] & 0x3f];
33 out_index += 1;
34 }
35
36 if (i < source.len) {
37 dest[out_index] = alphabet[(source[i] >> 2) & 0x3f];
38 out_index += 1;
39
40 if (i + 1 == source.len) {
41 dest[out_index] = alphabet[(source[i] & 0x3) <<% 4];
42 out_index += 1;
43
44 dest[out_index] = alphabet[64];
45 out_index += 1;
46 } else {
47 dest[out_index] = alphabet[((source[i] & 0x3) <<% 4) |
48 ((source[i + 1] & 0xf0) >> 4)];
49 out_index += 1;
50
51 dest[out_index] = alphabet[(source[i + 1] & 0xf) <<% 2];
52 out_index += 1;
53 }
54
55 dest[out_index] = alphabet[64];
56 out_index += 1;
57 }
58
59 return dest[0...out_index];
60}
61
62pub fn decodeWithAlphabet(dest: []u8, source: []const u8, alphabet: []const u8) -> []u8 {
63 assert(alphabet.len == 65);
64
65 var ascii6 = []u8{64} ** 256;
66 for (alphabet) |c, i| {
67 ascii6[c] = u8(i);
68 }
69
70 return decodeWithAscii6BitMap(dest, source, ascii6[0...], alphabet[64]);
71}
72
73pub fn decodeWithAscii6BitMap(dest: []u8, source: []const u8, ascii6: []const u8, pad_char: u8) -> []u8 {
74 assert(ascii6.len == 256);
75 assert(dest.len >= calcExactDecodedSizeWithPadChar(source, pad_char));
76
77 var src_index: usize = 0;
78 var dest_index: usize = 0;
79 var in_buf_len: usize = source.len;
80
81 while (in_buf_len > 0 and source[in_buf_len - 1] == pad_char) {
82 in_buf_len -= 1;
83 }
84
85 while (in_buf_len > 4) {
86 dest[dest_index] = ascii6[source[src_index + 0]] <<% 2 |
87 ascii6[source[src_index + 1]] >> 4;
88 dest_index += 1;
89
90 dest[dest_index] = ascii6[source[src_index + 1]] <<% 4 |
91 ascii6[source[src_index + 2]] >> 2;
92 dest_index += 1;
93
94 dest[dest_index] = ascii6[source[src_index + 2]] <<% 6 |
95 ascii6[source[src_index + 3]];
96 dest_index += 1;
97
98 src_index += 4;
99 in_buf_len -= 4;
100 }
101
102 if (in_buf_len > 1) {
103 dest[dest_index] = ascii6[source[src_index + 0]] <<% 2 |
104 ascii6[source[src_index + 1]] >> 4;
105 dest_index += 1;
106 }
107 if (in_buf_len > 2) {
108 dest[dest_index] = ascii6[source[src_index + 1]] <<% 4 |
109 ascii6[source[src_index + 2]] >> 2;
110 dest_index += 1;
111 }
112 if (in_buf_len > 3) {
113 dest[dest_index] = ascii6[source[src_index + 2]] <<% 6 |
114 ascii6[source[src_index + 3]];
115 dest_index += 1;
116 }
117
118 return dest[0...dest_index];
119}
120
121pub fn calcEncodedSize(source_len: usize) -> usize {
122 return (((source_len * 4) / 3 + 3) / 4) * 4;
123}
124
125/// Computes the upper bound of the decoded size based only on the encoded length.
126/// To compute the exact decoded size, see ::calcExactDecodedSize
127pub fn calcMaxDecodedSize(encoded_len: usize) -> usize {
128 return @divExact(encoded_len * 3, 4);
129}
130
131/// Computes the number of decoded bytes there will be. This function must
132/// be given the encoded buffer because there might be padding
133/// bytes at the end ('=' in the standard alphabet)
134pub fn calcExactDecodedSize(encoded: []const u8) -> usize {
135 return calcExactDecodedSizeWithAlphabet(encoded, standard_alphabet);
136}
137
138pub fn calcExactDecodedSizeWithAlphabet(encoded: []const u8, alphabet: []const u8) -> usize {
139 assert(alphabet.len == 65);
140 return calcExactDecodedSizeWithPadChar(encoded, alphabet[64]);
141}
142
143pub fn calcExactDecodedSizeWithPadChar(encoded: []const u8, pad_char: u8) -> usize {
144 var buf_len = encoded.len;
145
146 while (buf_len > 0 and encoded[buf_len - 1] == pad_char) {
147 buf_len -= 1;
148 }
149
150 return (buf_len * 3) / 4;
151}
152
153test "base64" {
154 testBase64();
155 comptime testBase64();
156}
157
158fn testBase64() {
159 testBase64Case("", "");
160 testBase64Case("f", "Zg==");
161 testBase64Case("fo", "Zm8=");
162 testBase64Case("foo", "Zm9v");
163 testBase64Case("foob", "Zm9vYg==");
164 testBase64Case("fooba", "Zm9vYmE=");
165 testBase64Case("foobar", "Zm9vYmFy");
166}
167
168fn testBase64Case(expected_decoded: []const u8, expected_encoded: []const u8) {
169 const calculated_decoded_len = calcExactDecodedSize(expected_encoded);
170 assert(calculated_decoded_len == expected_decoded.len);
171
172 const calculated_encoded_len = calcEncodedSize(expected_decoded.len);
173 assert(calculated_encoded_len == expected_encoded.len);
174
175 var buf: [100]u8 = undefined;
176
177 const actual_decoded = decode(buf[0...], expected_encoded);
178 assert(actual_decoded.len == expected_decoded.len);
179 assert(mem.eql(u8, expected_decoded, actual_decoded));
180
181 const actual_encoded = encode(buf[0...], expected_decoded);
182 assert(actual_encoded.len == expected_encoded.len);
183 assert(mem.eql(u8, expected_encoded, actual_encoded));
184}
std/build.zig+4-8
...@@ -816,11 +816,9 @@ const CLibrary = struct {...@@ -816,11 +816,9 @@ const CLibrary = struct {
816 builder.spawnChild(cc, cc_args.toSliceConst());816 builder.spawnChild(cc, cc_args.toSliceConst());
817817
818 // sym link for libfoo.so.1 to libfoo.so.1.2.3818 // sym link for libfoo.so.1 to libfoo.so.1.2.3
819 _ = os.deleteFile(builder.allocator, self.major_only_filename);819 %%os.atomicSymLink(builder.allocator, self.out_filename, self.major_only_filename);
820 %%os.symLink(builder.allocator, self.out_filename, self.major_only_filename);
821 // sym link for libfoo.so to libfoo.so.1820 // sym link for libfoo.so to libfoo.so.1
822 _ = os.deleteFile(builder.allocator, self.name_only_filename);821 %%os.atomicSymLink(builder.allocator, self.major_only_filename, self.name_only_filename);
823 %%os.symLink(builder.allocator, self.major_only_filename, self.name_only_filename);
824 }822 }
825 }823 }
826824
...@@ -1029,10 +1027,8 @@ const InstallCLibraryStep = struct {...@@ -1029,10 +1027,8 @@ const InstallCLibraryStep = struct {
10291027
1030 self.builder.copyFile(self.lib.out_filename, self.dest_file);1028 self.builder.copyFile(self.lib.out_filename, self.dest_file);
1031 if (!self.lib.static) {1029 if (!self.lib.static) {
1032 _ = os.deleteFile(self.builder.allocator, self.lib.major_only_filename);1030 %%os.atomicSymLink(self.builder.allocator, self.lib.out_filename, self.lib.major_only_filename);
1033 %%os.symLink(self.builder.allocator, self.lib.out_filename, self.lib.major_only_filename);1031 %%os.atomicSymLink(self.builder.allocator, self.lib.major_only_filename, self.lib.name_only_filename);
1034 _ = os.deleteFile(self.builder.allocator, self.lib.name_only_filename);
1035 %%os.symLink(self.builder.allocator, self.lib.major_only_filename, self.lib.name_only_filename);
1036 }1032 }
1037 }1033 }
1038};1034};
std/index.zig+1
...@@ -1,3 +1,4 @@...@@ -1,3 +1,4 @@
1pub const base64 = @import("base64.zig");
1pub const build = @import("build.zig");2pub const build = @import("build.zig");
2pub const c = @import("c/index.zig");3pub const c = @import("c/index.zig");
3pub const cstr = @import("cstr.zig");4pub const cstr = @import("cstr.zig");
std/os/index.zig+72-3
...@@ -26,6 +26,7 @@ const BufMap = @import("../buf_map.zig").BufMap;...@@ -26,6 +26,7 @@ const BufMap = @import("../buf_map.zig").BufMap;
26const cstr = @import("../cstr.zig");26const cstr = @import("../cstr.zig");
2727
28const io = @import("../io.zig");28const io = @import("../io.zig");
29const base64 = @import("../base64.zig");
2930
30error Unexpected;31error Unexpected;
31error SystemResources;32error SystemResources;
...@@ -35,9 +36,11 @@ error FileSystem;...@@ -35,9 +36,11 @@ error FileSystem;
35error IsDir;36error IsDir;
36error FileNotFound;37error FileNotFound;
37error FileBusy;38error FileBusy;
38error LinkPathAlreadyExists;39error PathAlreadyExists;
39error SymLinkLoop;40error SymLinkLoop;
40error ReadOnlyFileSystem;41error ReadOnlyFileSystem;
42error LinkQuotaExceeded;
43error RenameAcrossMountPoints;
4144
42/// Fills `buf` with random bytes. If linking against libc, this calls the45/// Fills `buf` with random bytes. If linking against libc, this calls the
43/// appropriate OS-specific library call. Otherwise it uses the zig standard46/// 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 {...@@ -197,7 +200,7 @@ pub fn posixDup2(old_fd: i32, new_fd: i32) -> %void {
197 if (err > 0) {200 if (err > 0) {
198 return switch (err) {201 return switch (err) {
199 errno.EBUSY, errno.EINTR => continue,202 errno.EBUSY, errno.EINTR => continue,
200 errno.EMFILE => error.SystemResources,203 errno.EMFILE => error.ProcessFdQuotaExceeded,
201 errno.EINVAL => unreachable,204 errno.EINVAL => unreachable,
202 else => error.Unexpected,205 else => error.Unexpected,
203 };206 };
...@@ -406,7 +409,7 @@ pub fn symLink(allocator: &Allocator, existing_path: []const u8, new_path: []con...@@ -406,7 +409,7 @@ pub fn symLink(allocator: &Allocator, existing_path: []const u8, new_path: []con
406 errno.EFAULT, errno.EINVAL => unreachable,409 errno.EFAULT, errno.EINVAL => unreachable,
407 errno.EACCES, errno.EPERM => error.AccessDenied,410 errno.EACCES, errno.EPERM => error.AccessDenied,
408 errno.EDQUOT => error.DiskQuota,411 errno.EDQUOT => error.DiskQuota,
409 errno.EEXIST => error.LinkPathAlreadyExists,412 errno.EEXIST => error.PathAlreadyExists,
410 errno.EIO => error.FileSystem,413 errno.EIO => error.FileSystem,
411 errno.ELOOP => error.SymLinkLoop,414 errno.ELOOP => error.SymLinkLoop,
412 errno.ENAMETOOLONG => error.NameTooLong,415 errno.ENAMETOOLONG => error.NameTooLong,
...@@ -419,6 +422,38 @@ pub fn symLink(allocator: &Allocator, existing_path: []const u8, new_path: []con...@@ -419,6 +422,38 @@ pub fn symLink(allocator: &Allocator, existing_path: []const u8, new_path: []con
419 }422 }
420}423}
421424
425// here we replace the standard +/ with -_ so that it can be used in a file name
426const b64_fs_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=";
427
428pub fn atomicSymLink(allocator: &Allocator, existing_path: []const u8, new_path: []const u8) -> %void {
429 try (symLink(allocator, existing_path, new_path)) {
430 return;
431 } else |err| {
432 if (err != error.PathAlreadyExists) {
433 return err;
434 }
435 }
436
437 var rand_buf: [12]u8 = undefined;
438 const tmp_path = %return allocator.alloc(u8, new_path.len + base64.calcEncodedSize(rand_buf.len));
439 defer allocator.free(tmp_path);
440 mem.copy(u8, tmp_path[0...], new_path);
441 while (true) {
442 %return getRandomBytes(rand_buf[0...]);
443 _ = base64.encodeWithAlphabet(tmp_path[new_path.len...], rand_buf, b64_fs_alphabet);
444 try (symLink(allocator, existing_path, tmp_path)) {
445 return rename(allocator, tmp_path, new_path);
446 } else |err| {
447 if (err == error.PathAlreadyExists) {
448 continue;
449 } else {
450 return err;
451 }
452 }
453 }
454
455}
456
422pub fn deleteFile(allocator: &Allocator, file_path: []const u8) -> %void {457pub fn deleteFile(allocator: &Allocator, file_path: []const u8) -> %void {
423 const buf = %return allocator.alloc(u8, file_path.len + 1);458 const buf = %return allocator.alloc(u8, file_path.len + 1);
424 defer allocator.free(buf);459 defer allocator.free(buf);
...@@ -459,3 +494,37 @@ pub fn copyFile(allocator: &Allocator, source_path: []const u8, dest_path: []con...@@ -459,3 +494,37 @@ pub fn copyFile(allocator: &Allocator, source_path: []const u8, dest_path: []con
459 return;494 return;
460 }495 }
461}496}
497
498pub fn rename(allocator: &Allocator, old_path: []const u8, new_path: []const u8) -> %void {
499 const full_buf = %return allocator.alloc(u8, old_path.len + new_path.len + 2);
500 defer allocator.free(full_buf);
501
502 const old_buf = full_buf;
503 mem.copy(u8, old_buf, old_path);
504 old_buf[old_path.len] = 0;
505
506 const new_buf = full_buf[old_path.len + 1...];
507 mem.copy(u8, new_buf, new_path);
508 new_buf[new_path.len] = 0;
509
510 const err = posix.getErrno(posix.rename(old_buf.ptr, new_buf.ptr));
511 if (err > 0) {
512 return switch (err) {
513 errno.EACCES, errno.EPERM => error.AccessDenied,
514 errno.EBUSY => error.FileBusy,
515 errno.EDQUOT => error.DiskQuota,
516 errno.EFAULT, errno.EINVAL => unreachable,
517 errno.EISDIR => error.IsDir,
518 errno.ELOOP => error.SymLinkLoop,
519 errno.EMLINK => error.LinkQuotaExceeded,
520 errno.ENAMETOOLONG => error.NameTooLong,
521 errno.ENOENT, errno.ENOTDIR => error.FileNotFound,
522 errno.ENOMEM => error.SystemResources,
523 errno.ENOSPC => error.NoSpaceLeft,
524 errno.EEXIST, errno.ENOTEMPTY => error.PathAlreadyExists,
525 errno.EROFS => error.ReadOnlyFileSystem,
526 errno.EXDEV => error.RenameAcrossMountPoints,
527 else => error.Unexpected,
528 };
529 }
530}
std/os/linux.zig+4
...@@ -311,6 +311,10 @@ pub fn pwrite(fd: i32, buf: &const u8, count: usize, offset: usize) -> usize {...@@ -311,6 +311,10 @@ pub fn pwrite(fd: i32, buf: &const u8, count: usize, offset: usize) -> usize {
311 arch.syscall4(arch.SYS_pwrite, usize(fd), usize(buf), count, offset)311 arch.syscall4(arch.SYS_pwrite, usize(fd), usize(buf), count, offset)
312}312}
313313
314pub fn rename(old: &const u8, new: &const u8) -> usize {
315 arch.syscall2(arch.SYS_rename, usize(old), usize(new))
316}
317
314pub fn open(path: &const u8, flags: usize, perm: usize) -> usize {318pub fn open(path: &const u8, flags: usize, perm: usize) -> usize {
315 arch.syscall3(arch.SYS_open, usize(path), flags, perm)319 arch.syscall3(arch.SYS_open, usize(path), flags, perm)
316}320}