| ... | @@ -461,13 +461,16 @@ fn runResource( | ... | @@ -461,13 +461,16 @@ fn runResource( |
| 461 | }; | 461 | }; |
| 462 | defer tmp_directory.handle.close(); | 462 | defer tmp_directory.handle.close(); |
| 463 | | 463 | |
| 464 | // Unpack resource into tmp_directory. A non-null return value means | 464 | var unpack_result = try unpackResource(f, resource, uri_path, tmp_directory); |
| 465 | // that the package contents are inside a `pkg_dir` sub-directory. | 465 | defer unpack_result.deinit(); |
| 466 | const pkg_dir = try unpackResource(f, resource, uri_path, tmp_directory); | 466 | if (unpack_result.hasErrors()) { |
| | 467 | try unpack_result.bundleErrors(eb, try f.srcLoc(f.location_tok)); |
| | 468 | return error.FetchFailed; |
| | 469 | } |
| 467 | | 470 | |
| 468 | var pkg_path: Cache.Path = .{ | 471 | var pkg_path: Cache.Path = .{ |
| 469 | .root_dir = tmp_directory, | 472 | .root_dir = tmp_directory, |
| 470 | .sub_path = if (pkg_dir) |pkg_dir_name| pkg_dir_name else "", | 473 | .sub_path = if (unpack_result.root_dir) |root_dir| root_dir else "", |
| 471 | }; | 474 | }; |
| 472 | | 475 | |
| 473 | // Apply btrfs workaround if needed. Reopen tmp_directory. | 476 | // Apply btrfs workaround if needed. Reopen tmp_directory. |
| ... | @@ -500,8 +503,8 @@ fn runResource( | ... | @@ -500,8 +503,8 @@ fn runResource( |
| 500 | // directory. | 503 | // directory. |
| 501 | f.actual_hash = try computeHash(f, pkg_path, filter); | 504 | f.actual_hash = try computeHash(f, pkg_path, filter); |
| 502 | | 505 | |
| 503 | break :blk if (pkg_dir) |pkg_dir_name| | 506 | break :blk if (unpack_result.root_dir) |root_dir| |
| 504 | try fs.path.join(arena, &.{ tmp_dir_sub_path, pkg_dir_name }) | 507 | try fs.path.join(arena, &.{ tmp_dir_sub_path, root_dir }) |
| 505 | else | 508 | else |
| 506 | tmp_dir_sub_path; | 509 | tmp_dir_sub_path; |
| 507 | }; | 510 | }; |
| ... | @@ -1053,7 +1056,7 @@ fn unpackResource( | ... | @@ -1053,7 +1056,7 @@ fn unpackResource( |
| 1053 | resource: *Resource, | 1056 | resource: *Resource, |
| 1054 | uri_path: []const u8, | 1057 | uri_path: []const u8, |
| 1055 | tmp_directory: Cache.Directory, | 1058 | tmp_directory: Cache.Directory, |
| 1056 | ) RunError!?[]const u8 { | 1059 | ) RunError!UnpackResult { |
| 1057 | const eb = &f.error_bundle; | 1060 | const eb = &f.error_bundle; |
| 1058 | const file_type = switch (resource.*) { | 1061 | const file_type = switch (resource.*) { |
| 1059 | .file => FileType.fromPath(uri_path) orelse | 1062 | .file => FileType.fromPath(uri_path) orelse |
| ... | @@ -1121,7 +1124,8 @@ fn unpackResource( | ... | @@ -1121,7 +1124,8 @@ fn unpackResource( |
| 1121 | .{ uri_path, @errorName(err) }, | 1124 | .{ uri_path, @errorName(err) }, |
| 1122 | )); | 1125 | )); |
| 1123 | }; | 1126 | }; |
| 1124 | return null; | 1127 | const gpa = f.arena.child_allocator; |
| | 1128 | return UnpackResult.init(gpa); |
| 1125 | }, | 1129 | }, |
| 1126 | }; | 1130 | }; |
| 1127 | | 1131 | |
| ... | @@ -1156,23 +1160,19 @@ fn unpackResource( | ... | @@ -1156,23 +1160,19 @@ fn unpackResource( |
| 1156 | }); | 1160 | }); |
| 1157 | return try unpackTarball(f, tmp_directory.handle, dcp.reader()); | 1161 | return try unpackTarball(f, tmp_directory.handle, dcp.reader()); |
| 1158 | }, | 1162 | }, |
| 1159 | .git_pack => { | 1163 | .git_pack => return unpackGitPack(f, tmp_directory.handle, resource) catch |err| switch (err) { |
| 1160 | unpackGitPack(f, tmp_directory.handle, resource) catch |err| switch (err) { | 1164 | error.FetchFailed => return error.FetchFailed, |
| 1161 | error.FetchFailed => return error.FetchFailed, | 1165 | error.OutOfMemory => return error.OutOfMemory, |
| 1162 | error.OutOfMemory => return error.OutOfMemory, | 1166 | else => |e| return f.fail(f.location_tok, try eb.printString( |
| 1163 | else => |e| return f.fail(f.location_tok, try eb.printString( | 1167 | "unable to unpack git files: {s}", |
| 1164 | "unable to unpack git files: {s}", | 1168 | .{@errorName(e)}, |
| 1165 | .{@errorName(e)}, | 1169 | )), |
| 1166 | )), | | |
| 1167 | }; | | |
| 1168 | return null; | | |
| 1169 | }, | 1170 | }, |
| 1170 | } | 1171 | } |
| 1171 | } | 1172 | } |
| 1172 | | 1173 | |
| 1173 | fn unpackTarball(f: *Fetch, out_dir: fs.Dir, reader: anytype) RunError!?[]const u8 { | 1174 | fn unpackTarball(f: *Fetch, out_dir: fs.Dir, reader: anytype) RunError!UnpackResult { |
| 1174 | const eb = &f.error_bundle; | 1175 | const eb = &f.error_bundle; |
| 1175 | const arena = f.arena.allocator(); | | |
| 1176 | const gpa = f.arena.child_allocator; | 1176 | const gpa = f.arena.child_allocator; |
| 1177 | | 1177 | |
| 1178 | var diagnostics: std.tar.Diagnostics = .{ .allocator = gpa }; | 1178 | var diagnostics: std.tar.Diagnostics = .{ .allocator = gpa }; |
| ... | @@ -1188,10 +1188,11 @@ fn unpackTarball(f: *Fetch, out_dir: fs.Dir, reader: anytype) RunError!?[]const | ... | @@ -1188,10 +1188,11 @@ fn unpackTarball(f: *Fetch, out_dir: fs.Dir, reader: anytype) RunError!?[]const |
| 1188 | .{@errorName(err)}, | 1188 | .{@errorName(err)}, |
| 1189 | )); | 1189 | )); |
| 1190 | | 1190 | |
| | 1191 | var res = UnpackResult.init(gpa); |
| | 1192 | if (diagnostics.root_dir) |root_dir| { |
| | 1193 | res.root_dir = try gpa.dupe(u8, root_dir); |
| | 1194 | } |
| 1191 | if (diagnostics.errors.items.len > 0) { | 1195 | if (diagnostics.errors.items.len > 0) { |
| 1192 | var res = UnpackResult.init(gpa); | | |
| 1193 | defer res.deinit(); | | |
| 1194 | | | |
| 1195 | for (diagnostics.errors.items) |item| { | 1196 | for (diagnostics.errors.items) |item| { |
| 1196 | switch (item) { | 1197 | switch (item) { |
| 1197 | .unable_to_create_file => |i| try res.createFile(i.file_name, i.code), | 1198 | .unable_to_create_file => |i| try res.createFile(i.file_name, i.code), |
| ... | @@ -1199,21 +1200,17 @@ fn unpackTarball(f: *Fetch, out_dir: fs.Dir, reader: anytype) RunError!?[]const | ... | @@ -1199,21 +1200,17 @@ fn unpackTarball(f: *Fetch, out_dir: fs.Dir, reader: anytype) RunError!?[]const |
| 1199 | .unsupported_file_type => |i| try res.unsupportedFileType(i.file_name, @intFromEnum(i.file_type)), | 1200 | .unsupported_file_type => |i| try res.unsupportedFileType(i.file_name, @intFromEnum(i.file_type)), |
| 1200 | } | 1201 | } |
| 1201 | } | 1202 | } |
| 1202 | try res.bundleErrors(eb, "unable to unpack tarball", try f.srcLoc(f.location_tok)); | 1203 | try res.rootErrorMessage("unable to unpack tarball"); |
| 1203 | return error.FetchFailed; | | |
| 1204 | } | 1204 | } |
| 1205 | | 1205 | return res; |
| 1206 | return if (diagnostics.root_dir) |root_dir| | | |
| 1207 | return try arena.dupe(u8, root_dir) | | |
| 1208 | else | | |
| 1209 | null; | | |
| 1210 | } | 1206 | } |
| 1211 | | 1207 | |
| 1212 | fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource) anyerror!void { | 1208 | fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource) anyerror!UnpackResult { |
| 1213 | const eb = &f.error_bundle; | | |
| 1214 | const gpa = f.arena.child_allocator; | 1209 | const gpa = f.arena.child_allocator; |
| 1215 | const want_oid = resource.git.want_oid; | 1210 | const want_oid = resource.git.want_oid; |
| 1216 | const reader = resource.git.fetch_stream.reader(); | 1211 | const reader = resource.git.fetch_stream.reader(); |
| | 1212 | |
| | 1213 | var res = UnpackResult.init(gpa); |
| 1217 | // The .git directory is used to store the packfile and associated index, but | 1214 | // The .git directory is used to store the packfile and associated index, but |
| 1218 | // we do not attempt to replicate the exact structure of a real .git | 1215 | // we do not attempt to replicate the exact structure of a real .git |
| 1219 | // directory, since that isn't relevant for fetching a package. | 1216 | // directory, since that isn't relevant for fetching a package. |
| ... | @@ -1249,7 +1246,6 @@ fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource) anyerror!void | ... | @@ -1249,7 +1246,6 @@ fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource) anyerror!void |
| 1249 | try repository.checkout(out_dir, want_oid, &diagnostics); | 1246 | try repository.checkout(out_dir, want_oid, &diagnostics); |
| 1250 | | 1247 | |
| 1251 | if (diagnostics.errors.items.len > 0) { | 1248 | if (diagnostics.errors.items.len > 0) { |
| 1252 | var res = UnpackResult.init(gpa); | | |
| 1253 | defer res.deinit(); | 1249 | defer res.deinit(); |
| 1254 | | 1250 | |
| 1255 | for (diagnostics.errors.items) |item| { | 1251 | for (diagnostics.errors.items) |item| { |
| ... | @@ -1257,14 +1253,13 @@ fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource) anyerror!void | ... | @@ -1257,14 +1253,13 @@ fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource) anyerror!void |
| 1257 | .unable_to_create_sym_link => |i| try res.symLink(i.file_name, i.link_name, i.code), | 1253 | .unable_to_create_sym_link => |i| try res.symLink(i.file_name, i.link_name, i.code), |
| 1258 | } | 1254 | } |
| 1259 | } | 1255 | } |
| 1260 | try res.bundleErrors(eb, "unable to unpack packfile", try f.srcLoc(f.location_tok)); | 1256 | try res.rootErrorMessage("unable to unpack packfile"); |
| 1261 | | | |
| 1262 | return error.InvalidGitPack; | | |
| 1263 | } | 1257 | } |
| 1264 | } | 1258 | } |
| 1265 | } | 1259 | } |
| 1266 | | 1260 | |
| 1267 | try out_dir.deleteTree(".git"); | 1261 | try out_dir.deleteTree(".git"); |
| | 1262 | return res; |
| 1268 | } | 1263 | } |
| 1269 | | 1264 | |
| 1270 | fn recursiveDirectoryCopy(f: *Fetch, dir: fs.Dir, tmp_dir: fs.Dir) anyerror!void { | 1265 | fn recursiveDirectoryCopy(f: *Fetch, dir: fs.Dir, tmp_dir: fs.Dir) anyerror!void { |
| ... | @@ -1753,6 +1748,8 @@ test FileHeader { | ... | @@ -1753,6 +1748,8 @@ test FileHeader { |
| 1753 | const UnpackResult = struct { | 1748 | const UnpackResult = struct { |
| 1754 | allocator: std.mem.Allocator, | 1749 | allocator: std.mem.Allocator, |
| 1755 | errors: std.ArrayListUnmanaged(Error) = .{}, | 1750 | errors: std.ArrayListUnmanaged(Error) = .{}, |
| | 1751 | root_error_message: []const u8 = "", |
| | 1752 | root_dir: ?[]const u8 = null, |
| 1756 | | 1753 | |
| 1757 | const Error = union(enum) { | 1754 | const Error = union(enum) { |
| 1758 | unable_to_create_sym_link: struct { | 1755 | unable_to_create_sym_link: struct { |
| ... | @@ -1802,6 +1799,10 @@ const UnpackResult = struct { | ... | @@ -1802,6 +1799,10 @@ const UnpackResult = struct { |
| 1802 | item.free(self.allocator); | 1799 | item.free(self.allocator); |
| 1803 | } | 1800 | } |
| 1804 | self.errors.deinit(self.allocator); | 1801 | self.errors.deinit(self.allocator); |
| | 1802 | self.allocator.free(self.root_error_message); |
| | 1803 | if (self.root_dir) |root_dir| { |
| | 1804 | self.allocator.free(root_dir); |
| | 1805 | } |
| 1805 | self.* = undefined; | 1806 | self.* = undefined; |
| 1806 | } | 1807 | } |
| 1807 | | 1808 | |
| ... | @@ -1843,15 +1844,18 @@ const UnpackResult = struct { | ... | @@ -1843,15 +1844,18 @@ const UnpackResult = struct { |
| 1843 | } | 1844 | } |
| 1844 | } | 1845 | } |
| 1845 | | 1846 | |
| | 1847 | fn rootErrorMessage(self: *UnpackResult, msg: []const u8) !void { |
| | 1848 | self.root_error_message = try self.allocator.dupe(u8, msg); |
| | 1849 | } |
| | 1850 | |
| 1846 | fn bundleErrors( | 1851 | fn bundleErrors( |
| 1847 | self: *UnpackResult, | 1852 | self: *UnpackResult, |
| 1848 | eb: *ErrorBundle.Wip, | 1853 | eb: *ErrorBundle.Wip, |
| 1849 | msg: []const u8, | | |
| 1850 | src_loc: ErrorBundle.SourceLocationIndex, | 1854 | src_loc: ErrorBundle.SourceLocationIndex, |
| 1851 | ) !void { | 1855 | ) !void { |
| 1852 | const notes_len: u32 = @intCast(self.errors.items.len); | 1856 | const notes_len: u32 = @intCast(self.errors.items.len); |
| 1853 | try eb.addRootErrorMessage(.{ | 1857 | try eb.addRootErrorMessage(.{ |
| 1854 | .msg = try eb.addString(msg), | 1858 | .msg = try eb.addString(self.root_error_message), |
| 1855 | .src_loc = src_loc, | 1859 | .src_loc = src_loc, |
| 1856 | .notes_len = notes_len, | 1860 | .notes_len = notes_len, |
| 1857 | }); | 1861 | }); |