Если добавить ключ --delete, утилита реально удаляет найденные старые файлы и печатает их список. Для каждого файла она может показать путь, размер, время последнего доступа (atime) и время изменения (mtime), а ключ -h выводит краткую справку по использованию.
####################################################
// cc -O2 -Wall -Wextra -o prune_atime prune_atime.c
#define _DEFAULT_SOURCE
#define _XOPEN_SOURCE 700
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <dirent.h>
#include <limits.h>
#include <unistd.h>
#include <sys/stat.h>
static int is_dot_name(const char *name) {
return strcmp(name, ".") == 0 || strcmp(name, "..") == 0;
}
static void format_time_local(time_t ts, char *buf, size_t size) {
struct tm tmv;
if (localtime_r(&ts, &tmv) == NULL) {
snprintf(buf, size, "1970-01-01 00:00:00");
return;
}
strftime(buf, size, "%Y-%m-%d %H:%M:%S", &tmv);
}
static void print_usage(const char *prog) {
printf("Usage: %s [DIR] [DAYS] [--delete]\n", prog);
printf("\n");
printf("Delete regular files whose atime is older than DAYS.\n");
printf("Default mode is preview (dry-run), nothing is removed.\n");
printf("\n");
printf("Arguments:\n");
printf(" DIR directory to scan, default: uploads\n");
printf(" DAYS age in days, default: 30\n");
printf(" --delete actually remove matched files\n");
printf("\n");
printf("Options:\n");
printf(" -h, --help show this help and exit\n");
printf("\n");
printf("Examples:\n");
printf(" %s\n", prog);
printf(" %s uploads 30\n", prog);
printf(" %s . 30 --delete\n", prog);
}
static void print_file_line(const char *tag, const char *path, const struct stat *st) {
char atime_buf[32];
char mtime_buf[32];
format_time_local(st->st_atime, atime_buf, sizeof(atime_buf));
format_time_local(st->st_mtime, mtime_buf, sizeof(mtime_buf));
printf("%-12s atime=%s mtime=%s size=%lld %s\n",
tag,
atime_buf,
mtime_buf,
(long long)st->st_size,
path);
}
static int walk_and_prune(const char *dirpath, time_t cutoff, int do_delete,
int *matched_count, int *kept_count, int *error_count) {
DIR *dir = opendir(dirpath);
if (!dir) {
fprintf(stderr, "opendir(%s): %s\n", dirpath, strerror(errno));
(*error_count)++;
return -1;
}
struct dirent *ent;
while ((ent = readdir(dir)) != NULL) {
if (is_dot_name(ent->d_name)) {
continue;
}
char path[PATH_MAX];
if (snprintf(path, sizeof(path), "%s/%s", dirpath, ent->d_name) >= (int)sizeof(path)) {
fprintf(stderr, "path too long: %s/%s\n", dirpath, ent->d_name);
(*error_count)++;
continue;
}
struct stat st;
if (lstat(path, &st) != 0) {
fprintf(stderr, "lstat(%s): %s\n", path, strerror(errno));
(*error_count)++;
continue;
}
if (S_ISDIR(st.st_mode)) {
walk_and_prune(path, cutoff, do_delete, matched_count, kept_count, error_count);
continue;
}
if (!S_ISREG(st.st_mode)) {
continue;
}
if (st.st_atime < cutoff) {
(*matched_count)++;
if (do_delete) {
if (unlink(path) == 0) {
print_file_line("deleted", path, &st);
} else {
fprintf(stderr, "unlink(%s): %s\n", path, strerror(errno));
(*error_count)++;
}
} else {
print_file_line("would-delete", path, &st);
}
} else {
(*kept_count)++;
}
}
closedir(dir);
return 0;
}
int main(int argc, char **argv) {
const char *dirpath = "uploads";
int days = 30;
int do_delete = 0;
if (argc >= 2) {
if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0) {
print_usage(argv[0]);
return 0;
}
dirpath = argv[1];
}
if (argc >= 3) {
if (strcmp(argv[2], "-h") == 0 || strcmp(argv[2], "--help") == 0) {
print_usage(argv[0]);
return 0;
}
days = atoi(argv[2]);
if (days < 0) {
fprintf(stderr, "invalid days: %s\n", argv[2]);
return 1;
}
}
if (argc >= 4) {
if (strcmp(argv[3], "-h") == 0 || strcmp(argv[3], "--help") == 0) {
print_usage(argv[0]);
return 0;
}
if (strcmp(argv[3], "--delete") == 0) {
do_delete = 1;
} else {
fprintf(stderr, "unknown option: %s\n\n", argv[3]);
print_usage(argv[0]);
return 1;
}
}
if (argc > 4) {
fprintf(stderr, "too many arguments\n\n");
print_usage(argv[0]);
return 1;
}
time_t now = time(NULL);
if (now == (time_t)-1) {
perror("time");
return 1;
}
time_t cutoff = now - (time_t)days * 86400;
char cutoff_buf[32];
format_time_local(cutoff, cutoff_buf, sizeof(cutoff_buf));
int matched_count = 0;
int kept_count = 0;
int error_count = 0;
printf("%s mode\n", do_delete ? "delete" : "dry-run");
printf("dir: %s\n", dirpath);
printf("days: %d\n", days);
printf("mode: %s\n", do_delete ? "delete" : "preview");
printf("cutoff epoch: %lld\n", (long long)cutoff);
printf("cutoff local: %s\n", cutoff_buf);
printf("\n");
walk_and_prune(dirpath, cutoff, do_delete, &matched_count, &kept_count, &error_count);
printf("\n");
printf("old files matched: %d\n", matched_count);
printf("recent files kept: %d\n", kept_count);
printf("errors: %d\n", error_count);
return error_count ? 1 : 0;
}
[file-name 000380_2026-07-05_10-17-41.txt]