aboutsummaryrefslogtreecommitdiff
path: root/lib/std
diff options
context:
space:
mode:
authorAndrew Kelley <andrew@ziglang.org>2020-09-21 21:16:46 -0700
committerAndrew Kelley <andrew@ziglang.org>2020-09-21 21:16:46 -0700
commit0c70bb4fce8b0460f86ed218f54ba31b291f2bfb (patch)
tree1e0ad8f9ea76b30e52753a2f25f5f1d18d25896d /lib/std
parentafac5d28951cfd913851094649e8b9f2136694ca (diff)
parent58ee5f4e61cd9b7a9ba65798e2214efa3753a733 (diff)
downloadzig-0c70bb4fce8b0460f86ed218f54ba31b291f2bfb.tar.gz
zig-0c70bb4fce8b0460f86ed218f54ba31b291f2bfb.zip
Merge remote-tracking branch 'origin/master' into stage2-zig-cc
Diffstat (limited to 'lib/std')
-rw-r--r--lib/std/build.zig2
-rw-r--r--lib/std/crypto.zig24
-rw-r--r--lib/std/crypto/pbkdf2.zig280
-rw-r--r--lib/std/crypto/siphash.zig3
-rw-r--r--lib/std/fmt.zig139
-rw-r--r--lib/std/fs.zig66
-rw-r--r--lib/std/fs/test.zig161
-rw-r--r--lib/std/heap.zig2
-rw-r--r--lib/std/os.zig3
-rw-r--r--lib/std/os/windows.zig3
10 files changed, 591 insertions, 92 deletions
diff --git a/lib/std/build.zig b/lib/std/build.zig
index 30ecf98b76..69f44bad32 100644
--- a/lib/std/build.zig
+++ b/lib/std/build.zig
@@ -1188,6 +1188,7 @@ pub const LibExeObjStep = struct {
emit_llvm_ir: bool = false,
emit_asm: bool = false,
emit_bin: bool = true,
+ emit_docs: bool = false,
emit_h: bool = false,
bundle_compiler_rt: bool,
disable_stack_probing: bool,
@@ -2033,6 +2034,7 @@ pub const LibExeObjStep = struct {
if (self.emit_llvm_ir) try zig_args.append("-femit-llvm-ir");
if (self.emit_asm) try zig_args.append("-femit-asm");
if (!self.emit_bin) try zig_args.append("-fno-emit-bin");
+ if (self.emit_docs) try zig_args.append("-femit-docs");
if (self.emit_h) try zig_args.append("-femit-h");
if (self.strip) {
diff --git a/lib/std/crypto.zig b/lib/std/crypto.zig
index 5de2f13896..3a1ae599a0 100644
--- a/lib/std/crypto.zig
+++ b/lib/std/crypto.zig
@@ -35,6 +35,15 @@ pub const onetimeauth = struct {
pub const Poly1305 = @import("crypto/poly1305.zig").Poly1305;
};
+/// A Key Derivation Function (KDF) is intended to turn a weak, human generated password into a
+/// strong key, suitable for cryptographic uses. It does this by salting and stretching the
+/// password. Salting injects non-secret random data, so that identical passwords will be converted
+/// into unique keys. Stretching applies a deliberately slow hashing function to frustrate
+/// brute-force guessing.
+pub const kdf = struct {
+ pub const pbkdf2 = @import("crypto/pbkdf2.zig").pbkdf2;
+};
+
/// Core functions, that should rarely be used directly by applications.
pub const core = struct {
pub const aes = @import("crypto/aes.zig");
@@ -70,6 +79,20 @@ const std = @import("std.zig");
pub const randomBytes = std.os.getrandom;
test "crypto" {
+ inline for (std.meta.declarations(@This())) |decl| {
+ switch (decl.data) {
+ .Type => |t| {
+ std.meta.refAllDecls(t);
+ },
+ .Var => |v| {
+ _ = v;
+ },
+ .Fn => |f| {
+ _ = f;
+ },
+ }
+ }
+
_ = @import("crypto/aes.zig");
_ = @import("crypto/blake2.zig");
_ = @import("crypto/blake3.zig");
@@ -77,6 +100,7 @@ test "crypto" {
_ = @import("crypto/gimli.zig");
_ = @import("crypto/hmac.zig");
_ = @import("crypto/md5.zig");
+ _ = @import("crypto/pbkdf2.zig");
_ = @import("crypto/poly1305.zig");
_ = @import("crypto/sha1.zig");
_ = @import("crypto/sha2.zig");
diff --git a/lib/std/crypto/pbkdf2.zig b/lib/std/crypto/pbkdf2.zig
new file mode 100644
index 0000000000..85c8e01105
--- /dev/null
+++ b/lib/std/crypto/pbkdf2.zig
@@ -0,0 +1,280 @@
+// SPDX-License-Identifier: MIT
+// Copyright (c) 2015-2020 Zig Contributors
+// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
+// The MIT license requires this copyright notice to be included in all copies
+// and substantial portions of the software.
+
+const std = @import("std");
+const mem = std.mem;
+const maxInt = std.math.maxInt;
+
+// RFC 2898 Section 5.2
+//
+// FromSpec:
+//
+// PBKDF2 applies a pseudorandom function (see Appendix B.1 for an
+// example) to derive keys. The length of the derived key is essentially
+// unbounded. (However, the maximum effective search space for the
+// derived key may be limited by the structure of the underlying
+// pseudorandom function. See Appendix B.1 for further discussion.)
+// PBKDF2 is recommended for new applications.
+//
+// PBKDF2 (P, S, c, dkLen)
+//
+// Options: PRF underlying pseudorandom function (hLen
+// denotes the length in octets of the
+// pseudorandom function output)
+//
+// Input: P password, an octet string
+// S salt, an octet string
+// c iteration count, a positive integer
+// dkLen intended length in octets of the derived
+// key, a positive integer, at most
+// (2^32 - 1) * hLen
+//
+// Output: DK derived key, a dkLen-octet string
+
+// Based on Apple's CommonKeyDerivation, based originally on code by Damien Bergamini.
+
+pub const Pbkdf2Error = error{
+ /// At least one round is required
+ TooFewRounds,
+
+ /// Maximum length of the derived key is `maxInt(u32) * Prf.mac_length`
+ DerivedKeyTooLong,
+};
+
+/// Apply PBKDF2 to generate a key from a password.
+///
+/// PBKDF2 is defined in RFC 2898, and is a recommendation of NIST SP 800-132.
+///
+/// derivedKey: Slice of appropriate size for generated key. Generally 16 or 32 bytes in length.
+/// May be uninitialized. All bytes will be overwritten.
+/// Maximum size is `maxInt(u32) * Hash.digest_length`
+/// It is a programming error to pass buffer longer than the maximum size.
+///
+/// password: Arbitrary sequence of bytes of any length, including empty.
+///
+/// salt: Arbitrary sequence of bytes of any length, including empty. A common length is 8 bytes.
+///
+/// rounds: Iteration count. Must be greater than 0. Common values range from 1,000 to 100,000.
+/// Larger iteration counts improve security by increasing the time required to compute
+/// the derivedKey. It is common to tune this parameter to achieve approximately 100ms.
+///
+/// Prf: Pseudo-random function to use. A common choice is `std.crypto.auth.hmac.HmacSha256`.
+pub fn pbkdf2(derivedKey: []u8, password: []const u8, salt: []const u8, rounds: u32, comptime Prf: type) Pbkdf2Error!void {
+ if (rounds < 1) return error.TooFewRounds;
+
+ const dkLen = derivedKey.len;
+ const hLen = Prf.mac_length;
+ comptime std.debug.assert(hLen >= 1);
+
+ // FromSpec:
+ //
+ // 1. If dkLen > maxInt(u32) * hLen, output "derived key too long" and
+ // stop.
+ //
+ if (comptime (maxInt(usize) > maxInt(u32) * hLen) and (dkLen > @as(usize, maxInt(u32) * hLen))) {
+ // If maxInt(usize) is less than `maxInt(u32) * hLen` then dkLen is always inbounds
+ return error.DerivedKeyTooLong;
+ }
+
+ // FromSpec:
+ //
+ // 2. Let l be the number of hLen-long blocks of bytes in the derived key,
+ // rounding up, and let r be the number of bytes in the last
+ // block
+ //
+
+ // l will not overflow, proof:
+ // let `L(dkLen, hLen) = (dkLen + hLen - 1) / hLen`
+ // then `L^-1(l, hLen) = l*hLen - hLen + 1`
+ // 1) L^-1(maxInt(u32), hLen) <= maxInt(u32)*hLen
+ // 2) maxInt(u32)*hLen - hLen + 1 <= maxInt(u32)*hLen // subtract maxInt(u32)*hLen + 1
+ // 3) -hLen <= -1 // multiply by -1
+ // 4) hLen >= 1
+ const r_ = dkLen % hLen;
+ const l = @intCast(u32, (dkLen / hLen) + @as(u1, if (r_ == 0) 0 else 1)); // original: (dkLen + hLen - 1) / hLen
+ const r = if (r_ == 0) hLen else r_;
+
+ // FromSpec:
+ //
+ // 3. For each block of the derived key apply the function F defined
+ // below to the password P, the salt S, the iteration count c, and
+ // the block index to compute the block:
+ //
+ // T_1 = F (P, S, c, 1) ,
+ // T_2 = F (P, S, c, 2) ,
+ // ...
+ // T_l = F (P, S, c, l) ,
+ //
+ // where the function F is defined as the exclusive-or sum of the
+ // first c iterates of the underlying pseudorandom function PRF
+ // applied to the password P and the concatenation of the salt S
+ // and the block index i:
+ //
+ // F (P, S, c, i) = U_1 \xor U_2 \xor ... \xor U_c
+ //
+ // where
+ //
+ // U_1 = PRF (P, S || INT (i)) ,
+ // U_2 = PRF (P, U_1) ,
+ // ...
+ // U_c = PRF (P, U_{c-1}) .
+ //
+ // Here, INT (i) is a four-octet encoding of the integer i, most
+ // significant octet first.
+ //
+ // 4. Concatenate the blocks and extract the first dkLen octets to
+ // produce a derived key DK:
+ //
+ // DK = T_1 || T_2 || ... || T_l<0..r-1>
+ var block: u32 = 0; // Spec limits to u32
+ while (block < l) : (block += 1) {
+ var prevBlock: [hLen]u8 = undefined;
+ var newBlock: [hLen]u8 = undefined;
+
+ // U_1 = PRF (P, S || INT (i))
+ const blockIndex = mem.toBytes(mem.nativeToBig(u32, block + 1)); // Block index starts at 0001
+ var ctx = Prf.init(password);
+ ctx.update(salt);
+ ctx.update(blockIndex[0..]);
+ ctx.final(prevBlock[0..]);
+
+ // Choose portion of DK to write into (T_n) and initialize
+ const offset = block * hLen;
+ const blockLen = if (block != l - 1) hLen else r;
+ const dkBlock: []u8 = derivedKey[offset..][0..blockLen];
+ mem.copy(u8, dkBlock, prevBlock[0..dkBlock.len]);
+
+ var i: u32 = 1;
+ while (i < rounds) : (i += 1) {
+ // U_c = PRF (P, U_{c-1})
+ Prf.create(&newBlock, prevBlock[0..], password);
+ mem.copy(u8, prevBlock[0..], newBlock[0..]);
+
+ // F (P, S, c, i) = U_1 \xor U_2 \xor ... \xor U_c
+ for (dkBlock) |_, j| {
+ dkBlock[j] ^= newBlock[j];
+ }
+ }
+ }
+}
+
+const htest = @import("test.zig");
+const HmacSha1 = std.crypto.auth.hmac.HmacSha1;
+
+// RFC 6070 PBKDF2 HMAC-SHA1 Test Vectors
+test "RFC 6070 one iteration" {
+ const p = "password";
+ const s = "salt";
+ const c = 1;
+ const dkLen = 20;
+
+ var derivedKey: [dkLen]u8 = undefined;
+
+ try pbkdf2(&derivedKey, p, s, c, HmacSha1);
+
+ const expected = "0c60c80f961f0e71f3a9b524af6012062fe037a6";
+
+ htest.assertEqual(expected, derivedKey[0..]);
+}
+
+test "RFC 6070 two iterations" {
+ const p = "password";
+ const s = "salt";
+ const c = 2;
+ const dkLen = 20;
+
+ var derivedKey: [dkLen]u8 = undefined;
+
+ try pbkdf2(&derivedKey, p, s, c, HmacSha1);
+
+ const expected = "ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957";
+
+ htest.assertEqual(expected, derivedKey[0..]);
+}
+
+test "RFC 6070 4096 iterations" {
+ const p = "password";
+ const s = "salt";
+ const c = 4096;
+ const dkLen = 20;
+
+ var derivedKey: [dkLen]u8 = undefined;
+
+ try pbkdf2(&derivedKey, p, s, c, HmacSha1);
+
+ const expected = "4b007901b765489abead49d926f721d065a429c1";
+
+ htest.assertEqual(expected, derivedKey[0..]);
+}
+
+test "RFC 6070 16,777,216 iterations" {
+ // These iteration tests are slow so we always skip them. Results have been verified.
+ if (true) {
+ return error.SkipZigTest;
+ }
+
+ const p = "password";
+ const s = "salt";
+ const c = 16777216;
+ const dkLen = 20;
+
+ var derivedKey = [_]u8{0} ** dkLen;
+
+ try pbkdf2(&derivedKey, p, s, c, HmacSha1);
+
+ const expected = "eefe3d61cd4da4e4e9945b3d6ba2158c2634e984";
+
+ htest.assertEqual(expected, derivedKey[0..]);
+}
+
+test "RFC 6070 multi-block salt and password" {
+ const p = "passwordPASSWORDpassword";
+ const s = "saltSALTsaltSALTsaltSALTsaltSALTsalt";
+ const c = 4096;
+ const dkLen = 25;
+
+ var derivedKey: [dkLen]u8 = undefined;
+
+ try pbkdf2(&derivedKey, p, s, c, HmacSha1);
+
+ const expected = "3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038";
+
+ htest.assertEqual(expected, derivedKey[0..]);
+}
+
+test "RFC 6070 embedded NUL" {
+ const p = "pass\x00word";
+ const s = "sa\x00lt";
+ const c = 4096;
+ const dkLen = 16;
+
+ var derivedKey: [dkLen]u8 = undefined;
+
+ try pbkdf2(&derivedKey, p, s, c, HmacSha1);
+
+ const expected = "56fa6aa75548099dcc37d7f03425e0c3";
+
+ htest.assertEqual(expected, derivedKey[0..]);
+}
+
+test "Very large dkLen" {
+ // This test allocates 8GB of memory and is expected to take several hours to run.
+ if (true) {
+ return error.SkipZigTest;
+ }
+ const p = "password";
+ const s = "salt";
+ const c = 1;
+ const dkLen = 1 << 33;
+
+ var derivedKey = try std.testing.allocator.alloc(u8, dkLen);
+ defer {
+ std.testing.allocator.free(derivedKey);
+ }
+
+ try pbkdf2(derivedKey, p, s, c, HmacSha1);
+ // Just verify this doesn't crash with an overflow
+}
diff --git a/lib/std/crypto/siphash.zig b/lib/std/crypto/siphash.zig
index 26c892fdd7..ae059b2567 100644
--- a/lib/std/crypto/siphash.zig
+++ b/lib/std/crypto/siphash.zig
@@ -218,8 +218,9 @@ fn SipHash(comptime T: type, comptime c_rounds: usize, comptime d_rounds: usize)
}
/// Return an authentication tag for the current state
+ /// Assumes `out` is less than or equal to `mac_length`.
pub fn final(self: *Self, out: []u8) void {
- std.debug.assert(out.len >= mac_length);
+ std.debug.assert(out.len <= mac_length);
mem.writeIntLittle(T, out[0..mac_length], self.state.final(self.buf[0..self.buf_len]));
}
diff --git a/lib/std/fmt.zig b/lib/std/fmt.zig
index 8d31733959..56a1aba217 100644
--- a/lib/std/fmt.zig
+++ b/lib/std/fmt.zig
@@ -22,7 +22,7 @@ pub const Alignment = enum {
pub const FormatOptions = struct {
precision: ?usize = null,
width: ?usize = null,
- alignment: Alignment = .Left,
+ alignment: Alignment = .Right,
fill: u8 = ' ',
};
@@ -327,7 +327,7 @@ pub fn formatType(
max_depth: usize,
) @TypeOf(writer).Error!void {
if (comptime std.mem.eql(u8, fmt, "*")) {
- try writer.writeAll(@typeName(@typeInfo(@TypeOf(value)).Pointer.child));
+ try writer.writeAll(@typeName(std.meta.Child(@TypeOf(value))));
try writer.writeAll("@");
try formatInt(@ptrToInt(value), 16, false, FormatOptions{}, writer);
return;
@@ -631,26 +631,22 @@ pub fn formatBuf(
writer: anytype,
) !void {
const width = options.width orelse buf.len;
- var padding = if (width > buf.len) (width - buf.len) else 0;
- const pad_byte = [1]u8{options.fill};
+ const padding = if (width > buf.len) (width - buf.len) else 0;
+
switch (options.alignment) {
.Left => {
try writer.writeAll(buf);
- while (padding > 0) : (padding -= 1) {
- try writer.writeAll(&pad_byte);
- }
+ try writer.writeByteNTimes(options.fill, padding);
},
.Center => {
- const padl = padding / 2;
- var i: usize = 0;
- while (i < padl) : (i += 1) try writer.writeAll(&pad_byte);
+ const left_padding = padding / 2;
+ const right_padding = (padding + 1) / 2;
+ try writer.writeByteNTimes(options.fill, left_padding);
try writer.writeAll(buf);
- while (i < padding) : (i += 1) try writer.writeAll(&pad_byte);
+ try writer.writeByteNTimes(options.fill, right_padding);
},
.Right => {
- while (padding > 0) : (padding -= 1) {
- try writer.writeAll(&pad_byte);
- }
+ try writer.writeByteNTimes(options.fill, padding);
try writer.writeAll(buf);
},
}
@@ -941,61 +937,27 @@ pub fn formatInt(
options: FormatOptions,
writer: anytype,
) !void {
+ assert(base >= 2);
+
const int_value = if (@TypeOf(value) == comptime_int) blk: {
const Int = math.IntFittingRange(value, value);
break :blk @as(Int, value);
} else
value;
- if (@typeInfo(@TypeOf(int_value)).Int.is_signed) {
- return formatIntSigned(int_value, base, uppercase, options, writer);
- } else {
- return formatIntUnsigned(int_value, base, uppercase, options, writer);
- }
-}
+ const value_info = @typeInfo(@TypeOf(int_value)).Int;
-fn formatIntSigned(
- value: anytype,
- base: u8,
- uppercase: bool,
- options: FormatOptions,
- writer: anytype,
-) !void {
- const new_options = FormatOptions{
- .width = if (options.width) |w| (if (w == 0) 0 else w - 1) else null,
- .precision = options.precision,
- .fill = options.fill,
- };
- const bit_count = @typeInfo(@TypeOf(value)).Int.bits;
- const Uint = std.meta.Int(false, bit_count);
- if (value < 0) {
- try writer.writeAll("-");
- const new_value = math.absCast(value);
- return formatIntUnsigned(new_value, base, uppercase, new_options, writer);
- } else if (options.width == null or options.width.? == 0) {
- return formatIntUnsigned(@intCast(Uint, value), base, uppercase, options, writer);
- } else {
- try writer.writeAll("+");
- const new_value = @intCast(Uint, value);
- return formatIntUnsigned(new_value, base, uppercase, new_options, writer);
- }
-}
+ // The type must have the same size as `base` or be wider in order for the
+ // division to work
+ const min_int_bits = comptime math.max(value_info.bits, 8);
+ const MinInt = std.meta.Int(false, min_int_bits);
-fn formatIntUnsigned(
- value: anytype,
- base: u8,
- uppercase: bool,
- options: FormatOptions,
- writer: anytype,
-) !void {
- assert(base >= 2);
- const value_info = @typeInfo(@TypeOf(value)).Int;
- var buf: [math.max(value_info.bits, 1)]u8 = undefined;
- const min_int_bits = comptime math.max(value_info.bits, @typeInfo(@TypeOf(base)).Int.bits);
- const MinInt = std.meta.Int(value_info.is_signed, min_int_bits);
- var a: MinInt = value;
- var index: usize = buf.len;
+ const abs_value = math.absCast(int_value);
+ // The worst case in terms of space needed is base 2, plus 1 for the sign
+ var buf: [1 + math.max(value_info.bits, 1)]u8 = undefined;
+ var a: MinInt = abs_value;
+ var index: usize = buf.len;
while (true) {
const digit = a % base;
index -= 1;
@@ -1004,25 +966,21 @@ fn formatIntUnsigned(
if (a == 0) break;
}
- const digits_buf = buf[index..];
- const width = options.width orelse 0;
- const padding = if (width > digits_buf.len) (width - digits_buf.len) else 0;
-
- if (padding > index) {
- const zero_byte: u8 = options.fill;
- var leftover_padding = padding - index;
- while (true) {
- try writer.writeAll(@as(*const [1]u8, &zero_byte)[0..]);
- leftover_padding -= 1;
- if (leftover_padding == 0) break;
+ if (value_info.is_signed) {
+ if (value < 0) {
+ // Negative integer
+ index -= 1;
+ buf[index] = '-';
+ } else if (options.width == null or options.width.? == 0) {
+ // Positive integer, omit the plus sign
+ } else {
+ // Positive integer
+ index -= 1;
+ buf[index] = '+';
}
- mem.set(u8, buf[0..index], options.fill);
- return writer.writeAll(&buf);
- } else {
- const padded_buf = buf[index - padding ..];
- mem.set(u8, padded_buf[0..padding], options.fill);
- return writer.writeAll(padded_buf);
}
+
+ return formatBuf(buf[index..], options, writer);
}
pub fn formatIntBuf(out_buf: []u8, value: anytype, base: u8, uppercase: bool, options: FormatOptions) usize {
@@ -1246,6 +1204,10 @@ test "optional" {
const value: ?i32 = null;
try testFmt("optional: null\n", "optional: {}\n", .{value});
}
+ {
+ const value = @intToPtr(?*i32, 0xf000d000);
+ try testFmt("optional: *i32@f000d000\n", "optional: {*}\n", .{value});
+ }
}
test "error" {
@@ -1283,7 +1245,17 @@ test "int.specifier" {
test "int.padded" {
try testFmt("u8: ' 1'", "u8: '{:4}'", .{@as(u8, 1)});
- try testFmt("u8: 'xxx1'", "u8: '{:x<4}'", .{@as(u8, 1)});
+ try testFmt("u8: '1000'", "u8: '{:0<4}'", .{@as(u8, 1)});
+ try testFmt("u8: '0001'", "u8: '{:0>4}'", .{@as(u8, 1)});
+ try testFmt("u8: '0100'", "u8: '{:0^4}'", .{@as(u8, 1)});
+ try testFmt("i8: '-1 '", "i8: '{:<4}'", .{@as(i8, -1)});
+ try testFmt("i8: ' -1'", "i8: '{:>4}'", .{@as(i8, -1)});
+ try testFmt("i8: ' -1 '", "i8: '{:^4}'", .{@as(i8, -1)});
+ try testFmt("i16: '-1234'", "i16: '{:4}'", .{@as(i16, -1234)});
+ try testFmt("i16: '+1234'", "i16: '{:4}'", .{@as(i16, 1234)});
+ try testFmt("i16: '-12345'", "i16: '{:4}'", .{@as(i16, -12345)});
+ try testFmt("i16: '+12345'", "i16: '{:4}'", .{@as(i16, 12345)});
+ try testFmt("u16: '12345'", "u16: '{:4}'", .{@as(u16, 12345)});
}
test "buffer" {
@@ -1329,7 +1301,7 @@ test "slice" {
try testFmt("slice: []const u8@deadbeef\n", "slice: {}\n", .{value});
}
- try testFmt("buf: Test \n", "buf: {s:5}\n", .{"Test"});
+ try testFmt("buf: Test\n", "buf: {s:5}\n", .{"Test"});
try testFmt("buf: Test\n Other text", "buf: {s}\n Other text", .{"Test"});
}
@@ -1362,7 +1334,7 @@ test "cstr" {
.{@ptrCast([*c]const u8, "Test C")},
);
try testFmt(
- "cstr: Test C \n",
+ "cstr: Test C\n",
"cstr: {s:10}\n",
.{@ptrCast([*c]const u8, "Test C")},
);
@@ -1805,7 +1777,7 @@ test "vector" {
try testFmt("{ true, false, true, false }", "{}", .{vbool});
try testFmt("{ -2, -1, 0, 1 }", "{}", .{vi64});
- try testFmt("{ - 2, - 1, + 0, + 1 }", "{d:5}", .{vi64});
+ try testFmt("{ -2, -1, +0, +1 }", "{d:5}", .{vi64});
try testFmt("{ 1000, 2000, 3000, 4000 }", "{}", .{vu64});
try testFmt("{ 3e8, 7d0, bb8, fa0 }", "{x}", .{vu64});
try testFmt("{ 1kB, 2kB, 3kB, 4kB }", "{B}", .{vu64});
@@ -1818,15 +1790,16 @@ test "enum-literal" {
test "padding" {
try testFmt("Simple", "{}", .{"Simple"});
- try testFmt("true ", "{:10}", .{true});
+ try testFmt(" true", "{:10}", .{true});
try testFmt(" true", "{:>10}", .{true});
try testFmt("======true", "{:=>10}", .{true});
try testFmt("true======", "{:=<10}", .{true});
try testFmt(" true ", "{:^10}", .{true});
try testFmt("===true===", "{:=^10}", .{true});
- try testFmt("Minimum width", "{:18} width", .{"Minimum"});
+ try testFmt(" Minimum width", "{:18} width", .{"Minimum"});
try testFmt("==================Filled", "{:=>24}", .{"Filled"});
try testFmt(" Centered ", "{:^24}", .{"Centered"});
+ try testFmt("-", "{:-^1}", .{""});
}
test "decimal float padding" {
diff --git a/lib/std/fs.zig b/lib/std/fs.zig
index a217fb3e9b..1890d7e136 100644
--- a/lib/std/fs.zig
+++ b/lib/std/fs.zig
@@ -21,10 +21,6 @@ pub const wasi = @import("fs/wasi.zig");
// TODO audit these APIs with respect to Dir and absolute paths
-pub const rename = os.rename;
-pub const renameZ = os.renameZ;
-pub const renameC = @compileError("deprecated: renamed to renameZ");
-pub const renameW = os.renameW;
pub const realpath = os.realpath;
pub const realpathZ = os.realpathZ;
pub const realpathC = @compileError("deprecated: renamed to realpathZ");
@@ -90,7 +86,7 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path:
base64_encoder.encode(tmp_path[dirname.len + 1 ..], &rand_buf);
if (cwd().symLink(existing_path, tmp_path, .{})) {
- return rename(tmp_path, new_path);
+ return cwd().rename(tmp_path, new_path);
} else |err| switch (err) {
error.PathAlreadyExists => continue,
else => return err, // TODO zig should know this set does not include PathAlreadyExists
@@ -255,6 +251,45 @@ pub fn deleteDirAbsoluteW(dir_path: [*:0]const u16) !void {
return os.rmdirW(dir_path);
}
+pub const renameC = @compileError("deprecated: use renameZ, dir.renameZ, or renameAbsoluteZ");
+
+/// Same as `Dir.rename` except the paths are absolute.
+pub fn renameAbsolute(old_path: []const u8, new_path: []const u8) !void {
+ assert(path.isAbsolute(old_path));
+ assert(path.isAbsolute(new_path));
+ return os.rename(old_path, new_path);
+}
+
+/// Same as `renameAbsolute` except the path parameters are null-terminated.
+pub fn renameAbsoluteZ(old_path: [*:0]const u8, new_path: [*:0]const u8) !void {
+ assert(path.isAbsoluteZ(old_path));
+ assert(path.isAbsoluteZ(new_path));
+ return os.renameZ(old_path, new_path);
+}
+
+/// Same as `renameAbsolute` except the path parameters are WTF-16 and target OS is assumed Windows.
+pub fn renameAbsoluteW(old_path: [*:0]const u16, new_path: [*:0]const u16) !void {
+ assert(path.isAbsoluteWindowsW(old_path));
+ assert(path.isAbsoluteWindowsW(new_path));
+ return os.renameW(old_path, new_path);
+}
+
+/// Same as `Dir.rename`, except `new_sub_path` is relative to `new_dir`
+pub fn rename(old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) !void {
+ return os.renameat(old_dir.fd, old_sub_path, new_dir.fd, new_sub_path);
+}
+
+/// Same as `rename` except the parameters are null-terminated.
+pub fn renameZ(old_dir: Dir, old_sub_path_z: [*:0]const u8, new_dir: Dir, new_sub_path_z: [*:0]const u8) !void {
+ return os.renameatZ(old_dir.fd, old_sub_path_z, new_dir.fd, new_sub_path_z);
+}
+
+/// Same as `rename` except the parameters are UTF16LE, NT prefixed.
+/// This function is Windows-only.
+pub fn renameW(old_dir: Dir, old_sub_path_w: []const u16, new_dir: Dir, new_sub_path_w: []const u16) !void {
+ return os.renameatW(old_dir.fd, old_sub_path_w, new_dir.fd, new_sub_path_w);
+}
+
pub const Dir = struct {
fd: os.fd_t,
@@ -1338,6 +1373,27 @@ pub const Dir = struct {
};
}
+ pub const RenameError = os.RenameError;
+
+ /// Change the name or location of a file or directory.
+ /// If new_sub_path already exists, it will be replaced.
+ /// Renaming a file over an existing directory or a directory
+ /// over an existing file will fail with `error.IsDir` or `error.NotDir`
+ pub fn rename(self: Dir, old_sub_path: []const u8, new_sub_path: []const u8) RenameError!void {
+ return os.renameat(self.fd, old_sub_path, self.fd, new_sub_path);
+ }
+
+ /// Same as `rename` except the parameters are null-terminated.
+ pub fn renameZ(self: Dir, old_sub_path_z: [*:0]const u8, new_sub_path_z: [*:0]const u8) RenameError!void {
+ return os.renameatZ(self.fd, old_sub_path_z, self.fd, new_sub_path_z);
+ }
+
+ /// Same as `rename` except the parameters are UTF16LE, NT prefixed.
+ /// This function is Windows-only.
+ pub fn renameW(self: Dir, old_sub_path_w: []const u16, new_sub_path_w: []const u16) RenameError!void {
+ return os.renameatW(self.fd, old_sub_path_w, self.fd, new_sub_path_w);
+ }
+
/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.
/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
/// one; the latter case is known as a dangling link.
diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig
index a59bc46245..b3cc1fe569 100644
--- a/lib/std/fs/test.zig
+++ b/lib/std/fs/test.zig
@@ -274,6 +274,167 @@ test "file operations on directories" {
dir.close();
}
+test "Dir.rename files" {
+ var tmp_dir = tmpDir(.{});
+ defer tmp_dir.cleanup();
+
+ testing.expectError(error.FileNotFound, tmp_dir.dir.rename("missing_file_name", "something_else"));
+
+ // Renaming files
+ const test_file_name = "test_file";
+ const renamed_test_file_name = "test_file_renamed";
+ var file = try tmp_dir.dir.createFile(test_file_name, .{ .read = true });
+ file.close();
+ try tmp_dir.dir.rename(test_file_name, renamed_test_file_name);
+
+ // Ensure the file was renamed
+ testing.expectError(error.FileNotFound, tmp_dir.dir.openFile(test_file_name, .{}));
+ file = try tmp_dir.dir.openFile(renamed_test_file_name, .{});
+ file.close();
+
+ // Rename to self succeeds
+ try tmp_dir.dir.rename(renamed_test_file_name, renamed_test_file_name);
+
+ // Rename to existing file succeeds
+ var existing_file = try tmp_dir.dir.createFile("existing_file", .{ .read = true });
+ existing_file.close();
+ try tmp_dir.dir.rename(renamed_test_file_name, "existing_file");
+
+ testing.expectError(error.FileNotFound, tmp_dir.dir.openFile(renamed_test_file_name, .{}));
+ file = try tmp_dir.dir.openFile("existing_file", .{});
+ file.close();
+}
+
+test "Dir.rename directories" {
+ // TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364
+ if (builtin.os.tag == .windows) return error.SkipZigTest;
+
+ var tmp_dir = tmpDir(.{});
+ defer tmp_dir.cleanup();
+
+ // Renaming directories
+ try tmp_dir.dir.makeDir("test_dir");
+ try tmp_dir.dir.rename("test_dir", "test_dir_renamed");
+
+ // Ensure the directory was renamed
+ testing.expectError(error.FileNotFound, tmp_dir.dir.openDir("test_dir", .{}));
+ var dir = try tmp_dir.dir.openDir("test_dir_renamed", .{});
+
+ // Put a file in the directory
+ var file = try dir.createFile("test_file", .{ .read = true });
+ file.close();
+ dir.close();
+
+ try tmp_dir.dir.rename("test_dir_renamed", "test_dir_renamed_again");
+
+ // Ensure the directory was renamed and the file still exists in it
+ testing.expectError(error.FileNotFound, tmp_dir.dir.openDir("test_dir_renamed", .{}));
+ dir = try tmp_dir.dir.openDir("test_dir_renamed_again", .{});
+ file = try dir.openFile("test_file", .{});
+ file.close();
+ dir.close();
+
+ // Try to rename to a non-empty directory now
+ var target_dir = try tmp_dir.dir.makeOpenPath("non_empty_target_dir", .{});
+ file = try target_dir.createFile("filler", .{ .read = true });
+ file.close();
+
+ testing.expectError(error.PathAlreadyExists, tmp_dir.dir.rename("test_dir_renamed_again", "non_empty_target_dir"));
+
+ // Ensure the directory was not renamed
+ dir = try tmp_dir.dir.openDir("test_dir_renamed_again", .{});
+ file = try dir.openFile("test_file", .{});
+ file.close();
+ dir.close();
+}
+
+test "Dir.rename file <-> dir" {
+ // TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364
+ if (builtin.os.tag == .windows) return error.SkipZigTest;
+
+ var tmp_dir = tmpDir(.{});
+ defer tmp_dir.cleanup();
+
+ var file = try tmp_dir.dir.createFile("test_file", .{ .read = true });
+ file.close();
+ try tmp_dir.dir.makeDir("test_dir");
+ testing.expectError(error.IsDir, tmp_dir.dir.rename("test_file", "test_dir"));
+ testing.expectError(error.NotDir, tmp_dir.dir.rename("test_dir", "test_file"));
+}
+
+test "rename" {
+ var tmp_dir1 = tmpDir(.{});
+ defer tmp_dir1.cleanup();
+
+ var tmp_dir2 = tmpDir(.{});
+ defer tmp_dir2.cleanup();
+
+ // Renaming files
+ const test_file_name = "test_file";
+ const renamed_test_file_name = "test_file_renamed";
+ var file = try tmp_dir1.dir.createFile(test_file_name, .{ .read = true });
+ file.close();
+ try fs.rename(tmp_dir1.dir, test_file_name, tmp_dir2.dir, renamed_test_file_name);
+
+ // ensure the file was renamed
+ testing.expectError(error.FileNotFound, tmp_dir1.dir.openFile(test_file_name, .{}));
+ file = try tmp_dir2.dir.openFile(renamed_test_file_name, .{});
+ file.close();
+}
+
+test "renameAbsolute" {
+ if (builtin.os.tag == .wasi) return error.SkipZigTest;
+
+ var tmp_dir = tmpDir(.{});
+ defer tmp_dir.cleanup();
+
+ // Get base abs path
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const allocator = &arena.allocator;
+
+ const base_path = blk: {
+ const relative_path = try fs.path.join(&arena.allocator, &[_][]const u8{ "zig-cache", "tmp", tmp_dir.sub_path[0..] });
+ break :blk try fs.realpathAlloc(&arena.allocator, relative_path);
+ };
+
+ testing.expectError(error.FileNotFound, fs.renameAbsolute(
+ try fs.path.join(allocator, &[_][]const u8{ base_path, "missing_file_name" }),
+ try fs.path.join(allocator, &[_][]const u8{ base_path, "something_else" }),
+ ));
+
+ // Renaming files
+ const test_file_name = "test_file";
+ const renamed_test_file_name = "test_file_renamed";
+ var file = try tmp_dir.dir.createFile(test_file_name, .{ .read = true });
+ file.close();
+ try fs.renameAbsolute(
+ try fs.path.join(allocator, &[_][]const u8{ base_path, test_file_name }),
+ try fs.path.join(allocator, &[_][]const u8{ base_path, renamed_test_file_name }),
+ );
+
+ // ensure the file was renamed
+ testing.expectError(error.FileNotFound, tmp_dir.dir.openFile(test_file_name, .{}));
+ file = try tmp_dir.dir.openFile(renamed_test_file_name, .{});
+ const stat = try file.stat();
+ testing.expect(stat.kind == .File);
+ file.close();
+
+ // Renaming directories
+ const test_dir_name = "test_dir";
+ const renamed_test_dir_name = "test_dir_renamed";
+ try tmp_dir.dir.makeDir(test_dir_name);
+ try fs.renameAbsolute(
+ try fs.path.join(allocator, &[_][]const u8{ base_path, test_dir_name }),
+ try fs.path.join(allocator, &[_][]const u8{ base_path, renamed_test_dir_name }),
+ );
+
+ // ensure the directory was renamed
+ testing.expectError(error.FileNotFound, tmp_dir.dir.openDir(test_dir_name, .{}));
+ var dir = try tmp_dir.dir.openDir(renamed_test_dir_name, .{});
+ dir.close();
+}
+
test "openSelfExe" {
if (builtin.os.tag == .wasi) return error.SkipZigTest;
diff --git a/lib/std/heap.zig b/lib/std/heap.zig
index 6db1be539c..16de215cc2 100644
--- a/lib/std/heap.zig
+++ b/lib/std/heap.zig
@@ -489,7 +489,7 @@ pub const HeapAllocator = switch (builtin.os.tag) {
const full_len = os.windows.kernel32.HeapSize(heap_handle, 0, ptr);
assert(full_len != std.math.maxInt(usize));
assert(full_len >= amt);
- break :init mem.alignBackwardAnyAlign(full_len - (aligned_addr - root_addr), len_align);
+ break :init mem.alignBackwardAnyAlign(full_len - (aligned_addr - root_addr) - @sizeOf(usize), len_align);
};
const buf = @intToPtr([*]u8, aligned_addr)[0..return_len];
getRecordPtr(buf).* = root_addr;
diff --git a/lib/std/os.zig b/lib/std/os.zig
index bdc746419a..0b09b1f82a 100644
--- a/lib/std/os.zig
+++ b/lib/std/os.zig
@@ -1890,7 +1890,7 @@ pub fn unlinkatW(dirfd: fd_t, sub_path_w: []const u16, flags: u32) UnlinkatError
return windows.DeleteFile(sub_path_w, .{ .dir = dirfd, .remove_dir = remove_dir });
}
-const RenameError = error{
+pub const RenameError = error{
/// In WASI, this error may occur when the file descriptor does
/// not hold the required rights to rename a resource by path relative to it.
AccessDenied,
@@ -2107,6 +2107,7 @@ pub fn renameatW(
.ACCESS_DENIED => return error.AccessDenied,
.OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
.OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
+ .NOT_SAME_DEVICE => return error.RenameAcrossMountPoints,
else => return windows.unexpectedStatus(rc),
}
}
diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig
index 405f7438f5..579215cf12 100644
--- a/lib/std/os/windows.zig
+++ b/lib/std/os/windows.zig
@@ -830,7 +830,7 @@ pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFil
}
}
-pub const MoveFileError = error{Unexpected};
+pub const MoveFileError = error{ FileNotFound, Unexpected };
pub fn MoveFileEx(old_path: []const u8, new_path: []const u8, flags: DWORD) MoveFileError!void {
const old_path_w = try sliceToPrefixedFileW(old_path);
@@ -841,6 +841,7 @@ pub fn MoveFileEx(old_path: []const u8, new_path: []const u8, flags: DWORD) Move
pub fn MoveFileExW(old_path: [*:0]const u16, new_path: [*:0]const u16, flags: DWORD) MoveFileError!void {
if (kernel32.MoveFileExW(old_path, new_path, flags) == 0) {
switch (kernel32.GetLastError()) {
+ .FILE_NOT_FOUND => return error.FileNotFound,
else => |err| return unexpectedError(err),
}
}