Commit Graph

37 Commits

Author SHA1 Message Date
Érico Nogueira 90693c6d04 Fix build on older glibc versions.
_POSIX_C_SOURCE is necessary for getopt(3), and _DEFAULT_SOURCE is
necessary for the d_type enumerations in struct dirent.

Since we are here, use the C11 standard explicitly.

These fixes only allow us to build with glibc>=2.28, which is when the
<threads.h> header was added [1], necessary to access the thread_local
macro.

[1] https://stackoverflow.com/a/22875599
2023-07-26 10:17:48 -03:00
Érico Nogueira e56abc9ff9 Add directory file descriptor caching as well.
Now a parent can store its own fd, which will live on for as long as the
parent does, allowing us to use relative *at functions (that might be
faster on the kernel side?) and store smaller path buffers.

Since we were playing with soft limit detection, also make the EMFILE
operations conditional on limited_fds, avoiding potentially expensive
mutex operations when possible.

When checking the soft limit, we use 2 for the number of standard
file descriptors, because we closed stdin in main().
2022-06-12 23:14:31 -03:00
Érico Nogueira 3db1db5cf4 Close stdin on program launch.
It's unused and can give us an extra file descriptor.
2022-06-12 22:59:17 -03:00
Érico Nogueira fe0c1705b2 Move struct task cache to file scope.
Making it a thread_local variable that can be used by any function means
we can set it from recurse_into_parents with ease, increasing the
likelihood it's set when allocating a new struct.

We also add a debugging print to the malloc branch in task allocation;
this allows us to compare how many allocations use p_old and how many
don't. Again with the linux kernel tree, ~50% of allocations now use
p_old.
2022-06-12 21:59:45 -03:00
Érico Nogueira a626ae594d Add a per-thread struct task cache.
This allows us to save some malloc()/free() calls.

The structs can leak on program exit, but an external vector to store
them could fix this.

As it stands, this isn't really useful: in multiple tests with the linux
kernel tree, the p_old branch was taken between 0 and 3 times in a full
run.
2022-06-12 21:47:52 -03:00
Érico Nogueira 630ccbf1ce Only compute plen if necessary.
Save on strlen calls by only doing them if plen is going to be used.
2022-06-12 21:33:36 -03:00
Érico Nogueira 851bd62ab6 Implement . and .. detection manually.
strcmp() startup cost is too high and not worth it here.
2022-06-12 21:29:22 -03:00
Érico Nogueira 2fbe3a2789 Use more specific memory ordering.
- other threads need to see the correct value of p->files, so use
  'release' in the parent cleanup section
- recurse_into_parents needs to see the correct value of p->files, so
  use 'acquire' there
- also add comments explaining each branch in the parent cleanup section
2022-06-12 21:25:34 -03:00
É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
Érico Nogueira 37e4158aa8 Remove dead code.
There's no occasion when queue_remove won't succeed or call exit(), as
handled by the while loop above it, so we can also switch it to a void
function.
2021-10-27 17:19:19 -03:00
Érico Nogueira e323a9e59e Style fixes.
- make mutex and cond var static
- move some definitions/declarations around
2021-10-03 01:37:26 -03:00
Érico Nogueira 44cb175eb3 Small optimizations.
- use static initializers for mtx and cond
- use smaller variables where possible
2021-10-03 01:32:48 -03:00
Érico Nogueira 976ab2003a Make it so queue only receives non empty directories.
- allows us to remove the initial unlink/rmdir cases
- needs only a 3-line change in recurse_into to work
- makes struct task smaller
2021-10-03 01:28:30 -03:00
Érico Nogueira a00591c9a2 Add fast path removal to avoid allocations.
Instead of always adding files to the queue, we can try to remove them
in the readdir loop. This allows us to:

- add fewer items to the queue
- skip allocating and copying the path, since with the dir stream open
  we can use unlinkat(2)
- allocate parent task lazily, since it might not be needed
- stop using a recursive mutex, which can be slightly more expensive

Doing this in a naive way lead to a slow down, since we were holding the
queue mutex during the entirety of the operation. Instead, it was
necessary to change the loop structure a lot in order to be able to add
items to the queue without knowing the number of entries in the
directory. It could have been calculated with a readdir(3) loop +
rewinddir(3), but that would have added a lot of syscalls. In order to
work around that, we changed the purpose of the atomic int in struct
task.

Now, removed_count holds how many entries from the directory were
removed and a flag in its most significant byte to signal whether we are
still adding entries to the queue that refer to it or not. This flag,
plus some fancy atomic operations, allow us to control whether the
directory cleanup should happen in the thread that was adding its
entries to the queue or in the thread that removes the last item from
the queue.

