diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2023-01-12 18:49:15 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-01-12 18:49:15 -0500 |
| commit | 7cb2f9222da38d687e8708dd5d94d3175cc77995 (patch) | |
| tree | ae6a7cf313236d085999c2a99fd13241704f3c74 /lib/std | |
| parent | cbbf8c8a2d77d84ce88ea1cef9a3e7d54081e33d (diff) | |
| parent | f4d6b37068db7ef3b5828dbe2403e65bf64a0f2c (diff) | |
| download | zig-7cb2f9222da38d687e8708dd5d94d3175cc77995.tar.gz zig-7cb2f9222da38d687e8708dd5d94d3175cc77995.zip | |
Merge pull request #14265 from ziglang/init-package-manager
Package Manager MVP
Diffstat (limited to 'lib/std')
| -rw-r--r-- | lib/std/Ini.zig | 66 | ||||
| -rw-r--r-- | lib/std/build.zig | 161 | ||||
| -rw-r--r-- | lib/std/build/LibExeObjStep.zig | 52 | ||||
| -rw-r--r-- | lib/std/compress/gzip.zig | 3 | ||||
| -rw-r--r-- | lib/std/http/Client.zig | 135 | ||||
| -rw-r--r-- | lib/std/io.zig | 1 | ||||
| -rw-r--r-- | lib/std/io/buffered_reader.zig | 8 | ||||
| -rw-r--r-- | lib/std/io/reader.zig | 14 | ||||
| -rw-r--r-- | lib/std/os.zig | 3 | ||||
| -rw-r--r-- | lib/std/std.zig | 2 | ||||
| -rw-r--r-- | lib/std/tar.zig | 172 |
11 files changed, 598 insertions, 19 deletions
diff --git a/lib/std/Ini.zig b/lib/std/Ini.zig new file mode 100644 index 0000000000..1965339186 --- /dev/null +++ b/lib/std/Ini.zig @@ -0,0 +1,66 @@ +bytes: []const u8, + +pub const SectionIterator = struct { + ini: Ini, + next_index: ?usize, + header: []const u8, + + pub fn next(it: *SectionIterator) ?[]const u8 { + const bytes = it.ini.bytes; + const start = it.next_index orelse return null; + const end = mem.indexOfPos(u8, bytes, start, "\n[") orelse bytes.len; + const result = bytes[start..end]; + if (mem.indexOfPos(u8, bytes, start, it.header)) |next_index| { + it.next_index = next_index + it.header.len; + } else { + it.next_index = null; + } + return result; + } +}; + +/// Asserts that `header` includes "\n[" at the beginning and "]\n" at the end. +/// `header` must remain valid for the lifetime of the iterator. +pub fn iterateSection(ini: Ini, header: []const u8) SectionIterator { + assert(mem.startsWith(u8, header, "\n[")); + assert(mem.endsWith(u8, header, "]\n")); + const first_header = header[1..]; + const next_index = if (mem.indexOf(u8, ini.bytes, first_header)) |i| + i + first_header.len + else + null; + return .{ + .ini = ini, + .next_index = next_index, + .header = header, + }; +} + +const std = @import("std.zig"); +const mem = std.mem; +const assert = std.debug.assert; +const Ini = @This(); +const testing = std.testing; + +test iterateSection { + const example = + \\[package] + \\name=libffmpeg + \\version=5.1.2 + \\ + \\[dependency] + \\id=libz + \\url=url1 + \\ + \\[dependency] + \\id=libmp3lame + \\url=url2 + ; + var ini: Ini = .{ .bytes = example }; + var it = ini.iterateSection("\n[dependency]\n"); + const section1 = it.next() orelse return error.TestFailed; + try testing.expectEqualStrings("id=libz\nurl=url1\n", section1); + const section2 = it.next() orelse return error.TestFailed; + try testing.expectEqualStrings("id=libmp3lame\nurl=url2", section2); + try testing.expect(it.next() == null); +} diff --git a/lib/std/build.zig b/lib/std/build.zig index 7ce8ae2d10..7980bde418 100644 --- a/lib/std/build.zig +++ b/lib/std/build.zig @@ -69,13 +69,15 @@ pub const Builder = struct { search_prefixes: ArrayList([]const u8), libc_file: ?[]const u8 = null, installed_files: ArrayList(InstalledFile), + /// Path to the directory containing build.zig. build_root: []const u8, cache_root: []const u8, global_cache_root: []const u8, release_mode: ?std.builtin.Mode, is_release: bool, + /// zig lib dir override_lib_dir: ?[]const u8, - vcpkg_root: VcpkgRoot, + vcpkg_root: VcpkgRoot = .unattempted, pkg_config_pkg_list: ?(PkgConfigError![]const PkgConfigPkg) = null, args: ?[][]const u8 = null, debug_log_scopes: []const []const u8 = &.{}, @@ -100,6 +102,8 @@ pub const Builder = struct { /// Information about the native target. Computed before build() is invoked. host: NativeTargetInfo, + dep_prefix: []const u8 = "", + pub const ExecError = error{ ReadFailure, ExitCodeFailure, @@ -223,7 +227,6 @@ pub const Builder = struct { .is_release = false, .override_lib_dir = null, .install_path = undefined, - .vcpkg_root = VcpkgRoot{ .unattempted = {} }, .args = null, .host = host, }; @@ -233,6 +236,92 @@ pub const Builder = struct { return self; } + fn createChild( + parent: *Builder, + dep_name: []const u8, + build_root: []const u8, + args: anytype, + ) !*Builder { + const child = try createChildOnly(parent, dep_name, build_root); + try applyArgs(child, args); + return child; + } + + fn createChildOnly(parent: *Builder, dep_name: []const u8, build_root: []const u8) !*Builder { + const allocator = parent.allocator; + const child = try allocator.create(Builder); + child.* = .{ + .allocator = allocator, + .install_tls = .{ + .step = Step.initNoOp(.top_level, "install", allocator), + .description = "Copy build artifacts to prefix path", + }, + .uninstall_tls = .{ + .step = Step.init(.top_level, "uninstall", allocator, makeUninstall), + .description = "Remove build artifacts from prefix path", + }, + .user_input_options = UserInputOptionsMap.init(allocator), + .available_options_map = AvailableOptionsMap.init(allocator), + .available_options_list = ArrayList(AvailableOption).init(allocator), + .verbose = parent.verbose, + .verbose_link = parent.verbose_link, + .verbose_cc = parent.verbose_cc, + .verbose_air = parent.verbose_air, + .verbose_llvm_ir = parent.verbose_llvm_ir, + .verbose_cimport = parent.verbose_cimport, + .verbose_llvm_cpu_features = parent.verbose_llvm_cpu_features, + .prominent_compile_errors = parent.prominent_compile_errors, + .color = parent.color, + .reference_trace = parent.reference_trace, + .invalid_user_input = false, + .zig_exe = parent.zig_exe, + .default_step = undefined, + .env_map = parent.env_map, + .top_level_steps = ArrayList(*TopLevelStep).init(allocator), + .install_prefix = undefined, + .dest_dir = parent.dest_dir, + .lib_dir = parent.lib_dir, + .exe_dir = parent.exe_dir, + .h_dir = parent.h_dir, + .install_path = parent.install_path, + .sysroot = parent.sysroot, + .search_prefixes = ArrayList([]const u8).init(allocator), + .libc_file = parent.libc_file, + .installed_files = ArrayList(InstalledFile).init(allocator), + .build_root = build_root, + .cache_root = parent.cache_root, + .global_cache_root = parent.global_cache_root, + .release_mode = parent.release_mode, + .is_release = parent.is_release, + .override_lib_dir = parent.override_lib_dir, + .debug_log_scopes = parent.debug_log_scopes, + .debug_compile_errors = parent.debug_compile_errors, + .enable_darling = parent.enable_darling, + .enable_qemu = parent.enable_qemu, + .enable_rosetta = parent.enable_rosetta, + .enable_wasmtime = parent.enable_wasmtime, + .enable_wine = parent.enable_wine, + .glibc_runtimes_dir = parent.glibc_runtimes_dir, + .host = parent.host, + .dep_prefix = parent.fmt("{s}{s}.", .{ parent.dep_prefix, dep_name }), + }; + try child.top_level_steps.append(&child.install_tls); + try child.top_level_steps.append(&child.uninstall_tls); + child.default_step = &child.install_tls.step; + return child; + } + + fn applyArgs(b: *Builder, args: anytype) !void { + // TODO this function is the way that a build.zig file communicates + // options to its dependencies. It is the programmatic way to give + // command line arguments to a build.zig script. + _ = args; + // TODO create a hash based on the args and the package hash, use this + // to compute the install prefix. + const install_prefix = b.pathJoin(&.{ b.cache_root, "pkg" }); + b.resolveInstallPrefix(install_prefix, .{}); + } + pub fn destroy(self: *Builder) void { self.env_map.deinit(); self.top_level_steps.deinit(); @@ -1068,6 +1157,10 @@ pub const Builder = struct { return self.addInstallFileWithDir(source.dupe(self), .lib, dest_rel_path); } + pub fn addInstallHeaderFile(b: *Builder, src_path: []const u8, dest_rel_path: []const u8) *InstallFileStep { + return b.addInstallFileWithDir(.{ .path = src_path }, .header, dest_rel_path); + } + pub fn addInstallRaw(self: *Builder, artifact: *LibExeObjStep, dest_filename: []const u8, options: InstallRawStep.CreateOptions) *InstallRawStep { return InstallRawStep.create(self, artifact, dest_filename, options); } @@ -1300,6 +1393,70 @@ pub const Builder = struct { &[_][]const u8{ base_dir, dest_rel_path }, ) catch unreachable; } + + pub const Dependency = struct { + builder: *Builder, + + pub fn artifact(d: *Dependency, name: []const u8) *LibExeObjStep { + var found: ?*LibExeObjStep = null; + for (d.builder.install_tls.step.dependencies.items) |dep_step| { + const inst = dep_step.cast(InstallArtifactStep) orelse continue; + if (mem.eql(u8, inst.artifact.name, name)) { + if (found != null) panic("artifact name '{s}' is ambiguous", .{name}); + found = inst.artifact; + } + } + return found orelse { + for (d.builder.install_tls.step.dependencies.items) |dep_step| { + const inst = dep_step.cast(InstallArtifactStep) orelse continue; + log.info("available artifact: '{s}'", .{inst.artifact.name}); + } + panic("unable to find artifact '{s}'", .{name}); + }; + } + }; + + pub fn dependency(b: *Builder, name: []const u8, args: anytype) *Dependency { + const build_runner = @import("root"); + const deps = build_runner.dependencies; + + inline for (@typeInfo(deps.imports).Struct.decls) |decl| { + if (mem.startsWith(u8, decl.name, b.dep_prefix) and + mem.endsWith(u8, decl.name, name) and + decl.name.len == b.dep_prefix.len + name.len) + { + const build_zig = @field(deps.imports, decl.name); + const build_root = @field(deps.build_root, decl.name); + return dependencyInner(b, name, build_root, build_zig, args); + } + } + + const full_path = b.pathFromRoot("build.zig.ini"); + std.debug.print("no dependency named '{s}' in '{s}'\n", .{ name, full_path }); + std.process.exit(1); + } + + fn dependencyInner( + b: *Builder, + name: []const u8, + build_root: []const u8, + comptime build_zig: type, + args: anytype, + ) *Dependency { + const sub_builder = b.createChild(name, build_root, args) catch unreachable; + sub_builder.runBuild(build_zig) catch unreachable; + const dep = b.allocator.create(Dependency) catch unreachable; + dep.* = .{ .builder = sub_builder }; + return dep; + } + + pub fn runBuild(b: *Builder, build_zig: anytype) anyerror!void { + switch (@typeInfo(@typeInfo(@TypeOf(build_zig.build)).Fn.return_type.?)) { + .Void => build_zig.build(b), + .ErrorUnion => try build_zig.build(b), + else => @compileError("expected return type of build to be 'void' or '!void'"), + } + } }; test "builder.findProgram compiles" { diff --git a/lib/std/build/LibExeObjStep.zig b/lib/std/build/LibExeObjStep.zig index 4795ec6222..f0e1e95006 100644 --- a/lib/std/build/LibExeObjStep.zig +++ b/lib/std/build/LibExeObjStep.zig @@ -108,6 +108,7 @@ object_src: []const u8, link_objects: ArrayList(LinkObject), include_dirs: ArrayList(IncludeDir), c_macros: ArrayList([]const u8), +installed_headers: ArrayList(*std.build.Step), output_dir: ?[]const u8, is_linking_libc: bool = false, is_linking_libcpp: bool = false, @@ -370,6 +371,7 @@ fn initExtraArgs( .lib_paths = ArrayList([]const u8).init(builder.allocator), .rpaths = ArrayList([]const u8).init(builder.allocator), .framework_dirs = ArrayList([]const u8).init(builder.allocator), + .installed_headers = ArrayList(*std.build.Step).init(builder.allocator), .object_src = undefined, .c_std = Builder.CStd.C99, .override_lib_dir = null, @@ -472,6 +474,27 @@ pub fn installRaw(self: *LibExeObjStep, dest_filename: []const u8, options: Inst return self.builder.installRaw(self, dest_filename, options); } +pub fn installHeader(a: *LibExeObjStep, src_path: []const u8) void { + const basename = fs.path.basename(src_path); + const install_file = a.builder.addInstallHeaderFile(src_path, basename); + a.builder.getInstallStep().dependOn(&install_file.step); + a.installed_headers.append(&install_file.step) catch unreachable; +} + +pub fn installHeadersDirectory( + a: *LibExeObjStep, + src_dir_path: []const u8, + dest_rel_path: []const u8, +) void { + const install_dir = a.builder.addInstallDirectory(.{ + .source_dir = src_dir_path, + .install_dir = .header, + .install_subdir = dest_rel_path, + }); + a.builder.getInstallStep().dependOn(&install_dir.step); + a.installed_headers.append(&install_dir.step) catch unreachable; +} + /// Creates a `RunStep` with an executable built with `addExecutable`. /// Add command line arguments with `addArg`. pub fn run(exe: *LibExeObjStep) *RunStep { @@ -1362,7 +1385,7 @@ fn make(step: *Step) !void { if (self.libc_file) |libc_file| { try zig_args.append("--libc"); - try zig_args.append(libc_file.getPath(self.builder)); + try zig_args.append(libc_file.getPath(builder)); } else if (builder.libc_file) |libc_file| { try zig_args.append("--libc"); try zig_args.append(libc_file); @@ -1577,7 +1600,7 @@ fn make(step: *Step) !void { } else { const need_cross_glibc = self.target.isGnuLibC() and self.is_linking_libc; - switch (self.builder.host.getExternalExecutor(self.target_info, .{ + switch (builder.host.getExternalExecutor(self.target_info, .{ .qemu_fixes_dl = need_cross_glibc and builder.glibc_runtimes_dir != null, .link_libc = self.is_linking_libc, })) { @@ -1661,7 +1684,7 @@ fn make(step: *Step) !void { switch (include_dir) { .raw_path => |include_path| { try zig_args.append("-I"); - try zig_args.append(self.builder.pathFromRoot(include_path)); + try zig_args.append(builder.pathFromRoot(include_path)); }, .raw_path_system => |include_path| { if (builder.sysroot != null) { @@ -1670,7 +1693,7 @@ fn make(step: *Step) !void { try zig_args.append("-isystem"); } - const resolved_include_path = self.builder.pathFromRoot(include_path); + const resolved_include_path = builder.pathFromRoot(include_path); const common_include_path = if (builtin.os.tag == .windows and builder.sysroot != null and fs.path.isAbsolute(resolved_include_path)) blk: { // We need to check for disk designator and strip it out from dir path so @@ -1686,10 +1709,21 @@ fn make(step: *Step) !void { try zig_args.append(common_include_path); }, - .other_step => |other| if (other.emit_h) { - const h_path = other.getOutputHSource().getPath(self.builder); - try zig_args.append("-isystem"); - try zig_args.append(fs.path.dirname(h_path).?); + .other_step => |other| { + if (other.emit_h) { + const h_path = other.getOutputHSource().getPath(builder); + try zig_args.append("-isystem"); + try zig_args.append(fs.path.dirname(h_path).?); + } + if (other.installed_headers.items.len > 0) { + for (other.installed_headers.items) |install_step| { + try install_step.make(); + } + try zig_args.append("-I"); + try zig_args.append(builder.pathJoin(&.{ + other.builder.install_prefix, "include", + })); + } }, .config_header_step => |config_header| { try zig_args.append("-I"); @@ -1790,7 +1824,7 @@ fn make(step: *Step) !void { if (self.override_lib_dir) |dir| { try zig_args.append("--zig-lib-dir"); try zig_args.append(builder.pathFromRoot(dir)); - } else if (self.builder.override_lib_dir) |dir| { + } else if (builder.override_lib_dir) |dir| { try zig_args.append("--zig-lib-dir"); try zig_args.append(builder.pathFromRoot(dir)); } diff --git a/lib/std/compress/gzip.zig b/lib/std/compress/gzip.zig index d7a2cb0094..e9e57cc550 100644 --- a/lib/std/compress/gzip.zig +++ b/lib/std/compress/gzip.zig @@ -17,6 +17,9 @@ const FCOMMENT = 1 << 4; const max_string_len = 1024; +/// TODO: the fully qualified namespace to this declaration is +/// std.compress.gzip.GzipStream which has a redundant "gzip" in the name. +/// Instead, it should be `std.compress.gzip.Stream`. pub fn GzipStream(comptime ReaderType: type) type { return struct { const Self = @This(); diff --git a/lib/std/http/Client.zig b/lib/std/http/Client.zig index 85708c4687..e97b366370 100644 --- a/lib/std/http/Client.zig +++ b/lib/std/http/Client.zig @@ -524,11 +524,133 @@ pub const Request = struct { req.* = undefined; } + pub const Reader = std.io.Reader(*Request, ReadError, read); + + pub fn reader(req: *Request) Reader { + return .{ .context = req }; + } + pub fn readAll(req: *Request, buffer: []u8) !usize { return readAtLeast(req, buffer, buffer.len); } - pub fn read(req: *Request, buffer: []u8) !usize { + pub const ReadError = net.Stream.ReadError || error{ + // From HTTP protocol + HttpHeadersInvalid, + HttpHeadersExceededSizeLimit, + HttpRedirectMissingLocation, + HttpTransferEncodingUnsupported, + HttpContentLengthUnknown, + TooManyHttpRedirects, + ShortHttpStatusLine, + BadHttpVersion, + HttpHeaderContinuationsUnsupported, + UnsupportedUrlScheme, + UriMissingHost, + UnknownHostName, + + // Network problems + NetworkUnreachable, + HostLacksNetworkAddresses, + TemporaryNameServerFailure, + NameServerFailure, + ProtocolFamilyNotAvailable, + ProtocolNotSupported, + + // System resource problems + ProcessFdQuotaExceeded, + SystemFdQuotaExceeded, + OutOfMemory, + + // TLS problems + InsufficientEntropy, + TlsConnectionTruncated, + TlsRecordOverflow, + TlsDecodeError, + TlsAlert, + TlsBadRecordMac, + TlsBadLength, + TlsIllegalParameter, + TlsUnexpectedMessage, + TlsDecryptFailure, + CertificateFieldHasInvalidLength, + CertificateHostMismatch, + CertificatePublicKeyInvalid, + CertificateExpired, + CertificateFieldHasWrongDataType, + CertificateIssuerMismatch, + CertificateNotYetValid, + CertificateSignatureAlgorithmMismatch, + CertificateSignatureAlgorithmUnsupported, + CertificateSignatureInvalid, + CertificateSignatureInvalidLength, + CertificateSignatureNamedCurveUnsupported, + CertificateSignatureUnsupportedBitCount, + TlsCertificateNotVerified, + TlsBadSignatureScheme, + TlsBadRsaSignatureBitCount, + TlsDecryptError, + UnsupportedCertificateVersion, + CertificateTimeInvalid, + CertificateHasUnrecognizedObjectId, + CertificateHasInvalidBitString, + + // TODO: convert to higher level errors + InvalidFormat, + InvalidPort, + UnexpectedCharacter, + Overflow, + InvalidCharacter, + AddressFamilyNotSupported, + AddressInUse, + AddressNotAvailable, + ConnectionPending, + ConnectionRefused, + FileNotFound, + PermissionDenied, + ServiceUnavailable, + SocketTypeNotSupported, + FileTooBig, + LockViolation, + NoSpaceLeft, + NotOpenForWriting, + InvalidEncoding, + IdentityElement, + NonCanonical, + SignatureVerificationFailed, + MessageTooLong, + NegativeIntoUnsigned, + TargetTooSmall, + BufferTooSmall, + InvalidSignature, + NotSquare, + DiskQuota, + InvalidEnd, + Incomplete, + InvalidIpv4Mapping, + InvalidIPAddressFormat, + BadPathName, + DeviceBusy, + FileBusy, + FileLocksNotSupported, + InvalidHandle, + InvalidUtf8, + NameTooLong, + NoDevice, + PathAlreadyExists, + PipeBusy, + SharingViolation, + SymLinkLoop, + FileSystem, + InterfaceNotFound, + AlreadyBound, + FileDescriptorNotASocket, + NetworkSubsystemFailed, + NotDir, + ReadOnlyFileSystem, + }; + + pub fn read(req: *Request, buffer: []u8) ReadError!usize { return readAtLeast(req, buffer, 1); } @@ -671,7 +793,8 @@ pub const Request = struct { } }, .chunk_data => { - const sub_amt = @min(req.response.next_chunk_length, in.len); + // TODO https://github.com/ziglang/zig/issues/14039 + const sub_amt = @intCast(usize, @min(req.response.next_chunk_length, in.len)); req.response.next_chunk_length -= sub_amt; if (req.response.next_chunk_length > 0) { if (in.ptr == buffer.ptr) { @@ -709,11 +832,15 @@ pub const Request = struct { } }; -pub fn deinit(client: *Client, gpa: Allocator) void { - client.ca_bundle.deinit(gpa); +pub fn deinit(client: *Client) void { + client.ca_bundle.deinit(client.allocator); client.* = undefined; } +pub fn rescanRootCertificates(client: *Client) !void { + return client.ca_bundle.rescan(client.allocator); +} + pub fn connect(client: *Client, host: []const u8, port: u16, protocol: Connection.Protocol) !Connection { var conn: Connection = .{ .stream = try net.tcpConnectToHost(client.allocator, host, port), diff --git a/lib/std/io.zig b/lib/std/io.zig index d2a59a61a8..a61f2a4e0e 100644 --- a/lib/std/io.zig +++ b/lib/std/io.zig @@ -114,6 +114,7 @@ pub const bufferedWriter = @import("io/buffered_writer.zig").bufferedWriter; pub const BufferedReader = @import("io/buffered_reader.zig").BufferedReader; pub const bufferedReader = @import("io/buffered_reader.zig").bufferedReader; +pub const bufferedReaderSize = @import("io/buffered_reader.zig").bufferedReaderSize; pub const PeekStream = @import("io/peek_stream.zig").PeekStream; pub const peekStream = @import("io/peek_stream.zig").peekStream; diff --git a/lib/std/io/buffered_reader.zig b/lib/std/io/buffered_reader.zig index aca665fb30..2f6677c6a5 100644 --- a/lib/std/io/buffered_reader.zig +++ b/lib/std/io/buffered_reader.zig @@ -45,8 +45,12 @@ pub fn BufferedReader(comptime buffer_size: usize, comptime ReaderType: type) ty }; } -pub fn bufferedReader(underlying_stream: anytype) BufferedReader(4096, @TypeOf(underlying_stream)) { - return .{ .unbuffered_reader = underlying_stream }; +pub fn bufferedReader(reader: anytype) BufferedReader(4096, @TypeOf(reader)) { + return .{ .unbuffered_reader = reader }; +} + +pub fn bufferedReaderSize(comptime size: usize, reader: anytype) BufferedReader(size, @TypeOf(reader)) { + return .{ .unbuffered_reader = reader }; } test "io.BufferedReader OneByte" { diff --git a/lib/std/io/reader.zig b/lib/std/io/reader.zig index b86aca6cbd..f9d3085a29 100644 --- a/lib/std/io/reader.zig +++ b/lib/std/io/reader.zig @@ -30,10 +30,20 @@ pub fn Reader( /// means the stream reached the end. Reaching the end of a stream is not an error /// condition. pub fn readAll(self: Self, buffer: []u8) Error!usize { + return readAtLeast(self, buffer, buffer.len); + } + + /// Returns the number of bytes read, calling the underlying read + /// function the minimal number of times until the buffer has at least + /// `len` bytes filled. If the number read is less than `len` it means + /// the stream reached the end. Reaching the end of the stream is not + /// an error condition. + pub fn readAtLeast(self: Self, buffer: []u8, len: usize) Error!usize { + assert(len <= buffer.len); var index: usize = 0; - while (index != buffer.len) { + while (index < len) { const amt = try self.read(buffer[index..]); - if (amt == 0) return index; + if (amt == 0) break; index += amt; } return index; diff --git a/lib/std/os.zig b/lib/std/os.zig index ffc294f0e6..22cdf30351 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -2414,6 +2414,9 @@ pub fn unlinkatW(dirfd: fd_t, sub_path_w: []const u16, flags: u32) UnlinkatError 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. + /// + /// On Windows, this error may be returned instead of PathAlreadyExists when + /// renaming a directory over an existing directory. AccessDenied, FileBusy, DiskQuota, diff --git a/lib/std/std.zig b/lib/std/std.zig index 87772bf8f8..ba52784b45 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -21,6 +21,7 @@ pub const EnumMap = enums.EnumMap; pub const EnumSet = enums.EnumSet; pub const HashMap = hash_map.HashMap; pub const HashMapUnmanaged = hash_map.HashMapUnmanaged; +pub const Ini = @import("Ini.zig"); pub const MultiArrayList = @import("multi_array_list.zig").MultiArrayList; pub const PackedIntArray = @import("packed_int_array.zig").PackedIntArray; pub const PackedIntArrayEndian = @import("packed_int_array.zig").PackedIntArrayEndian; @@ -85,6 +86,7 @@ pub const rand = @import("rand.zig"); pub const sort = @import("sort.zig"); pub const simd = @import("simd.zig"); pub const ascii = @import("ascii.zig"); +pub const tar = @import("tar.zig"); pub const testing = @import("testing.zig"); pub const time = @import("time.zig"); pub const tz = @import("tz.zig"); diff --git a/lib/std/tar.zig b/lib/std/tar.zig new file mode 100644 index 0000000000..4f6a77c6ba --- /dev/null +++ b/lib/std/tar.zig @@ -0,0 +1,172 @@ +pub const Options = struct { + /// Number of directory levels to skip when extracting files. + strip_components: u32 = 0, +}; + +pub const Header = struct { + bytes: *const [512]u8, + + pub const FileType = enum(u8) { + normal = '0', + hard_link = '1', + symbolic_link = '2', + character_special = '3', + block_special = '4', + directory = '5', + fifo = '6', + contiguous = '7', + global_extended_header = 'g', + extended_header = 'x', + _, + }; + + pub fn fileSize(header: Header) !u64 { + const raw = header.bytes[124..][0..12]; + const ltrimmed = std.mem.trimLeft(u8, raw, "0"); + const rtrimmed = std.mem.trimRight(u8, ltrimmed, "\x00"); + if (rtrimmed.len == 0) return 0; + return std.fmt.parseInt(u64, rtrimmed, 8); + } + + pub fn is_ustar(header: Header) bool { + return std.mem.eql(u8, header.bytes[257..][0..6], "ustar\x00"); + } + + /// Includes prefix concatenated, if any. + /// Return value may point into Header buffer, or might point into the + /// argument buffer. + /// TODO: check against "../" and other nefarious things + pub fn fullFileName(header: Header, buffer: *[255]u8) ![]const u8 { + const n = name(header); + if (!is_ustar(header)) + return n; + const p = prefix(header); + if (p.len == 0) + return n; + std.mem.copy(u8, buffer[0..p.len], p); + buffer[p.len] = '/'; + std.mem.copy(u8, buffer[p.len + 1 ..], n); + return buffer[0 .. p.len + 1 + n.len]; + } + + pub fn name(header: Header) []const u8 { + return str(header, 0, 0 + 100); + } + + pub fn prefix(header: Header) []const u8 { + return str(header, 345, 345 + 155); + } + + pub fn fileType(header: Header) FileType { + const result = @intToEnum(FileType, header.bytes[156]); + return if (result == @intToEnum(FileType, 0)) .normal else result; + } + + fn str(header: Header, start: usize, end: usize) []const u8 { + var i: usize = start; + while (i < end) : (i += 1) { + if (header.bytes[i] == 0) break; + } + return header.bytes[start..i]; + } +}; + +pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !void { + var file_name_buffer: [255]u8 = undefined; + var buffer: [512 * 8]u8 = undefined; + var start: usize = 0; + var end: usize = 0; + header: while (true) { + if (buffer.len - start < 1024) { + std.mem.copy(u8, &buffer, buffer[start..end]); + end -= start; + start = 0; + } + const ask_header = @min(buffer.len - end, 1024 -| (end - start)); + end += try reader.readAtLeast(buffer[end..], ask_header); + switch (end - start) { + 0 => return, + 1...511 => return error.UnexpectedEndOfStream, + else => {}, + } + const header: Header = .{ .bytes = buffer[start..][0..512] }; + start += 512; + const file_size = try header.fileSize(); + const rounded_file_size = std.mem.alignForwardGeneric(u64, file_size, 512); + const pad_len = @intCast(usize, rounded_file_size - file_size); + const unstripped_file_name = try header.fullFileName(&file_name_buffer); + switch (header.fileType()) { + .directory => { + const file_name = try stripComponents(unstripped_file_name, options.strip_components); + if (file_name.len != 0) { + try dir.makeDir(file_name); + } + }, + .normal => { + if (file_size == 0 and unstripped_file_name.len == 0) return; + const file_name = try stripComponents(unstripped_file_name, options.strip_components); + + var file = try dir.createFile(file_name, .{}); + defer file.close(); + + var file_off: usize = 0; + while (true) { + if (buffer.len - start < 1024) { + std.mem.copy(u8, &buffer, buffer[start..end]); + end -= start; + start = 0; + } + // Ask for the rounded up file size + 512 for the next header. + // TODO: https://github.com/ziglang/zig/issues/14039 + const ask = @intCast(usize, @min( + buffer.len - end, + rounded_file_size + 512 - file_off -| (end - start), + )); + end += try reader.readAtLeast(buffer[end..], ask); + if (end - start < ask) return error.UnexpectedEndOfStream; + // TODO: https://github.com/ziglang/zig/issues/14039 + const slice = buffer[start..@intCast(usize, @min(file_size - file_off + start, end))]; + try file.writeAll(slice); + file_off += slice.len; + start += slice.len; + if (file_off >= file_size) { + start += pad_len; + // Guaranteed since we use a buffer divisible by 512. + assert(start <= end); + continue :header; + } + } + }, + .global_extended_header, .extended_header => { + if (start + rounded_file_size > end) return error.TarHeadersTooBig; + start = @intCast(usize, start + rounded_file_size); + }, + .hard_link => return error.TarUnsupportedFileType, + .symbolic_link => return error.TarUnsupportedFileType, + else => return error.TarUnsupportedFileType, + } + } +} + +fn stripComponents(path: []const u8, count: u32) ![]const u8 { + var i: usize = 0; + var c = count; + while (c > 0) : (c -= 1) { + if (std.mem.indexOfScalarPos(u8, path, i, '/')) |pos| { + i = pos + 1; + } else { + return error.TarComponentsOutsideStrippedPrefix; + } + } + return path[i..]; +} + +test stripComponents { + const expectEqualStrings = std.testing.expectEqualStrings; + try expectEqualStrings("a/b/c", try stripComponents("a/b/c", 0)); + try expectEqualStrings("b/c", try stripComponents("a/b/c", 1)); + try expectEqualStrings("c", try stripComponents("a/b/c", 2)); +} + +const std = @import("std.zig"); +const assert = std.debug.assert; |
