Author: Frank Ch. Eigler <f...@redhat.com>
Date:   Mon Apr 26 09:58:45 2021 -0400

    PR26125: debuginfod client cache - rmdir harder
    
    With PR25365, we accidentally lost the ability to rmdir client-cache
    directories corresponding to buildids.  Bring this back, with some
    attention to a possible race between a client doing cleanup and
    another client doing lookups at the same time.
    
    Signed-off-by: Frank Ch. Eigler <f...@redhat.com>

diff --git a/debuginfod/ChangeLog b/debuginfod/ChangeLog
index ad9dbc0a8a74..9af641ec0e13 100644
--- a/debuginfod/ChangeLog
+++ b/debuginfod/ChangeLog
@@ -1,3 +1,11 @@
+2021-04-26  Frank Ch. Eigler <f...@redhat.com>
+
+       PR26125
+       * debuginfod-client.c (debuginfod_clean_cache): For directory
+       rmdir, check mtime first.
+       (debuginfod_query_server): Try mkdir / mkstemp up to twice,
+       in case of race.
+
 2021-04-23  Frank Ch. Eigler <f...@redhat.com>
 
        PR27701
diff --git a/debuginfod/debuginfod-client.c b/debuginfod/debuginfod-client.c
index 7fdc6c9f30ec..0170500faaa9 100644
--- a/debuginfod/debuginfod-client.c
+++ b/debuginfod/debuginfod-client.c
@@ -307,12 +307,13 @@ debuginfod_clean_cache(debuginfod_client *c,
     return -errno;
 
   regex_t re;
-  const char * pattern = ".*/[a-f0-9]+/(debuginfo|executable|source.*)$";
+  const char * pattern = ".*/[a-f0-9]+(/debuginfo|/executable|/source.*|)$"; 
/* include dirs */
   if (regcomp (&re, pattern, REG_EXTENDED | REG_NOSUB) != 0)
     return -ENOMEM;
 
   FTSENT *f;
   long files = 0;
+  time_t now = time(NULL);
   while ((f = fts_read(fts)) != NULL)
     {
       /* ignore any files that do not match the pattern.  */
@@ -327,15 +328,16 @@ debuginfod_clean_cache(debuginfod_client *c,
       switch (f->fts_info)
         {
         case FTS_F:
-          /* delete file if max_unused_age has been met or exceeded.  */
-          /* XXX consider extra effort to clean up old tmp files */
-          if (time(NULL) - f->fts_statp->st_atime >= max_unused_age)
-            unlink (f->fts_path);
+          /* delete file if max_unused_age has been met or exceeded w.r.t. 
atime.  */
+          if (now - f->fts_statp->st_atime >= max_unused_age)
+            (void) unlink (f->fts_path);
           break;
 
         case FTS_DP:
-          /* Remove if empty. */
-          (void) rmdir (f->fts_path);
+          /* Remove if old & empty.  Weaken race against concurrent creation 
by 
+             checking mtime. */
+          if (now - f->fts_statp->st_mtime >= max_unused_age)
+            (void) rmdir (f->fts_path);
           break;
 
         default:
@@ -715,19 +717,18 @@ debuginfod_query_server (debuginfod_client *c,
     }
   /* thereafter, goto out0 on error*/
 
-  /* create target directory in cache if not found.  */
-  struct stat st;
-  if (stat(target_cache_dir, &st) == -1 && mkdir(target_cache_dir, 0700) < 0)
-    {
-      rc = -errno;
-      goto out0;
-    }
-
-  /* NB: write to a temporary file first, to avoid race condition of
-     multiple clients checking the cache, while a partially-written or empty
-     file is in there, being written from libcurl. */
-  fd = mkstemp (target_cache_tmppath);
-  if (fd < 0)
+  /* Because of a race with cache cleanup / rmdir, try to mkdir/mkstemp up to 
twice. */
+  for(int i=0; i<2; i++) {
+    /* (re)create target directory in cache */
+    (void) mkdir(target_cache_dir, 0700);
+
+    /* NB: write to a temporary file first, to avoid race condition of
+       multiple clients checking the cache, while a partially-written or empty
+       file is in there, being written from libcurl. */
+    fd = mkstemp (target_cache_tmppath);
+    if (fd >= 0) break;
+  }
+  if (fd < 0) /* Still failed after two iterations. */
     {
       rc = -errno;
       goto out0;
diff --git a/tests/ChangeLog b/tests/ChangeLog
index ac1963650693..4995ba609b29 100644
--- a/tests/ChangeLog
+++ b/tests/ChangeLog
@@ -1,3 +1,8 @@
+2021-04-26  Frank Ch. Eigler <f...@redhat.com>
+
+       PR26125
+       * run-debuginfod-find.sh: Add test case for cache cleanup rmdir.
+
 2021-04-23  Frank Ch. Eigler  <f...@redhat.com>
 
        * run-debuginfod-find.sh: Add a tiny test for client object reuse.
diff --git a/tests/run-debuginfod-find.sh b/tests/run-debuginfod-find.sh
index b1911a92f909..2ba31d8266f6 100755
--- a/tests/run-debuginfod-find.sh
+++ b/tests/run-debuginfod-find.sh
@@ -570,6 +570,10 @@ mkdir $DEBUGINFOD_CACHE_PATH/malformed
 touch $DEBUGINFOD_CACHE_PATH/malformed0
 touch $DEBUGINFOD_CACHE_PATH/malformed/malformed1
 
+# A valid format for an empty buildid subdirectory
+mkdir $DEBUGINFOD_CACHE_PATH/00000000
+touch -d '1970-01-01' $DEBUGINFOD_CACHE_PATH/00000000 # old enough to 
guarantee nukage
+
 # Trigger a cache clean and run the tests again. The clients should be unable 
to
 # find the target.
 echo 0 > $DEBUGINFOD_CACHE_PATH/cache_clean_interval_s
@@ -585,6 +589,11 @@ if [ ! -f $DEBUGINFOD_CACHE_PATH/malformed0 ] \
   exit 1
 fi
 
+if [ -d $DEBUGINFOD_CACHE_PATH/00000000 ]; then
+    echo "failed to rmdir old cache dir"
+    exit 1
+fi
+
 # Test debuginfod without a path list; reuse $PORT1
 env LD_LIBRARY_PATH=$ldpath ${abs_builddir}/../debuginfod/debuginfod $VERBOSE 
-F -U -d :memory: -p $PORT1 -L -F &
 PID3=$!

Reply via email to