We consider it safe to use the most significant bit of the unsigned int
as a flag because scandir(3) returns a signed int for the number of
entries in a directory.
2021-10-03 01:19:03 -03:00
Érico Nogueira be074ea79e Create queue_print function.
Since we are here, also make it dependant on an extra debug macro,
DEBUGP, and add "static [inline]" wherever missing.
2021-10-03 01:06:46 -03:00
Érico Nogueira 0e80093346 Fix UB with the path.
- we were allocating plen+nlen+1 and accessing plen+nlen+1; the correct
  allocation size should have been plen+nlen+2, because it needed to fit
  the null byte and the slash
- printing buf after it's been added to queue gets into a race condition
  where it can be freed before it's printed
2021-10-03 01:02:26 -03:00
Érico Nogueira 948783245d Fix error code for opendir, use cond vars.
ENFILE is for system wide file descriptors, we need to deal with EMFILE
instead. Use a cond var to signal that a directory stream has been
closed, which means we can open another one.
2021-09-20 10:52:39 -03:00
Érico Nogueira fdf8fa3c30 General cleanup.
- actually print error for bad parent
- declare functions as static when necessary
- clean up headers
- clear up error message
2021-09-18 23:48:56 -03:00
Érico Nogueira 5fcae83d95 Fix stack size to work on more (all?) platforms.
Use PTHREAD_STACK_MIN where available.

This required some changes to work on musl:

- the removal of an intermediary buffer with PATH_MAX bytes. This was an
  optimization, since it avoided usage of a *printf function and halved
  the amount of copying being done
- stop using scandir, since qsort uses a lot of stack. This is partially
  a performance improvement: avoided calling qsort multiple times and
  allocating the result list, but it locks the queue mutex for a longer
  time
2021-09-18 16:51:10 -03:00
Érico Nogueira f1b569a5de Add comment to try and remove sched_yield().
A cond var might work here, but we need to make sure we aren't keeping
other threads from running file operations for no reason.
2021-09-18 16:10:46 -03:00
Érico Nogueira ff3cbb8471 Update Makefile. 2021-09-18 16:08:44 -03:00
Érico Nogueira 1377344e10 Don't try unlink() if we know it's a dir. 2021-09-18 16:08:41 -03:00
Érico Nogueira 330481fe79 Exit when file couldn't be unlink()d. 2021-09-18 15:48:11 -03:00
Érico Nogueira 1d315663ba Add exit condition.
Requires adding a counter to the queue. If the last active thread was
going to also wait on the condition variable, we know we can quit.
2021-09-18 15:47:21 -03:00
Érico Nogueira 82d06ef184 Use cond variables instead of sched_yield(). 2021-09-18 15:36:26 -03:00
Érico Nogueira e98dc6a1a3 Use attributes for the mutex and threads.
- thread stack size: (8K - 1024) so the libc TLS can fit in 8K, and we
  allocate only two memory pages (on systems with 4K pages, at least),
  plus one guard page. Will probably error out on glibc...
- mutex type: recursive so we can lock the queue entirely for some
  operations; isn't used yet.
2021-09-18 15:19:59 -03:00
Érico Nogueira fafb351498 Instead of using priorities, use a parent task.
This simplifies the code and avoids wasting syscalls. Does require
atomics, though.
2021-09-18 15:04:29 -03:00
Érico Nogueira 7c6fe16e7b Stop using ftw. 2021-09-18 11:23:56 -03:00
Érico Nogueira e24aef5395 Split implementation into two files.
- also fix threadings bugs: threads could leak (if the join loop exited
  due to some error) or non existent threads could be joined (we add the
  created struct member to avoid this)
2021-05-18 00:35:30 -03:00
Érico Nogueira 75c36e6432 Make recursive removal threaded. 2021-05-18 00:06:47 -03:00
Érico Nogueira 0741a2da4e Refactor into a simpler code path. 2021-05-17 23:50:24 -03:00
Érico Nogueira f3f90857e8 Implement recursion using nftw.
FTW_DEPTH gives the natural file removal order, but it doesn't lend
itself easily well to traversing the directory tree in a multi-threaded
manner.
2021-05-17 23:44:56 -03:00
Érico Nogueira 4bdc7f18c5 Initial commit.
Can delete a sequence of files of directories in the command line, no
recursion yet.

Assumes remove(3) is fast and reasonable:

   unlink(); if (errno==EISDIR) rmdir();
2021-05-17 23:29:05 -03:00