aboutsummaryrefslogtreecommitdiff
path: root/src/install/extract_tarball.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/install/extract_tarball.zig')
-rw-r--r--src/install/extract_tarball.zig281
1 files changed, 281 insertions, 0 deletions
diff --git a/src/install/extract_tarball.zig b/src/install/extract_tarball.zig
new file mode 100644
index 000000000..6057448e5
--- /dev/null
+++ b/src/install/extract_tarball.zig
@@ -0,0 +1,281 @@
+const Output = @import("../global.zig").Output;
+const strings = @import("../string_immutable.zig");
+const string = @import("../string_types.zig").string;
+const Resolution = @import("./resolution.zig").Resolution;
+const FileSystem = @import("../fs.zig").FileSystem;
+const Semver = @import("./semver.zig");
+const Integrity = @import("./integrity.zig").Integrity;
+const PackageID = @import("./install.zig").PackageID;
+const PackageManager = @import("./install.zig").PackageManager;
+const std = @import("std");
+const Npm = @import("./npm.zig");
+const ExtractTarball = @This();
+const default_allocator = @import("../global.zig").default_allocator;
+const Global = @import("../global.zig").Global;
+
+name: strings.StringOrTinyString,
+resolution: Resolution,
+registry: string,
+cache_dir: string,
+package_id: PackageID,
+extracted_file_count: usize = 0,
+skip_verify: bool = false,
+
+integrity: Integrity = Integrity{},
+
+pub inline fn run(this: ExtractTarball, bytes: []const u8) !string {
+ if (!this.skip_verify and this.integrity.tag.isSupported()) {
+ if (!this.integrity.verify(bytes)) {
+ Output.prettyErrorln("<r><red>Integrity check failed<r> for tarball: {s}", .{this.name.slice()});
+ Output.flush();
+ return error.IntegrityCheckFailed;
+ }
+ }
+ return this.extract(bytes);
+}
+
+pub fn buildURL(
+ registry_: string,
+ full_name_: strings.StringOrTinyString,
+ version: Semver.Version,
+ string_buf: []const u8,
+) !string {
+ return try buildURLWithPrinter(
+ registry_,
+ full_name_,
+ version,
+ string_buf,
+ @TypeOf(FileSystem.instance.dirname_store),
+ string,
+ anyerror,
+ FileSystem.instance.dirname_store,
+ FileSystem.DirnameStore.print,
+ );
+}
+
+pub fn buildURLWithWriter(
+ comptime Writer: type,
+ writer: Writer,
+ registry_: string,
+ full_name_: strings.StringOrTinyString,
+ version: Semver.Version,
+ string_buf: []const u8,
+) !void {
+ const Printer = struct {
+ writer: Writer,
+
+ pub fn print(this: @This(), comptime fmt: string, args: anytype) Writer.Error!void {
+ return try std.fmt.format(this.writer, fmt, args);
+ }
+ };
+
+ return try buildURLWithPrinter(
+ registry_,
+ full_name_,
+ version,
+ string_buf,
+ Printer,
+ void,
+ Writer.Error,
+ Printer{
+ .writer = writer,
+ },
+ Printer.print,
+ );
+}
+
+pub fn buildURLWithPrinter(
+ registry_: string,
+ full_name_: strings.StringOrTinyString,
+ version: Semver.Version,
+ string_buf: []const u8,
+ comptime PrinterContext: type,
+ comptime ReturnType: type,
+ comptime ErrorType: type,
+ printer: PrinterContext,
+ comptime print: fn (ctx: PrinterContext, comptime str: string, args: anytype) ErrorType!ReturnType,
+) ErrorType!ReturnType {
+ const registry = std.mem.trimRight(u8, registry_, "/");
+ const full_name = full_name_.slice();
+
+ var name = full_name;
+ if (name[0] == '@') {
+ if (std.mem.indexOfScalar(u8, name, '/')) |i| {
+ name = name[i + 1 ..];
+ }
+ }
+
+ const default_format = "{s}/{s}/-/";
+
+ if (!version.tag.hasPre() and !version.tag.hasBuild()) {
+ const args = .{ registry, full_name, name, version.major, version.minor, version.patch };
+ return try print(
+ printer,
+ default_format ++ "{s}-{d}.{d}.{d}.tgz",
+ args,
+ );
+ } else if (version.tag.hasPre() and version.tag.hasBuild()) {
+ const args = .{ registry, full_name, name, version.major, version.minor, version.patch, version.tag.pre.slice(string_buf), version.tag.build.slice(string_buf) };
+ return try print(
+ printer,
+ default_format ++ "{s}-{d}.{d}.{d}-{s}+{s}.tgz",
+ args,
+ );
+ } else if (version.tag.hasPre()) {
+ const args = .{ registry, full_name, name, version.major, version.minor, version.patch, version.tag.pre.slice(string_buf) };
+ return try print(
+ printer,
+ default_format ++ "{s}-{d}.{d}.{d}-{s}.tgz",
+ args,
+ );
+ } else if (version.tag.hasBuild()) {
+ const args = .{ registry, full_name, name, version.major, version.minor, version.patch, version.tag.build.slice(string_buf) };
+ return try print(
+ printer,
+ default_format ++ "{s}-{d}.{d}.{d}+{s}.tgz",
+ args,
+ );
+ } else {
+ unreachable;
+ }
+}
+
+threadlocal var abs_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
+threadlocal var abs_buf2: [std.fs.MAX_PATH_BYTES]u8 = undefined;
+
+fn extract(this: *const ExtractTarball, tgz_bytes: []const u8) !string {
+ var tmpdir = FileSystem.instance.tmpdir();
+ var tmpname_buf: [128]u8 = undefined;
+ const name = this.name.slice();
+
+ var basename = this.name.slice();
+ if (basename[0] == '@') {
+ if (std.mem.indexOfScalar(u8, basename, '/')) |i| {
+ basename = basename[i + 1 ..];
+ }
+ }
+
+ var tmpname = try FileSystem.instance.tmpname(basename, &tmpname_buf, tgz_bytes.len);
+
+ var cache_dir = tmpdir.makeOpenPath(std.mem.span(tmpname), .{ .iterate = true }) catch |err| {
+ Output.panic("err: {s} when create temporary directory named {s} (while extracting {s})", .{ @errorName(err), tmpname, name });
+ };
+ var temp_destination = std.os.getFdPath(cache_dir.fd, &abs_buf) catch |err| {
+ Output.panic("err: {s} when resolve path for temporary directory named {s} (while extracting {s})", .{ @errorName(err), tmpname, name });
+ };
+ cache_dir.close();
+
+ if (PackageManager.verbose_install) {
+ Output.prettyErrorln("[{s}] Start extracting {s}<r>", .{ name, tmpname });
+ Output.flush();
+ }
+
+ const Archive = @import("../libarchive/libarchive.zig").Archive;
+ const Zlib = @import("../zlib.zig");
+ var zlib_pool = Npm.Registry.BodyPool.get(default_allocator);
+ zlib_pool.data.reset();
+ defer Npm.Registry.BodyPool.release(zlib_pool);
+
+ var zlib_entry = try Zlib.ZlibReaderArrayList.init(tgz_bytes, &zlib_pool.data.list, default_allocator);
+ zlib_entry.readAll() catch |err| {
+ Output.prettyErrorln(
+ "<r><red>Error {s}<r> decompressing {s}",
+ .{
+ @errorName(err),
+ name,
+ },
+ );
+ Output.flush();
+ Global.crash();
+ };
+ const extracted_file_count = if (PackageManager.verbose_install)
+ try Archive.extractToDisk(
+ zlib_pool.data.list.items,
+ temp_destination,
+ null,
+ void,
+ void{},
+ // for npm packages, the root dir is always "package"
+ 1,
+ true,
+ true,
+ )
+ else
+ try Archive.extractToDisk(
+ zlib_pool.data.list.items,
+ temp_destination,
+ null,
+ void,
+ void{},
+ // for npm packages, the root dir is always "package"
+ 1,
+ true,
+ false,
+ );
+
+ if (PackageManager.verbose_install) {
+ Output.prettyErrorln(
+ "[{s}] Extracted<r>",
+ .{
+ name,
+ },
+ );
+ Output.flush();
+ }
+
+ var folder_name = PackageManager.cachedNPMPackageFolderNamePrint(&abs_buf2, name, this.resolution.value.npm);
+ if (folder_name.len == 0 or (folder_name.len == 1 and folder_name[0] == '/')) @panic("Tried to delete root and stopped it");
+ PackageManager.instance.cache_directory.deleteTree(folder_name) catch {};
+
+ // e.g. @next
+ // if it's a namespace package, we need to make sure the @name folder exists
+ if (basename.len != name.len) {
+ PackageManager.instance.cache_directory.makeDir(std.mem.trim(u8, name[0 .. name.len - basename.len], "/")) catch {};
+ }
+
+ // Now that we've extracted the archive, we rename.
+ std.os.renameatZ(tmpdir.fd, tmpname, PackageManager.instance.cache_directory.fd, folder_name) catch |err| {
+ Output.prettyErrorln(
+ "<r><red>Error {s}<r> moving {s} to cache dir:\n From: {s} To: {s}",
+ .{
+ @errorName(err),
+ name,
+ tmpname,
+ folder_name,
+ },
+ );
+ Output.flush();
+ Global.crash();
+ };
+
+ // We return a resolved absolute absolute file path to the cache dir.
+ // To get that directory, we open the directory again.
+ var final_dir = PackageManager.instance.cache_directory.openDirZ(folder_name, .{ .iterate = true }) catch |err| {
+ Output.prettyErrorln(
+ "<r><red>Error {s}<r> failed to verify cache dir for {s}",
+ .{
+ @errorName(err),
+ name,
+ },
+ );
+ Output.flush();
+ Global.crash();
+ };
+ defer final_dir.close();
+ // and get the fd path
+ var final_path = std.os.getFdPath(
+ final_dir.fd,
+ &abs_buf,
+ ) catch |err| {
+ Output.prettyErrorln(
+ "<r><red>Error {s}<r> failed to verify cache dir for {s}",
+ .{
+ @errorName(err),
+ name,
+ },
+ );
+ Output.flush();
+ Global.crash();
+ };
+ return try FileSystem.instance.dirname_store.append(@TypeOf(final_path), final_path);
+}