Compare commits

...

5 Commits

Author SHA1 Message Date
Érico Nogueira 657fd48d42 Free task list before exiting.
This allows us to get a clean valgrind pass.

    valgrind -s --soname-synonyms=somalloc=NONE --leak-check=full erm -r u-boot-2021.10/

      ==24385== Memcheck, a memory error detector
      ==24385== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
      ==24385== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
      ==24385== Command: erm -r u-boot-2021.10/
      ==24385==
      ==24385==
      ==24385== HEAP SUMMARY:
      ==24385==     in use at exit: 0 bytes in 0 blocks
      ==24385==   total heap usage: 4,047 allocs, 4,047 frees, 3,850,073 bytes allocated
      ==24385==
      ==24385== All heap blocks were freed -- no leaks are possible
      ==24385==
      ==24385== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
2022-01-21 16:07:21 -03:00
Érico Nogueira 9a1eecd457 Protect against fdopendir failure. 2022-01-21 16:00:02 -03:00
Érico Nogueira aa22e06b23 Don't allocate thread list.
Since we aren't using pthread_join to finish the program or tracking
threads individually, we can simply create the thread and detach it.
2022-01-21 15:47:20 -03:00
Érico Nogueira 5b4396c4a1 Improve error handling and error messages.
- Rrror out clearly on most allocation failures (the others will simply
  segfault). Avoid getting into weird program conditions in case some
  operations fail.
- Improve organization of main(), no function pointer usage should be
  necessary.
- Make main thread also a worker thread: avoid leaving a useless thread
  around simply waiting for others to complete. Also means one less
  thread to launch.
2022-01-21 15:45:34 -03:00
Érico Nogueira 3f72eb9f5f Protect directory traversal from TOCTOU issues.
This is usually not being run as root so it isn't a security
vulnerability, but in the interest of security, we should open the
directory using open() with the appropriate flags to avoid following a
symlink erroneously.

Inspired by [1].

[1] https://blog.rust-lang.org/2022/01/20/cve-2022-21658.html
2022-01-21 15:45:30 -03:00
3 changed files with 70 additions and 62 deletions

22
erm.c
View File

