Created
March 9, 2025 10:11
-
-
Save fuzzy/d750e3f34dce8189a3c2bda8835e8253 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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