Skip to content

Instantly share code, notes, and snippets.

@fuzzy
Created March 9, 2025 10:11
Show Gist options
  • Save fuzzy/d750e3f34dce8189a3c2bda8835e8253 to your computer and use it in GitHub Desktop.
Save fuzzy/d750e3f34dce8189a3c2bda8835e8253 to your computer and use it in GitHub Desktop.
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <dirent.h>
#include <time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdint.h>
#define COLOR_RESET "\033[0m"
#define COLOR_YELLOW "\033[1;33m"
#define COLOR_GREEN "\033[1;32m"
#define COLOR_BLUE "\033[1;36m"
#define COLOR_RED "\033[1;31m"
#define COLOR_WHITE "\033[1;37m"
#define BUFFER_SIZE 4096
#define PATH_MAX 255 // Define PATH_MAX if not provided by system
volatile sig_atomic_t interrupted = 0;
volatile pid_t tar_pid = -1; // 🔹 Store tar process PID for cleanup
char checksum_filename[PATH_MAX];
void signal_handler(int sig) {
(void)sig;
interrupted = 1;
if (tar_pid > 0) {
kill(tar_pid, SIGTERM); // 🔹 Kill tar process
}
}
// CRC-32 Lookup Table
uint32_t crc32_table[256];
void init_crc32() {
uint32_t poly = 0xEDB88320;
for (uint32_t i = 0; i < 256; i++) {
uint32_t crc = i;
for (int j = 0; j < 8; j++) {
crc = (crc & 1) ? (crc >> 1) ^ poly : crc >> 1;
}
crc32_table[i] = crc;
}
}
// Compute CRC-32 checksum of a file
uint32_t crc32_file(const char *filename) {
FILE *file = fopen(filename, "rb");
if (!file) return 0;
uint32_t crc = 0xFFFFFFFF;
unsigned char buffer[BUFFER_SIZE];
size_t bytesRead;
while ((bytesRead = fread(buffer, 1, sizeof(buffer), file)) > 0) {
for (size_t i = 0; i < bytesRead; i++) {
crc = (crc >> 8) ^ crc32_table[(crc ^ buffer[i]) & 0xFF];
}
}
fclose(file);
return ~crc;
}
// Process tar output and compute CRC-32
void process_tar_output(FILE *pipe, size_t total_files, size_t total_size) {
char line[BUFFER_SIZE];
size_t processed_size = 0, processed_files = 0;
time_t start_time = time(NULL);
FILE *checksum_file = fopen(checksum_filename, "w");
if (!checksum_file) {
perror("Failed to open checksum file");
return;
}
while (fgets(line, sizeof(line), pipe) && !interrupted) {
char filename[PATH_MAX];
size_t file_size;
char file_type;
if (sscanf(line, "%c %*s %*s %zu %*s %*s %s", &file_type, &file_size, filename) < 3)
continue;
if (file_type != '-') continue; // Ignore directories, symlinks, etc.
processed_files++;
if (processed_files > total_files) {
processed_files = total_files;
}
processed_size += file_size;
// Compute CRC-32 for this file
uint32_t crc = crc32_file(filename);
fprintf(checksum_file, "%08X %s\n", crc, filename);
fflush(checksum_file);
// Progress calculation
char progress_size[32], speed_str[32], eta_str[10];
time_t elapsed = time(NULL) - start_time;
double speed = elapsed > 0 ? (double)processed_size / elapsed : 0;
time_t eta = (speed > 0 && processed_size < total_size) ? (total_size - processed_size) / speed : 0;
snprintf(progress_size, 32, "%.2f MB", processed_size / (1024.0 * 1024.0));
snprintf(speed_str, 32, "%.2f MB/s", speed / (1024.0 * 1024.0));
snprintf(eta_str, 10, "%02lu:%02lu:%02lu", eta / 3600, (eta % 3600) / 60, eta % 60);
printf("\r%sTime%s: %02lu:%02lu:%02lu %sFiles%s: %zu/%zu %sSize%s: %s %sSpeed%s: %s %sETA%s: %s ",
COLOR_BLUE, COLOR_WHITE, elapsed / 3600, (elapsed % 3600) / 60, elapsed % 60,
COLOR_BLUE, COLOR_WHITE, processed_files, total_files,
COLOR_BLUE, COLOR_WHITE, progress_size,
COLOR_BLUE, COLOR_WHITE, speed_str,
COLOR_BLUE, COLOR_WHITE, eta_str);
fflush(stdout);
}
fclose(checksum_file);
}
// 🔹 Function Definition for collect_file_info()
void collect_file_info(const char *path, size_t *total_size, size_t *total_files) {
struct stat statbuf;
// Use lstat() to properly check symlinks
if (lstat(path, &statbuf) == 0) {
if (S_ISDIR(statbuf.st_mode)) {
DIR *dir = opendir(path);
if (!dir) return;
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
char full_path[PATH_MAX];
snprintf(full_path, PATH_MAX, "%s/%s", path, entry->d_name);
collect_file_info(full_path, total_size, total_files);
}
closedir(dir);
}
// ✅ Only count **real regular files** (NO symlinks, sockets, FIFOs, devices)
else if (S_ISREG(statbuf.st_mode)) {
*total_size += statbuf.st_size;
(*total_files)++;
}
}
}
int main(int argc, char *argv[]) {
if (argc < 2) {
printf("Usage: ptar [tar arguments] /path/to/files ...\n");
return 1;
}
// Handle SIGINT (Ctrl+C)
signal(SIGINT, signal_handler);
// Initialize CRC-32 table
init_crc32();
// Generate a timestamped filename for the checksum log
snprintf(checksum_filename, PATH_MAX, "/tmp/ptar-%ld.crc32.txt", time(NULL));
// Allocate array for tar command arguments
char **tar_args = malloc((argc + 3) * sizeof(char *));
if (!tar_args) {
perror("Memory allocation failed");
return 1;
}
tar_args[0] = "tar";
int tar_argc = 1;
int positional_start = -1;
// Process arguments: Identify tar options vs. file paths
for (int i = 1; i < argc; i++) {
if (argv[i][0] == '-') {
tar_args[tar_argc++] = argv[i];
if ((strcmp(argv[i], "-f") == 0 || strcmp(argv[i], "-C") == 0) && i + 1 < argc) {
tar_args[tar_argc++] = argv[++i];
}
} else if (positional_start == -1) {
positional_start = i;
}
}
if (positional_start == -1) {
fprintf(stderr, "Error: No positional arguments (files/directories) specified.\n");
free(tar_args);
return 1;
}
for (int i = positional_start; i < argc; i++) {
tar_args[tar_argc++] = argv[i];
}
tar_args[tar_argc++] = "-vvv";
tar_args[tar_argc] = NULL;
size_t total_size = 0, total_files = 0;
printf("%sCollecting file information%s ...\r", COLOR_YELLOW, COLOR_WHITE);
fflush(stdout);
for (int i = positional_start; i < argc; i++) {
collect_file_info(argv[i], &total_size, &total_files);
}
printf("\r%sTotal files%s: %zu, %sTotal size%s: %.2f GB\n",
COLOR_BLUE, COLOR_WHITE, total_files, COLOR_BLUE, COLOR_WHITE, (double)total_size / (1024 * 1024 * 1024));
int pipe_fd[2];
pipe(pipe_fd);
pid_t pid = fork();
tar_pid = pid;
if (pid == 0) {
dup2(pipe_fd[1], STDOUT_FILENO);
execvp("tar", tar_args);
perror("tar execution failed");
exit(1);
}
close(pipe_fd[1]);
FILE *pipe = fdopen(pipe_fd[0], "r");
process_tar_output(pipe, total_files, total_size);
fclose(pipe);
waitpid(pid, NULL, 0);
printf("\n%sOperation complete.%s CRC-32 checksums saved to: %s\n", COLOR_GREEN, COLOR_WHITE, checksum_filename);
free(tar_args);
return interrupted ? 1 : 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment