// using zig-0.15.2 const std = @import("std"); const stdout = std.fs.File.stdout(); const stderr = std.fs.File.stderr(); fn err_print(comptime fmt: []const u8, args: anytype) void { var buf: [1024 * 16]u8 = undefined; var writer = stderr.writer(&buf); writer.interface.print(fmt, args) catch unreachable; writer.interface.flush() catch unreachable; } fn out_print(comptime fmt: []const u8, args: anytype) void { var buf: [1024 * 16]u8 = undefined; var writer = stdout.writer(&buf); writer.interface.print(fmt, args) catch unreachable; writer.interface.flush() catch unreachable; } const File = struct { path: []u8, size: u64, solved: bool = false, hash: ?u256 = null, }; var hashcount: usize = 0; fn cmp_file(path1: []const u8, path2: []const u8) !bool { var buf1: [1024 * 16]u8 = undefined; var buf2: [1024 * 16]u8 = undefined; const file1 = try std.fs.openFileAbsolute(path1, .{}); defer file1.close(); var f1reader = file1.reader(&buf1); const file2 = try std.fs.openFileAbsolute(path2, .{}); var f2reader = file2.reader(&buf2); defer file2.close(); while (true) { var contents1: [1024 * 16]u8 = undefined; var contents2: [1024 * 16]u8 = undefined; const n1 = try f1reader.interface.readSliceShort(&contents1); const n2 = try f2reader.interface.readSliceShort(&contents2); if (n1 != n2) return false; if (!std.mem.eql(u8, contents1[0..n1], contents2[0..n2])) return false; if (n1 < contents1.len) { return true; } } } pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; const allocator = gpa.allocator(); defer _ = gpa.deinit(); var list: std.ArrayList(File) = .empty; defer list.deinit(allocator); var args_it = std.process.args(); _ = args_it.next(); // skip program name var file_count: usize = 0; const start_time = try std.time.Instant.now(); while (args_it.next()) |arg| { const dir = try std.fs.cwd().openDir(arg, .{ .iterate = true }); var walker = try dir.walk(allocator); defer walker.deinit(); while (try walker.next()) |entry| { if (entry.kind == .file) { file_count += 1; const stat = try entry.dir.statFile(entry.basename); const path = try entry.dir.realpathAlloc(allocator, entry.basename); try list.append(allocator, .{ .path = path, .size = stat.size, }); } } } for (list.items, 0..) |*item, idx| { if (item.solved) continue; var duplicates: std.ArrayList(usize) = .empty; defer duplicates.deinit(allocator); for (list.items, 0..) |*item2, idx2| { if (item2.solved) continue; if (std.mem.eql(u8, item.path, item2.path)) continue; if (item.size == item2.size) { if (try cmp_file(item.path, item2.path)) { if (duplicates.items.len == 0) { try duplicates.append(allocator, idx); item.solved = true; } try duplicates.append(allocator, idx2); item2.solved = true; } } } if (duplicates.items.len > 0) { for (duplicates.items, 0..) |dup_idx, i| { if (i == 0) { out_print("duplicate ({}): \n {s}\n", .{ duplicates.items.len, list.items[duplicates.items[0]].path }); } else if (i == duplicates.items.len - 1) { out_print(" {s}\n", .{list.items[dup_idx].path}); } else { out_print(" {s}\n", .{list.items[dup_idx].path}); } } } } for (list.items) |*item| { allocator.free(item.path); } const end_time = try std.time.Instant.now(); const elapsed_nanos = end_time.since(start_time); const elapsed_seconds = @as(f64, @floatFromInt(elapsed_nanos)) / std.time.ns_per_s; err_print("found {} files in {:.2}s and hashcount is {}\n", .{ file_count, elapsed_seconds, hashcount }); }