Merge remote-tracking branch 'origin/master' into stage2-zig-cc

This commit is contained in:
Andrew Kelley 2020-09-21 21:16:46 -07:00
commit 0c70bb4fce
16 changed files with 678 additions and 111 deletions

View File

@ -9728,7 +9728,7 @@ const c = @cImport({
<li>Does not support Zig-only pointer attributes such as alignment. Use normal {#link|Pointers#}
please!</li>
</ul>
<p>When a C pointer is pointing to a single struct (not an array), deference the C pointer to
<p>When a C pointer is pointing to a single struct (not an array), dereference the C pointer to
access to the struct's fields or member data. That syntax looks like
this: </p>
<p>{#syntax#}ptr_to_struct.*.struct_member{#endsyntax#}</p>

View File

@ -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) {

View File

@ -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");

280
lib/std/crypto/pbkdf2.zig Normal file
View File

@ -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
}

View File

@ -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]));
}

View File

@ -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;
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" {

View File

@ -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.

View File

@ -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;

View File

@ -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;

View File

@ -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),
}
}

View File

@ -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),
}
}

View File

@ -3161,22 +3161,7 @@ static Error resolve_union_zero_bits(CodeGen *g, ZigType *union_type) {
tag_type->data.enumeration.fields_by_name.init(field_count);
tag_type->data.enumeration.decls_scope = union_type->data.unionation.decls_scope;
} else if (enum_type_node != nullptr) {
ZigType *enum_type = analyze_type_expr(g, scope, enum_type_node);
if (type_is_invalid(enum_type)) {
union_type->data.unionation.resolve_status = ResolveStatusInvalid;
return ErrorSemanticAnalyzeFail;
}
if (enum_type->id != ZigTypeIdEnum) {
union_type->data.unionation.resolve_status = ResolveStatusInvalid;
add_node_error(g, enum_type_node,
buf_sprintf("expected enum tag type, found '%s'", buf_ptr(&enum_type->name)));
return ErrorSemanticAnalyzeFail;
}
if ((err = type_resolve(g, enum_type, ResolveStatusAlignmentKnown))) {
assert(g->errors.length != 0);
return err;
}
tag_type = enum_type;
tag_type = analyze_type_expr(g, scope, enum_type_node);
} else {
if (decl_node->type == NodeTypeContainerDecl) {
tag_type = nullptr;
@ -3185,6 +3170,20 @@ static Error resolve_union_zero_bits(CodeGen *g, ZigType *union_type) {
}
}
if (tag_type != nullptr) {
if (type_is_invalid(tag_type)) {
union_type->data.unionation.resolve_status = ResolveStatusInvalid;
return ErrorSemanticAnalyzeFail;
}
if (tag_type->id != ZigTypeIdEnum) {
union_type->data.unionation.resolve_status = ResolveStatusInvalid;
add_node_error(g, enum_type_node != nullptr ? enum_type_node : decl_node,
buf_sprintf("expected enum tag type, found '%s'", buf_ptr(&tag_type->name)));
return ErrorSemanticAnalyzeFail;
}
if ((err = type_resolve(g, tag_type, ResolveStatusAlignmentKnown))) {
assert(g->errors.length != 0);
return err;
}
covered_enum_fields = heap::c_allocator.allocate<bool>(tag_type->data.enumeration.src_field_count);
}
union_type->data.unionation.tag_type = tag_type;

View File

@ -63,6 +63,7 @@ enum ConstCastResultId {
ConstCastResultIdPointerChild,
ConstCastResultIdSliceChild,
ConstCastResultIdOptionalChild,
ConstCastResultIdOptionalShape,
ConstCastResultIdErrorUnionPayload,
ConstCastResultIdErrorUnionErrorSet,
ConstCastResultIdFnAlign,
@ -11946,8 +11947,22 @@ static ConstCastOnly types_match_const_cast_only(IrAnalyze *ira, ZigType *wanted
}
}
// maybe
// optional types
if (wanted_type->id == ZigTypeIdOptional && actual_type->id == ZigTypeIdOptional) {
// Consider the case where the wanted type is ??[*]T and the actual one
// is ?[*]T, we cannot turn the former into the latter even though the
// child types are compatible (?[*]T and [*]T are both represented as a
// pointer). The extra level of indirection in ??[*]T means it's
// represented as a regular, fat, optional type and, as a consequence,
// has a different shape than the one of ?[*]T.
if ((wanted_ptr_type != nullptr) != (actual_ptr_type != nullptr)) {
// The use of type_mismatch is intentional
result.id = ConstCastResultIdOptionalShape;
result.data.type_mismatch = heap::c_allocator.allocate_nonzero<ConstCastTypeMismatch>(1);
result.data.type_mismatch->wanted_type = wanted_type;
result.data.type_mismatch->actual_type = actual_type;
return result;
}
ConstCastOnly child = types_match_const_cast_only(ira, wanted_type->data.maybe.child_type,
actual_type->data.maybe.child_type, source_node, wanted_is_mutable);
if (child.id == ConstCastResultIdInvalid)
@ -14549,6 +14564,13 @@ static void report_recursive_error(IrAnalyze *ira, AstNode *source_node, ConstCa
report_recursive_error(ira, source_node, &cast_result->data.optional->child, msg);
break;
}
case ConstCastResultIdOptionalShape: {
add_error_note(ira->codegen, parent_msg, source_node,
buf_sprintf("optional type child '%s' cannot cast into optional type '%s'",
buf_ptr(&cast_result->data.type_mismatch->actual_type->name),
buf_ptr(&cast_result->data.type_mismatch->wanted_type->name)));
break;
}
case ConstCastResultIdErrorUnionErrorSet: {
ErrorMsg *msg = add_error_note(ira->codegen, parent_msg, source_node,
buf_sprintf("error set '%s' cannot cast into error set '%s'",

View File

@ -2032,7 +2032,7 @@ fn escapeChar(c: u8, char_buf: *[4]u8) []const u8 {
// Handle the remaining escapes Zig doesn't support by turning them
// into their respective hex representation
else => if (std.ascii.isCntrl(c))
std.fmt.bufPrint(char_buf, "\\x{x:0<2}", .{c}) catch unreachable
std.fmt.bufPrint(char_buf, "\\x{x:0>2}", .{c}) catch unreachable
else
std.fmt.bufPrint(char_buf, "{c}", .{c}) catch unreachable,
};

View File

@ -849,3 +849,8 @@ test "comptime float casts" {
expect(b == 2);
expect(@TypeOf(b) == comptime_int);
}
test "cast from ?[*]T to ??[*]T" {
const a: ??[*]u8 = @as(?[*]u8, null);
expect(a != null and a.? == null);
}

View File

@ -374,3 +374,45 @@ test "Type.Union" {
tagged = .{ .unsigned = 1 };
testing.expectEqual(Tag.unsigned, tagged);
}
test "Type.Union from Type.Enum" {
const Tag = @Type(.{
.Enum = .{
.layout = .Auto,
.tag_type = u0,
.fields = &[_]TypeInfo.EnumField{
.{ .name = "working_as_expected", .value = 0 },
},
.decls = &[_]TypeInfo.Declaration{},
.is_exhaustive = true,
},
});
const T = @Type(.{
.Union = .{
.layout = .Auto,
.tag_type = Tag,
.fields = &[_]TypeInfo.UnionField{
.{ .name = "working_as_expected", .field_type = u32 },
},
.decls = &[_]TypeInfo.Declaration{},
},
});
_ = T;
_ = @typeInfo(T).Union;
}
test "Type.Union from regular enum" {
const E = enum { working_as_expected = 0 };
const T = @Type(.{
.Union = .{
.layout = .Auto,
.tag_type = E,
.fields = &[_]TypeInfo.UnionField{
.{ .name = "working_as_expected", .field_type = u32 },
},
.decls = &[_]TypeInfo.Declaration{},
},
});
_ = T;
_ = @typeInfo(T).Union;
}