diff options
Diffstat (limited to 'lib/std')
| -rw-r--r-- | lib/std/fs.zig | 119 | ||||
| -rw-r--r-- | lib/std/fs/test.zig | 58 | ||||
| -rw-r--r-- | lib/std/log.zig | 119 | ||||
| -rw-r--r-- | lib/std/os.zig | 65 | ||||
| -rw-r--r-- | lib/std/target/powerpc.zig | 56 |
5 files changed, 348 insertions, 69 deletions
diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 57c1534e97..a492a43499 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -926,6 +926,123 @@ pub const Dir = struct { return self.openDir(sub_path, open_dir_options); } + /// This function returns the canonicalized absolute pathname of + /// `pathname` relative to this `Dir`. If `pathname` is absolute, ignores this + /// `Dir` handle and returns the canonicalized absolute pathname of `pathname` + /// argument. + /// This function is not universally supported by all platforms. + /// Currently supported hosts are: Linux, macOS, and Windows. + /// See also `Dir.realpathZ`, `Dir.realpathW`, and `Dir.realpathAlloc`. + pub fn realpath(self: Dir, pathname: []const u8, out_buffer: []u8) ![]u8 { + if (builtin.os.tag == .wasi) { + @compileError("realpath is unsupported in WASI"); + } + if (builtin.os.tag == .windows) { + const pathname_w = try os.windows.sliceToPrefixedFileW(pathname); + return self.realpathW(pathname_w.span(), out_buffer); + } + const pathname_c = try os.toPosixPath(pathname); + return self.realpathZ(&pathname_c, out_buffer); + } + + /// Same as `Dir.realpath` except `pathname` is null-terminated. + /// See also `Dir.realpath`, `realpathZ`. + pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) ![]u8 { + if (builtin.os.tag == .windows) { + const pathname_w = try os.windows.cStrToPrefixedFileW(pathname); + return self.realpathW(pathname_w.span(), out_buffer); + } + + const flags = if (builtin.os.tag == .linux) os.O_PATH | os.O_NONBLOCK | os.O_CLOEXEC else os.O_NONBLOCK | os.O_CLOEXEC; + const fd = os.openatZ(self.fd, pathname, flags, 0) catch |err| switch (err) { + error.FileLocksNotSupported => unreachable, + else => |e| return e, + }; + defer os.close(fd); + + // Use of MAX_PATH_BYTES here is valid as the realpath function does not + // have a variant that takes an arbitrary-size buffer. + // TODO(#4812): Consider reimplementing realpath or using the POSIX.1-2008 + // NULL out parameter (GNU's canonicalize_file_name) to handle overelong + // paths. musl supports passing NULL but restricts the output to PATH_MAX + // anyway. + var buffer: [MAX_PATH_BYTES]u8 = undefined; + const out_path = try os.getFdPath(fd, &buffer); + + if (out_path.len > out_buffer.len) { + return error.NameTooLong; + } + + mem.copy(u8, out_buffer, out_path); + + return out_buffer[0..out_path.len]; + } + + /// Windows-only. Same as `Dir.realpath` except `pathname` is WTF16 encoded. + /// See also `Dir.realpath`, `realpathW`. + pub fn realpathW(self: Dir, pathname: []const u16, out_buffer: []u8) ![]u8 { + const w = os.windows; + + const access_mask = w.GENERIC_READ | w.SYNCHRONIZE; + const share_access = w.FILE_SHARE_READ; + const creation = w.FILE_OPEN; + const h_file = blk: { + const res = w.OpenFile(pathname, .{ + .dir = self.fd, + .access_mask = access_mask, + .share_access = share_access, + .creation = creation, + .io_mode = .blocking, + }) catch |err| switch (err) { + error.IsDir => break :blk w.OpenFile(pathname, .{ + .dir = self.fd, + .access_mask = access_mask, + .share_access = share_access, + .creation = creation, + .io_mode = .blocking, + .open_dir = true, + }) catch |er| switch (er) { + error.WouldBlock => unreachable, + else => |e2| return e2, + }, + error.WouldBlock => unreachable, + else => |e| return e, + }; + break :blk res; + }; + defer w.CloseHandle(h_file); + + // Use of MAX_PATH_BYTES here is valid as the realpath function does not + // have a variant that takes an arbitrary-size buffer. + // TODO(#4812): Consider reimplementing realpath or using the POSIX.1-2008 + // NULL out parameter (GNU's canonicalize_file_name) to handle overelong + // paths. musl supports passing NULL but restricts the output to PATH_MAX + // anyway. + var buffer: [MAX_PATH_BYTES]u8 = undefined; + const out_path = try os.getFdPath(h_file, &buffer); + + if (out_path.len > out_buffer.len) { + return error.NameTooLong; + } + + mem.copy(u8, out_buffer, out_path); + + return out_buffer[0..out_path.len]; + } + + /// Same as `Dir.realpath` except caller must free the returned memory. + /// See also `Dir.realpath`. + pub fn realpathAlloc(self: Dir, allocator: *Allocator, pathname: []const u8) ![]u8 { + // Use of MAX_PATH_BYTES here is valid as the realpath function does not + // have a variant that takes an arbitrary-size buffer. + // TODO(#4812): Consider reimplementing realpath or using the POSIX.1-2008 + // NULL out parameter (GNU's canonicalize_file_name) to handle overelong + // paths. musl supports passing NULL but restricts the output to PATH_MAX + // anyway. + var buf: [MAX_PATH_BYTES]u8 = undefined; + return allocator.dupe(u8, try self.realpath(pathname, buf[0..])); + } + /// Changes the current working directory to the open directory handle. /// This modifies global state and can have surprising effects in multi- /// threaded applications. Most applications and especially libraries should @@ -2060,7 +2177,7 @@ pub fn selfExeDirPath(out_buffer: []u8) SelfExePathError![]const u8 { } /// `realpath`, except caller must free the returned memory. -/// TODO integrate with `Dir` +/// See also `Dir.realpath`. pub fn realpathAlloc(allocator: *Allocator, pathname: []const u8) ![]u8 { // Use of MAX_PATH_BYTES here is valid as the realpath function does not // have a variant that takes an arbitrary-size buffer. diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 26d8632c37..9e6f4bb3ac 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -109,17 +109,57 @@ test "Dir.Iterator" { testing.expect(contains(&entries, Dir.Entry{ .name = "some_dir", .kind = Dir.Entry.Kind.Directory })); } -fn entry_eql(lhs: Dir.Entry, rhs: Dir.Entry) bool { +fn entryEql(lhs: Dir.Entry, rhs: Dir.Entry) bool { return mem.eql(u8, lhs.name, rhs.name) and lhs.kind == rhs.kind; } fn contains(entries: *const std.ArrayList(Dir.Entry), el: Dir.Entry) bool { for (entries.items) |entry| { - if (entry_eql(entry, el)) return true; + if (entryEql(entry, el)) return true; } return false; } +test "Dir.realpath smoke test" { + switch (builtin.os.tag) { + .linux, .windows, .macosx, .ios, .watchos, .tvos => {}, + else => return error.SkipZigTest, + } + + var tmp_dir = tmpDir(.{}); + defer tmp_dir.cleanup(); + + var file = try tmp_dir.dir.createFile("test_file", .{ .lock = File.Lock.Shared }); + // We need to close the file immediately as otherwise on Windows we'll end up + // with a sharing violation. + file.close(); + + var arena = ArenaAllocator.init(testing.allocator); + defer arena.deinit(); + + 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); + }; + + // First, test non-alloc version + { + var buf1: [fs.MAX_PATH_BYTES]u8 = undefined; + const file_path = try tmp_dir.dir.realpath("test_file", buf1[0..]); + const expected_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "test_file" }); + + testing.expect(mem.eql(u8, file_path, expected_path)); + } + + // Next, test alloc version + { + const file_path = try tmp_dir.dir.realpathAlloc(&arena.allocator, "test_file"); + const expected_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "test_file" }); + + testing.expect(mem.eql(u8, file_path, expected_path)); + } +} + test "readAllAlloc" { var tmp_dir = tmpDir(.{}); defer tmp_dir.cleanup(); @@ -167,12 +207,7 @@ test "directory operations on files" { testing.expectError(error.NotDir, tmp_dir.dir.deleteDir(test_file_name)); if (builtin.os.tag != .wasi) { - // TODO: use Dir's realpath function once that exists - const absolute_path = blk: { - const relative_path = try fs.path.join(testing.allocator, &[_][]const u8{ "zig-cache", "tmp", tmp_dir.sub_path[0..], test_file_name }); - defer testing.allocator.free(relative_path); - break :blk try fs.realpathAlloc(testing.allocator, relative_path); - }; + const absolute_path = try tmp_dir.dir.realpathAlloc(testing.allocator, test_file_name); defer testing.allocator.free(absolute_path); testing.expectError(error.PathAlreadyExists, fs.makeDirAbsolute(absolute_path)); @@ -206,12 +241,7 @@ test "file operations on directories" { testing.expectError(error.IsDir, tmp_dir.dir.openFile(test_dir_name, .{ .write = true })); if (builtin.os.tag != .wasi) { - // TODO: use Dir's realpath function once that exists - const absolute_path = blk: { - const relative_path = try fs.path.join(testing.allocator, &[_][]const u8{ "zig-cache", "tmp", tmp_dir.sub_path[0..], test_dir_name }); - defer testing.allocator.free(relative_path); - break :blk try fs.realpathAlloc(testing.allocator, relative_path); - }; + const absolute_path = try tmp_dir.dir.realpathAlloc(testing.allocator, test_dir_name); defer testing.allocator.free(absolute_path); testing.expectError(error.IsDir, fs.createFileAbsolute(absolute_path, .{})); diff --git a/lib/std/log.zig b/lib/std/log.zig index d8bcba38cc..9a0dcecc05 100644 --- a/lib/std/log.zig +++ b/lib/std/log.zig @@ -2,12 +2,16 @@ const std = @import("std.zig"); const builtin = std.builtin; const root = @import("root"); -//! std.log is standardized interface for logging which allows for the logging +//! std.log is a standardized interface for logging which allows for the logging //! of programs and libraries using this interface to be formatted and filtered //! by the implementer of the root.log function. //! //! The scope parameter should be used to give context to the logging. For //! example, a library called 'libfoo' might use .libfoo as its scope. +//! This parameter can either be passed explicitly to the logging functions +//! provided here, or a scoped logging namespace can be created +//! using the `log.scoped` function. If logging scopes are not relevant for +//! your use case, the `log.default` scope namespace can be used. //! //! An example root.log might look something like this: //! @@ -44,16 +48,26 @@ const root = @import("root"); //! } //! //! pub fn main() void { +//! // Using explicit scopes: //! // Won't be printed as log_level is .warn //! std.log.info(.my_project, "Starting up.", .{}); //! std.log.err(.nice_library, "Something went very wrong, sorry.", .{}); //! // Won't be printed as it gets filtered out by our log function //! std.log.err(.lib_that_logs_too_much, "Added 1 + 1", .{}); +//! +//! // Using a scoped logging namespace: +//! const scoped_log = std.log.scoped(.my_project); +//! scoped_log.alert("The scope for this message is implicitly .my_project", .{}); +//! +//! // Using the default namespace: +//! // Won't be printed as log_level is .warn +//! std.log.default.info("I don't care about my namespace", .{}); //! } //! ``` //! Which produces the following output: //! ``` //! [err] (nice_library): Something went very wrong, sorry. +//! [alert] (my_project): The scope for this message is implicitly .my_project //! ``` pub const Level = enum { @@ -115,7 +129,7 @@ fn log( } } -/// Log an emergency message to stderr. This log level is intended to be used +/// Log an emergency message. This log level is intended to be used /// for conditions that cannot be handled and is usually followed by a panic. pub fn emerg( comptime scope: @Type(.EnumLiteral), @@ -126,7 +140,7 @@ pub fn emerg( log(.emerg, scope, format, args); } -/// Log an alert message to stderr. This log level is intended to be used for +/// Log an alert message. This log level is intended to be used for /// conditions that should be corrected immediately (e.g. database corruption). pub fn alert( comptime scope: @Type(.EnumLiteral), @@ -137,7 +151,7 @@ pub fn alert( log(.alert, scope, format, args); } -/// Log a critical message to stderr. This log level is intended to be used +/// Log a critical message. This log level is intended to be used /// when a bug has been detected or something has gone wrong and it will have /// an effect on the operation of the program. pub fn crit( @@ -149,7 +163,7 @@ pub fn crit( log(.crit, scope, format, args); } -/// Log an error message to stderr. This log level is intended to be used when +/// Log an error message. This log level is intended to be used when /// a bug has been detected or something has gone wrong but it is recoverable. pub fn err( comptime scope: @Type(.EnumLiteral), @@ -160,7 +174,7 @@ pub fn err( log(.err, scope, format, args); } -/// Log a warning message to stderr. This log level is intended to be used if +/// Log a warning message. This log level is intended to be used if /// it is uncertain whether something has gone wrong or not, but the /// circumstances would be worth investigating. pub fn warn( @@ -171,7 +185,7 @@ pub fn warn( log(.warn, scope, format, args); } -/// Log a notice message to stderr. This log level is intended to be used for +/// Log a notice message. This log level is intended to be used for /// non-error but significant conditions. pub fn notice( comptime scope: @Type(.EnumLiteral), @@ -181,7 +195,7 @@ pub fn notice( log(.notice, scope, format, args); } -/// Log an info message to stderr. This log level is intended to be used for +/// Log an info message. This log level is intended to be used for /// general messages about the state of the program. pub fn info( comptime scope: @Type(.EnumLiteral), @@ -191,7 +205,7 @@ pub fn info( log(.info, scope, format, args); } -/// Log a debug message to stderr. This log level is intended to be used for +/// Log a debug message. This log level is intended to be used for /// messages which are only useful for debugging. pub fn debug( comptime scope: @Type(.EnumLiteral), @@ -200,3 +214,90 @@ pub fn debug( ) void { log(.debug, scope, format, args); } + +/// Returns a scoped logging namespace that logs all messages using the scope +/// provided here. +pub fn scoped(comptime scope: @Type(.EnumLiteral)) type { + return struct { + /// Log an emergency message. This log level is intended to be used + /// for conditions that cannot be handled and is usually followed by a panic. + pub fn emerg( + comptime format: []const u8, + args: anytype, + ) void { + @setCold(true); + log(.emerg, scope, format, args); + } + + /// Log an alert message. This log level is intended to be used for + /// conditions that should be corrected immediately (e.g. database corruption). + pub fn alert( + comptime format: []const u8, + args: anytype, + ) void { + @setCold(true); + log(.alert, scope, format, args); + } + + /// Log a critical message. This log level is intended to be used + /// when a bug has been detected or something has gone wrong and it will have + /// an effect on the operation of the program. + pub fn crit( + comptime format: []const u8, + args: anytype, + ) void { + @setCold(true); + log(.crit, scope, format, args); + } + + /// Log an error message. This log level is intended to be used when + /// a bug has been detected or something has gone wrong but it is recoverable. + pub fn err( + comptime format: []const u8, + args: anytype, + ) void { + @setCold(true); + log(.err, scope, format, args); + } + + /// Log a warning message. This log level is intended to be used if + /// it is uncertain whether something has gone wrong or not, but the + /// circumstances would be worth investigating. + pub fn warn( + comptime format: []const u8, + args: anytype, + ) void { + log(.warn, scope, format, args); + } + + /// Log a notice message. This log level is intended to be used for + /// non-error but significant conditions. + pub fn notice( + comptime format: []const u8, + args: anytype, + ) void { + log(.notice, scope, format, args); + } + + /// Log an info message. This log level is intended to be used for + /// general messages about the state of the program. + pub fn info( + comptime format: []const u8, + args: anytype, + ) void { + log(.info, scope, format, args); + } + + /// Log a debug message. This log level is intended to be used for + /// messages which are only useful for debugging. + pub fn debug( + comptime format: []const u8, + args: anytype, + ) void { + log(.debug, scope, format, args); + } + }; +} + +/// The default scoped logging namespace. +pub const default = scoped(.default); diff --git a/lib/std/os.zig b/lib/std/os.zig index ae2b232ef7..123dfc9747 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -4025,23 +4025,15 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP const pathname_w = try windows.cStrToPrefixedFileW(pathname); return realpathW(pathname_w.span(), out_buffer); } - if (builtin.os.tag == .linux and !builtin.link_libc) { - const fd = openZ(pathname, linux.O_PATH | linux.O_NONBLOCK | linux.O_CLOEXEC, 0) catch |err| switch (err) { + if (!builtin.link_libc) { + const flags = if (builtin.os.tag == .linux) O_PATH | O_NONBLOCK | O_CLOEXEC else O_NONBLOCK | O_CLOEXEC; + const fd = openZ(pathname, flags, 0) catch |err| switch (err) { error.FileLocksNotSupported => unreachable, else => |e| return e, }; defer close(fd); - var procfs_buf: ["/proc/self/fd/-2147483648".len:0]u8 = undefined; - const proc_path = std.fmt.bufPrint(procfs_buf[0..], "/proc/self/fd/{}\x00", .{fd}) catch unreachable; - - const target = readlinkZ(@ptrCast([*:0]const u8, proc_path.ptr), out_buffer) catch |err| { - switch (err) { - error.UnsupportedReparsePointType => unreachable, // Windows only, - else => |e| return e, - } - }; - return target; + return getFdPath(fd, out_buffer); } const result_path = std.c.realpath(pathname, out_buffer) orelse switch (std.c._errno().*) { EINVAL => unreachable, @@ -4093,12 +4085,51 @@ pub fn realpathW(pathname: []const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPat }; defer w.CloseHandle(h_file); - var wide_buf: [w.PATH_MAX_WIDE]u16 = undefined; - const wide_slice = try w.GetFinalPathNameByHandle(h_file, .{}, wide_buf[0..]); + return getFdPath(h_file, out_buffer); +} + +/// Return canonical path of handle `fd`. +/// This function is very host-specific and is not universally supported by all hosts. +/// For example, while it generally works on Linux, macOS or Windows, it is unsupported +/// on FreeBSD, or WASI. +pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { + switch (builtin.os.tag) { + .windows => { + var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined; + const wide_slice = try windows.GetFinalPathNameByHandle(fd, .{}, wide_buf[0..]); - // Trust that Windows gives us valid UTF-16LE. - const end_index = std.unicode.utf16leToUtf8(out_buffer, wide_slice) catch unreachable; - return out_buffer[0..end_index]; + // Trust that Windows gives us valid UTF-16LE. + const end_index = std.unicode.utf16leToUtf8(out_buffer, wide_slice) catch unreachable; + return out_buffer[0..end_index]; + }, + .macosx, .ios, .watchos, .tvos => { + // On macOS, we can use F_GETPATH fcntl command to query the OS for + // the path to the file descriptor. + @memset(out_buffer, 0, MAX_PATH_BYTES); + switch (errno(system.fcntl(fd, F_GETPATH, out_buffer))) { + 0 => {}, + EBADF => return error.FileNotFound, + // TODO man pages for fcntl on macOS don't really tell you what + // errno values to expect when command is F_GETPATH... + else => |err| return unexpectedErrno(err), + } + const len = mem.indexOfScalar(u8, out_buffer[0..], @as(u8, 0)) orelse MAX_PATH_BYTES; + return out_buffer[0..len]; + }, + .linux => { + var procfs_buf: ["/proc/self/fd/-2147483648".len:0]u8 = undefined; + const proc_path = std.fmt.bufPrint(procfs_buf[0..], "/proc/self/fd/{}\x00", .{fd}) catch unreachable; + + const target = readlinkZ(@ptrCast([*:0]const u8, proc_path.ptr), out_buffer) catch |err| { + switch (err) { + error.UnsupportedReparsePointType => unreachable, // Windows only, + else => |e| return e, + } + }; + return target; + }, + else => @compileError("querying for canonical path of a handle is unsupported on this host"), + } } /// Spurious wakeups are possible and no precision of timing is guaranteed. diff --git a/lib/std/target/powerpc.zig b/lib/std/target/powerpc.zig index ffea7344fc..19b050c5bd 100644 --- a/lib/std/target/powerpc.zig +++ b/lib/std/target/powerpc.zig @@ -384,8 +384,8 @@ pub const all_features = blk: { }; pub const cpu = struct { - pub const @"440" = CpuModel{ - .name = "440", + pub const @"ppc440" = CpuModel{ + .name = "ppc440", .llvm_name = "440", .features = featureSet(&[_]Feature{ .booke, @@ -396,8 +396,8 @@ pub const cpu = struct { .msync, }), }; - pub const @"450" = CpuModel{ - .name = "450", + pub const @"ppc450" = CpuModel{ + .name = "ppc450", .llvm_name = "450", .features = featureSet(&[_]Feature{ .booke, @@ -408,70 +408,70 @@ pub const cpu = struct { .msync, }), }; - pub const @"601" = CpuModel{ - .name = "601", + pub const @"ppc601" = CpuModel{ + .name = "ppc601", .llvm_name = "601", .features = featureSet(&[_]Feature{ .fpu, }), }; - pub const @"602" = CpuModel{ - .name = "602", + pub const @"ppc602" = CpuModel{ + .name = "ppc602", .llvm_name = "602", .features = featureSet(&[_]Feature{ .fpu, }), }; - pub const @"603" = CpuModel{ - .name = "603", + pub const @"ppc603" = CpuModel{ + .name = "ppc603", .llvm_name = "603", .features = featureSet(&[_]Feature{ .fres, .frsqrte, }), }; - pub const @"603e" = CpuModel{ - .name = "603e", + pub const @"ppc603e" = CpuModel{ + .name = "ppc603e", .llvm_name = "603e", .features = featureSet(&[_]Feature{ .fres, .frsqrte, }), }; - pub const @"603ev" = CpuModel{ - .name = "603ev", + pub const @"ppc603ev" = CpuModel{ + .name = "ppc603ev", .llvm_name = "603ev", .features = featureSet(&[_]Feature{ .fres, .frsqrte, }), }; - pub const @"604" = CpuModel{ - .name = "604", + pub const @"ppc604" = CpuModel{ + .name = "ppc604", .llvm_name = "604", .features = featureSet(&[_]Feature{ .fres, .frsqrte, }), }; - pub const @"604e" = CpuModel{ - .name = "604e", + pub const @"ppc604e" = CpuModel{ + .name = "ppc604e", .llvm_name = "604e", .features = featureSet(&[_]Feature{ .fres, .frsqrte, }), }; - pub const @"620" = CpuModel{ - .name = "620", + pub const @"ppc620" = CpuModel{ + .name = "ppc620", .llvm_name = "620", .features = featureSet(&[_]Feature{ .fres, .frsqrte, }), }; - pub const @"7400" = CpuModel{ - .name = "7400", + pub const @"ppc7400" = CpuModel{ + .name = "ppc7400", .llvm_name = "7400", .features = featureSet(&[_]Feature{ .altivec, @@ -479,8 +479,8 @@ pub const cpu = struct { .frsqrte, }), }; - pub const @"7450" = CpuModel{ - .name = "7450", + pub const @"ppc7450" = CpuModel{ + .name = "ppc7450", .llvm_name = "7450", .features = featureSet(&[_]Feature{ .altivec, @@ -488,16 +488,16 @@ pub const cpu = struct { .frsqrte, }), }; - pub const @"750" = CpuModel{ - .name = "750", + pub const @"ppc750" = CpuModel{ + .name = "ppc750", .llvm_name = "750", .features = featureSet(&[_]Feature{ .fres, .frsqrte, }), }; - pub const @"970" = CpuModel{ - .name = "970", + pub const @"ppc970" = CpuModel{ + .name = "ppc970", .llvm_name = "970", .features = featureSet(&[_]Feature{ .@"64bit", |
