[Git][reproducible-builds/disorderfs][master] 2 commits: Add the option to sort by ctime as returned by lstat syscall.

Chris Lamb (@lamby) gitlab at salsa.debian.org
Mon Jul 13 17:15:28 UTC 2026



Chris Lamb pushed to branch master at Reproducible Builds / disorderfs


Commits:
68d20f75 by Christelle Gloor at 2026-07-13T17:15:26+00:00
Add the option to sort by ctime as returned by lstat syscall.

- - - - -
595301c4 by Chris Lamb at 2026-07-13T17:15:26+00:00
Merge branch 'master' into 'master'

Add the option to sort by ctime as returned by lstat syscall.

See merge request reproducible-builds/disorderfs!6
- - - - -


2 changed files:

- disorderfs.cpp
- + tests/test_sort_ctime


Changes:

=====================================
disorderfs.cpp
=====================================
@@ -39,6 +39,7 @@
 #include <sys/syscall.h>
 #include <sys/file.h>
 #include <stddef.h>
+#include <sys/stat.h>
 
 #define DISORDERFS_VERSION "0.6.2"
 
@@ -55,6 +56,7 @@ struct Disorderfs_config {
     int                        pad_blocks{1};
     int                        share_locks{0};
     int                        quiet{0};
+    int                        sort_by_ctime{0};
 };
 Disorderfs_config                config;
 
@@ -69,6 +71,67 @@ int wrap (int retval) {
 }
 using Dirents = std::vector<std::pair<std::string, ino_t>>;
 
+typedef std::pair<timespec, std::pair<std::string, ino_t>> Ctime_Dirent_pair;
+
+// Overload timespec operator
+bool operator< (const timespec first, const timespec second){
+    if(first.tv_sec < second.tv_sec){
+        return true;
+    } else if (first.tv_sec == second.tv_sec)
+    {
+        return first.tv_nsec < second.tv_nsec;
+    }
+    return false; // first_seconds >= second_seconds
+};
+
+// At least provide a known value if lstat happens to fail to avoid data corruption
+const timespec INVALID = {0,0};
+
+// We found that std::sort was corrupting the data with the naive implementation of calling
+// lstat() during each comparison execution (probably because the value was not stable for the same element between comparisons)
+// this creates a stable sequence of timespecs for sort 
+// we trade memory for code legibility given the c++11 restriction
+/*
+ * @param dirents The data in the form of a vector of file/dir-name and inode pairs
+ * @param abspath The absolute path to the root of the directory the data was read from (assuming posix)
+*/
+std::vector<Ctime_Dirent_pair> create_ctime_dirents_list(std::unique_ptr<Dirents>& dirents, std::string abspath){
+    // include a trailing '/' if necessary, assuming posix
+    if(abspath.back() != '/'){
+        abspath.push_back('/');
+    }
+    // Iterate through the dirents list and call lstat() exactly once on each entry
+    std::vector<Ctime_Dirent_pair> result;
+    for(auto i = dirents->begin(); i < dirents->end(); i++){
+        std::string el_abspath = abspath;
+        el_abspath.append(i->first);
+        struct stat buffer;
+        int status = lstat(el_abspath.c_str(), &buffer);
+        timespec ctime;
+        if (status != 0){
+            std::cerr << "WARNING: lstat returned " << status << " for \n" << el_abspath << std::endl;
+            std::cerr << "WARNING: replacing ctime with {0s, 0ns}" << std::endl;
+            ctime = INVALID;
+        } else {
+            ctime = buffer.st_ctim;
+        }
+        Ctime_Dirent_pair new_element = {ctime, *i};
+        result.push_back(new_element);
+    }
+    return result;
+};
+
+bool compare_ctime_dirents(Ctime_Dirent_pair a, Ctime_Dirent_pair b){
+    return a.first < b.first;
+};
+
+// PRE: dirents.size() == sorted_interim.size()
+void overwrite_dirents(std::unique_ptr<Dirents>& dirents, std::vector<Ctime_Dirent_pair>& sorted_interim){
+    for(long unsigned int i = 0; i < sorted_interim.size(); i++){
+        *(dirents->begin() + i) = (sorted_interim.begin() + i)->second;
+    }
+};
+
 // The libc versions of seteuid, etc. set the credentials for all threads.
 // We need to set credentials for a single thread only, so call the syscalls directly.
 int thread_seteuid (uid_t euid)
