authorgravatar for cartersnook04@gmail.comCarter Snook <cartersnook04@gmail.com> 2024-06-12 20:39:18-05:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-07-28 20:08:52-07:00
log219acaa1d6ca4279085d7eb9449e7d0469a48432
tree8d25f1122c8a168dc37177f82028b56307ba0a0d
parent4a77c7f2588ce42c10df53fc1242f684ff506720

std.fs.Dir: Refactor atomicSymLink from std.fs

Deprecates std.fs.atomicSymLink and removes the allocator requirement from the new std.fs.Dir.atomicSymLink. Replaces the two usages of this within std. I did not include the TODOs from the original code that were based off of `switch (err) { ..., else => return err }` not having correct inference that cases handled in `...` are impossible in the error union return type because these are not specified in many places but I can add them back if wanted. Thank you @squeek502 for help with fixing buffer overflows!

3 files changed, 57 insertions(+), 38 deletions(-)

lib/std/Build/Step/Compile.zig+2-3
...@@ -1862,19 +1862,18 @@ pub fn doAtomicSymLinks(...@@ -1862,19 +1862,18 @@ pub fn doAtomicSymLinks(
1862 filename_name_only: []const u8,1862 filename_name_only: []const u8,
1863) !void {1863) !void {
1864 const b = step.owner;1864 const b = step.owner;
1865 const arena = b.allocator;
1866 const out_dir = fs.path.dirname(output_path) orelse ".";1865 const out_dir = fs.path.dirname(output_path) orelse ".";
1867 const out_basename = fs.path.basename(output_path);1866 const out_basename = fs.path.basename(output_path);
1868 // sym link for libfoo.so.1 to libfoo.so.1.2.31867 // sym link for libfoo.so.1 to libfoo.so.1.2.3
1869 const major_only_path = b.pathJoin(&.{ out_dir, filename_major_only });1868 const major_only_path = b.pathJoin(&.{ out_dir, filename_major_only });
1870 fs.atomicSymLink(arena, out_basename, major_only_path) catch |err| {1869 fs.cwd().atomicSymLink(out_basename, major_only_path, .{}) catch |err| {
1871 return step.fail("unable to symlink {s} -> {s}: {s}", .{1870 return step.fail("unable to symlink {s} -> {s}: {s}", .{
1872 major_only_path, out_basename, @errorName(err),1871 major_only_path, out_basename, @errorName(err),
1873 });1872 });
1874 };1873 };
1875 // sym link for libfoo.so to libfoo.so.11874 // sym link for libfoo.so to libfoo.so.1
1876 const name_only_path = b.pathJoin(&.{ out_dir, filename_name_only });1875 const name_only_path = b.pathJoin(&.{ out_dir, filename_name_only });
1877 fs.atomicSymLink(arena, filename_major_only, name_only_path) catch |err| {1876 fs.cwd().atomicSymLink(filename_major_only, name_only_path, .{}) catch |err| {
1878 return step.fail("Unable to symlink {s} -> {s}: {s}", .{1877 return step.fail("Unable to symlink {s} -> {s}: {s}", .{
1879 name_only_path, filename_major_only, @errorName(err),1878 name_only_path, filename_major_only, @errorName(err),
1880 });1879 });
lib/std/fs.zig+3-31
...@@ -101,37 +101,9 @@ pub const base64_encoder = base64.Base64Encoder.init(base64_alphabet, null);...@@ -101,37 +101,9 @@ pub const base64_encoder = base64.Base64Encoder.init(base64_alphabet, null);
101/// Base64 decoder, replacing the standard `+/` with `-_` so that it can be used in a file name on any filesystem.101/// Base64 decoder, replacing the standard `+/` with `-_` so that it can be used in a file name on any filesystem.
102pub const base64_decoder = base64.Base64Decoder.init(base64_alphabet, null);102pub const base64_decoder = base64.Base64Decoder.init(base64_alphabet, null);
103103
104/// TODO remove the allocator requirement from this API104/// Deprecated. Use `cwd().atomicSymLink()` instead.
105/// TODO move to Dir105pub fn atomicSymLink(_: Allocator, existing_path: []const u8, new_path: []const u8) !void {
106/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).106 try cwd().atomicSymLink(existing_path, new_path, .{});
107/// On WASI, both paths should be encoded as valid UTF-8.
108/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
109pub fn atomicSymLink(allocator: Allocator, existing_path: []const u8, new_path: []const u8) !void {
110 if (cwd().symLink(existing_path, new_path, .{})) {
111 return;
112 } else |err| switch (err) {
113 error.PathAlreadyExists => {},
114 else => return err, // TODO zig should know this set does not include PathAlreadyExists
115 }
116
117 const dirname = path.dirname(new_path) orelse ".";
118
119 var rand_buf: [AtomicFile.random_bytes_len]u8 = undefined;
120 const tmp_path = try allocator.alloc(u8, dirname.len + 1 + base64_encoder.calcSize(rand_buf.len));
121 defer allocator.free(tmp_path);
122 @memcpy(tmp_path[0..dirname.len], dirname);
123 tmp_path[dirname.len] = path.sep;
124 while (true) {
125 crypto.random.bytes(rand_buf[0..]);
126 _ = base64_encoder.encode(tmp_path[dirname.len + 1 ..], &rand_buf);
127
128 if (cwd().symLink(existing_path, tmp_path, .{})) {
129 return cwd().rename(tmp_path, new_path);
130 } else |err| switch (err) {
131 error.PathAlreadyExists => continue,
132 else => return err, // TODO zig should know this set does not include PathAlreadyExists
133 }
134 }
135}107}
136108
137/// Same as `Dir.updateFile`, except asserts that both `source_path` and `dest_path`109/// Same as `Dir.updateFile`, except asserts that both `source_path` and `dest_path`
lib/std/fs/Dir.zig+52-4
...@@ -1758,10 +1758,11 @@ pub fn renameW(self: Dir, old_sub_path_w: []const u16, new_sub_path_w: []const u...@@ -1758,10 +1758,11 @@ pub fn renameW(self: Dir, old_sub_path_w: []const u16, new_sub_path_w: []const u
1758 return posix.renameatW(self.fd, old_sub_path_w, self.fd, new_sub_path_w);1758 return posix.renameatW(self.fd, old_sub_path_w, self.fd, new_sub_path_w);
1759}1759}
17601760
1761/// Use with `Dir.symLink` and `symLinkAbsolute` to specify whether the symlink1761/// Use with `Dir.symLink`, `Dir.atomicSymLink`, and `symLinkAbsolute` to
1762/// will point to a file or a directory. This value is ignored on all hosts1762/// specify whether the symlink will point to a file or a directory. This value
1763/// except Windows where creating symlinks to different resource types, requires1763/// is ignored on all hosts except Windows where creating symlinks to different
1764/// different flags. By default, `symLinkAbsolute` is assumed to point to a file.1764/// resource types, requires different flags. By default, `symLinkAbsolute` is
1765/// assumed to point to a file.
1765pub const SymLinkFlags = struct {1766pub const SymLinkFlags = struct {
1766 is_directory: bool = false,1767 is_directory: bool = false,
1767};1768};
...@@ -1847,6 +1848,50 @@ pub fn symLinkW(...@@ -1847,6 +1848,50 @@ pub fn symLinkW(
1847 return windows.CreateSymbolicLink(self.fd, sym_link_path_w, target_path_w, flags.is_directory);1848 return windows.CreateSymbolicLink(self.fd, sym_link_path_w, target_path_w, flags.is_directory);
1848}1849}
18491850
1851/// Same as `symLink`, except tries to create the symbolic link until it
1852/// succeeds or encounters an error other than `error.PathAlreadyExists`.
1853/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1854/// On WASI, both paths should be encoded as valid UTF-8.
1855/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
1856pub fn atomicSymLink(
1857 dir: Dir,
1858 target_path: []const u8,
1859 sym_link_path: []const u8,
1860 flags: SymLinkFlags,
1861) !void {
1862 if (dir.symLink(target_path, sym_link_path, flags)) {
1863 return;
1864 } else |err| switch (err) {
1865 error.PathAlreadyExists => {},
1866 else => |e| return e,
1867 }
1868
1869 const dirname = path.dirname(sym_link_path) orelse ".";
1870
1871 var rand_buf: [AtomicFile.random_bytes_len]u8 = undefined;
1872
1873 const temp_path_len = dirname.len + 1 + base64_encoder.calcSize(rand_buf.len);
1874 var temp_path_buf: [fs.max_path_bytes]u8 = undefined;
1875
1876 if (temp_path_len > temp_path_buf.len) return error.NameTooLong;
1877 @memcpy(temp_path_buf[0..dirname.len], dirname);
1878 temp_path_buf[dirname.len] = path.sep;
1879
1880 const temp_path = temp_path_buf[0..temp_path_len];
1881
1882 while (true) {
1883 crypto.random.bytes(rand_buf[0..]);
1884 _ = base64_encoder.encode(temp_path[dirname.len + 1 ..], rand_buf[0..]);
1885
1886 if (dir.symLink(target_path, temp_path, flags)) {
1887 return dir.rename(temp_path, sym_link_path);
1888 } else |err| switch (err) {
1889 error.PathAlreadyExists => continue,
1890 else => |e| return e,
1891 }
1892 }
1893}
1894
1850pub const ReadLinkError = posix.ReadLinkError;1895pub const ReadLinkError = posix.ReadLinkError;
18511896
1852/// Read value of a symbolic link.1897/// Read value of a symbolic link.
...@@ -2695,8 +2740,11 @@ const builtin = @import("builtin");...@@ -2695,8 +2740,11 @@ const builtin = @import("builtin");
2695const std = @import("../std.zig");2740const std = @import("../std.zig");
2696const File = std.fs.File;2741const File = std.fs.File;
2697const AtomicFile = std.fs.AtomicFile;2742const AtomicFile = std.fs.AtomicFile;
2743const base64_encoder = fs.base64_encoder;
2744const crypto = std.crypto;
2698const posix = std.posix;2745const posix = std.posix;
2699const mem = std.mem;2746const mem = std.mem;
2747const path = fs.path;
2700const fs = std.fs;2748const fs = std.fs;
2701const Allocator = std.mem.Allocator;2749const Allocator = std.mem.Allocator;
2702const assert = std.debug.assert;2750const assert = std.debug.assert;