@ -40,24 +40,22 @@ int main(int argc, char **argv)
usage(1);
}
file_action action = recursive ? recurse_into : single_file;
callback_action callback = recursive ? run_queue : NULL;
const char *err_fmt = recursive ?
"failed to queue '%s': %s\n" : "failed to remove '%s': %s\n";
int rv = 0;
for (int i = 0; i < argc; i++) {
const char *path = argv[i];
if (action(path)) {
fprintf(stderr, err_fmt, path, strerror(errno));
if (stop_at_error) {
return 1;
} else {
rv = 1;
if (recursive) {
recurse_into(path, stop_at_error);
} else {
if (single_file(path)) {
if (stop_at_error) {
return 1;
} else {
rv = 1;
}
}
}
}
if (callback) callback();
if (recursive) run_queue();
return rv;
}

7
erm.h
View File

@ -1,7 +1,4 @@
typedef int (*file_action)(const char *path);
typedef int (*callback_action)(void);
/* remove.c */
int recurse_into(const char *);
void recurse_into(const char *, int);
void run_queue(void);
int single_file(const char *);
int run_queue(void);

103
remove.c
View File

@ -54,30 +54,23 @@ static inline void queue_print(struct queue *q)
#endif
}
static inline int queue_add(struct queue *q, char *path, struct task *parent)
static inline void queue_add(struct queue *q, char *path, struct task *parent)
{
int rv = 0;
pthread_mutex_lock(&q->mtx);
if (q->len + 1 > q->size) {
q->size *= 2;
if (q->size == 0) q->size = 32;
void *t = realloc(q->tasks, q->size * sizeof(struct task));
if (!t) {
rv = -1;
goto error;
q->tasks = realloc(q->tasks, q->size * sizeof(struct task));
if (!q->tasks) {
fprintf(stderr, "queue memory exhaustion: %m\n");
exit(1);
}
q->tasks = t;
}
struct task t = {.path = path, .parent = parent};
q->tasks[q->len++] = t;
q->tasks[q->len++] = (struct task){.path = path, .parent = parent};
pthread_cond_signal(&q->cond);
error:
pthread_mutex_unlock(&q->mtx);
return rv;
}
static inline void queue_remove(struct queue *q, struct task *t)
@ -86,6 +79,7 @@ static inline void queue_remove(struct queue *q, struct task *t)
while (q->len == 0) {
if (q->free == nproc - 1) {
/* we are done removing things */
free(q->tasks);
exit(0);
}
q->free++;
@ -138,18 +132,23 @@ static void *process_queue_item(void *arg)
while (1) {
queue_remove(q, &t);
DIR *d;
while (!(d = opendir(t.path))) {
int dfd;
while ((dfd = open(t.path, O_RDONLY|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC)) < 0) {
if (errno == EMFILE) {
pthread_mutex_lock(&fd_mtx);
pthread_cond_wait(&fd_cond, &fd_mtx);
pthread_mutex_unlock(&fd_mtx);
continue;
} else {
break;
fprintf(stderr, "couldn't open '%s': %m\n", t.path);
exit(1);
}
}
int dfd = dirfd(d);
DIR *d = fdopendir(dfd);
if (!d) {
fprintf(stderr, "couldn't create directory stream: %m\n");
exit(1);
}
struct task *p = NULL;
unsigned n = 0;
@ -222,51 +221,65 @@ fast_path_dir:
return NULL;
}
int run_queue(void)
static void exit_init(void)
{
fprintf(stderr, "thread initialization failed: %m\n");
exit(1);
}
void run_queue(void)
{
long nproc_l = sysconf(_SC_NPROCESSORS_ONLN);
if (nproc_l < 1) nproc_l = 1;
if (nproc_l > 64) nproc_l = 64;
nproc = nproc_l;
pthread_attr_t pattr;
if (pthread_attr_init(&pattr)) return -1;
/* main thread will also be a task */
unsigned nproc1 = nproc - 1;
if (nproc1) {
pthread_attr_t pattr;
if (pthread_attr_init(&pattr)) exit_init();
#if defined(PTHREAD_STACK_MIN)
if (pthread_attr_setstacksize(&pattr, PTHREAD_STACK_MIN)) return -1;
if (pthread_attr_setguardsize(&pattr, 1)) return -1;
if (pthread_attr_setstacksize(&pattr, PTHREAD_STACK_MIN) ||
pthread_attr_setguardsize(&pattr, 1)) exit_init();
#endif
pthread_t *threads = calloc(sizeof(pthread_t), nproc);
if (!threads) return -1;
unsigned i, j = 0;
for (i = 0; i < nproc; i++) {
if (pthread_create(threads+i, &pattr, process_queue_item, &queue)) {
j = 1;
break;
for (unsigned i = 0; i < nproc1; i++) {
pthread_t thread;
if (pthread_create(&thread, &pattr, process_queue_item, &queue)) exit_init();
pthread_detach(thread);
}
}
pthread_attr_destroy(&pattr);
/* if creating threads fails, cancell all the already created ones */
if (j) for (j = 0; j < i; j++) {
pthread_cancel(threads[j]);
}
for (j = 0; j < i; j++) {
pthread_join(threads[j], NULL);
pthread_attr_destroy(&pattr);
}
return 0;
/* become one of the worker threads */
process_queue_item(&queue);
}
static void fail_single_file(const char *path)
{
fprintf(stderr, "failed to remove '%s': %m\n", path);
}
int single_file(const char *path)
{
return remove(path);
int rv = remove(path);
if (rv) fail_single_file(path);
return rv;
}
int recurse_into(const char *path)
void recurse_into(const char *path, int stop_at_error)
{
if (!remove(path)) return 0;
if (errno==ENOTEMPTY) return queue_add(&queue, strdup(path), NULL);
return 1;
if (!remove(path)) {
return;
} else if (errno == ENOTEMPTY) {
queue_add(&queue, strdup(path), NULL);
return;
} else {
fail_single_file(path);
if (stop_at_error) exit(1);
}
}