@@ -197,6 +260,8 @@ const struct fuse_opt disorderfs_fuse_opts[] = {
     DISORDERFS_OPT("--pad-blocks=%i", pad_blocks, 0),
     DISORDERFS_OPT("--share-locks=no", share_locks, false),
     DISORDERFS_OPT("--share-locks=yes", share_locks, true),
+    DISORDERFS_OPT("--sort-by-ctime=no", sort_by_ctime, false),
+    DISORDERFS_OPT("--sort-by-ctime=yes", sort_by_ctime, true),
     FUSE_OPT_KEY("-h", KEY_HELP),
     FUSE_OPT_KEY("--help", KEY_HELP),
     FUSE_OPT_KEY("-V", KEY_VERSION),
@@ -222,7 +287,8 @@ int fuse_opt_proc (void* data, const char* arg, int key, struct fuse_args* outar
         std::clog << "    --multi-user=yes|no    allow multiple users to access overlay (requires root; default: no)" << std::endl;
         std::clog << "    --shuffle-dirents=yes|no  randomly shuffle directory entries? (default: no)" << std::endl;
         std::clog << "    --reverse-dirents=yes|no  reverse dirent order? (default: yes)" << std::endl;
-        std::clog << "    --sort-dirents=yes|no  sort directory entries deterministically instead (default: no)" << std::endl;
+        std::clog << "    --sort-dirents=yes|no  sort directory entries instead (default: no)" << std::endl;
+        std::clog << "    --sort-by-ctime=yes|no  sort directory entries by ctime as returned by lstat syscall instead of alphabetically (default: no). No effect if --sort-dirents=no (default). Will show the youngest file first if --reverse-dirents=yes." << std::endl;
         std::clog << "    --pad-blocks=N         add N to st_blocks (default: 1)" << std::endl;
         std::clog << "    --share-locks=yes|no   share locks with underlying filesystem (obsoleted, BUGGY; default: no)" << std::endl;
         std::clog << std::endl;
@@ -247,6 +313,7 @@ int        main (int argc, char** argv)
     signal(SIGPIPE, SIG_IGN);
     umask(0);
 
+    
     /*
      * Parse command line options
      */
@@ -280,14 +347,14 @@ int        main (int argc, char** argv)
         if (config.shuffle_dirents) {
             std::cout << "disorderfs: shuffling directory entries" << std::endl;
         }
+        if (config.sort_dirents) {
+            std::string sort_target = (config.sort_by_ctime)? "by ctime" : "alphabetically";
+            std::cout << "disorderfs: sorting directory entries " << sort_target << std::endl;
+        }
         if (config.reverse_dirents) {
             std::cout << "disorderfs: reversing directory entries" << std::endl;
         }
-        if (config.sort_dirents) {
-            std::cout << "disorderfs: sorting directory entries" << std::endl;
-        }
     }
-
     /*
      * Initialize disorderfs_fuse_operations
      */
@@ -430,7 +497,6 @@ int        main (int argc, char** argv)
     disorderfs_fuse_operations.opendir = [] (const char* path, struct fuse_file_info* info) -> int {
         Guard g;
         std::unique_ptr<Dirents> dirents{new Dirents};
-
         DIR* d = opendir((root + path).c_str());
         if (!d) {
             return -errno;
@@ -444,7 +510,14 @@ int        main (int argc, char** argv)
             return -errno;
         }
         if (config.sort_dirents) {
-            std::sort(dirents->begin(), dirents->end());
+            if (config.sort_by_ctime) {
+                auto tmp = create_ctime_dirents_list(dirents, (root + path).c_str());
+                std::sort(tmp.begin(), tmp.end(), compare_ctime_dirents);
+                overwrite_dirents(dirents, tmp);
+            } else {
+                // sort lexicographically
+                std::sort(dirents->begin(), dirents->end());
+            }
         }
         if (config.reverse_dirents) {
             std::reverse(dirents->begin(), dirents->end());
@@ -453,7 +526,6 @@ int        main (int argc, char** argv)
         if (errno != 0) {
             return -errno;
         }
-
         set_fuse_data<Dirents*>(info, dirents.release());
         return 0;
     };
@@ -553,6 +625,5 @@ int        main (int argc, char** argv)
     disorderfs_fuse_operations.fallocate = [] (const char* path, int mode, off_t off, off_t len, struct fuse_file_info* info) -> int {
         return wrap(fallocate(info->fh, mode, off, len));
     };
-
     return fuse_main(fargs.argc, fargs.argv, &disorderfs_fuse_operations, nullptr);
 }


=====================================
tests/test_sort_ctime
=====================================
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+. ./common
+ 
+echo "  sort_ctime: clearing fixtures and creating timed files..."
+cd fixtures || exit
+rm a b c
+touch old
+sleep 1
+touch mid
+sleep 1
+touch new
+cd .. || exit
+sleep 1
+
+Mount --sort-dirents=yes --reverse-dirents=no --sort-by-ctime=yes
+Expect oldmidnew
+Unmount
+
+Mount --sort-dirents=yes --reverse-dirents=yes --sort-by-ctime=yes
+Expect newmidold
+Unmount
+
+echo "  sort_ctime: recreating a,b,c testfiles..."
+cd fixtures || exit
+rm old mid new
+touch a b c
+cd .. || exit



View it on GitLab: https://salsa.debian.org/reproducible-builds/disorderfs/-/compare/23207faac48970c82a583890a62934457225df05...595301c4b04c058f2135addb829a17061c73788d

-- 
View it on GitLab: https://salsa.debian.org/reproducible-builds/disorderfs/-/compare/23207faac48970c82a583890a62934457225df05...595301c4b04c058f2135addb829a17061c73788d
You're receiving this email because of your account on salsa.debian.org. Manage all notifications: https://salsa.debian.org/-/profile/notifications | Help: https://salsa.debian.org/help


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.reproducible-builds.org/pipermail/rb-commits/attachments/20260713/66f64ff3/attachment.htm>


More information about the rb-commits mailing list