diff options
-rw-r--r-- | system/fatrace/README | 3 | ||||
-rw-r--r-- | system/fatrace/fatrace.SlackBuild | 26 | ||||
-rw-r--r-- | system/fatrace/fatrace.info | 8 | ||||
-rw-r--r-- | system/fatrace/patches/0001-Let-filter-distinguish-between-and.patch | 45 | ||||
-rw-r--r-- | system/fatrace/patches/0002-Help-text-formatting.patch | 54 | ||||
-rw-r--r-- | system/fatrace/patches/0003-Add-option-d-dir.patch | 320 | ||||
-rw-r--r-- | system/fatrace/slack-desc | 2 |
7 files changed, 443 insertions, 15 deletions
diff --git a/system/fatrace/README b/system/fatrace/README index edd2a3600c..d158bdab31 100644 --- a/system/fatrace/README +++ b/system/fatrace/README @@ -12,6 +12,5 @@ serves as a starting point for bug reports or optimizing a particular installation. fatrace has self-test scripts that can be run during the build. These -are disabled by default, because they create a file outside of the -temp directory (/etc/test.txt). If you want to run the tests, export +are disabled by default. If you want to run the tests, export RUNTESTS=yes in the environment. diff --git a/system/fatrace/fatrace.SlackBuild b/system/fatrace/fatrace.SlackBuild index e710598694..77203c99a0 100644 --- a/system/fatrace/fatrace.SlackBuild +++ b/system/fatrace/fatrace.SlackBuild @@ -7,6 +7,10 @@ # Licensed under the WTFPL. See http://www.wtfpl.net/txt/copying/ for details. +# 20250916 bkw: +# - update for v0.19.1. +# - switch to upstream's github, instead of launchpad. +# - add a couple of post-release patches from upstream git. # 20250126 bkw: update for v0.18.0. # 20230509 bkw: # - new maintainer. @@ -17,7 +21,7 @@ cd $(dirname $0) ; CWD=$(pwd) PRGNAM=fatrace -VERSION=${VERSION:-0.18.0} +VERSION=${VERSION:-0.19.1} BUILD=${BUILD:-1} TAG=${TAG:-_SBo} PKGTYPE=${PKGTYPE:-tgz} @@ -55,12 +59,20 @@ rm -rf $PKG mkdir -p $TMP $PKG $OUTPUT cd $TMP rm -rf $PRGNAM-$VERSION -tar xvf $CWD/${PRGNAM}_${VERSION}.orig.tar.gz +tar xvf $CWD/$PRGNAM-$VERSION.tar.gz cd $PRGNAM-$VERSION chown -R root:root . find -L . -perm /111 -a \! -perm 755 -a -exec chmod 755 {} + -o \ \! -perm /111 -a \! -perm 644 -a -exec chmod 644 {} + +# 20250916 bkw: these patches will be in the next release. One is a +# fix for a real bug (the < and > thing) and one adds a *very* useful +# option (-d). +for i in $CWD/patches/*.patch; do + echo "=== Applying $( basename $i )" + patch -p1 < $i +done + sed -i -e "/^CFLAGS/s,-O2,$SLKCFLAGS," \ -e 's,share/man,man,' \ -e 's,-Werror,,' \ @@ -71,17 +83,15 @@ strip $PRGNAM make install DESTDIR=$PKG PREFIX=/usr gzip $PKG/usr/man/man*/* -# This is disabled by default because it creates (and deletes) an -# /etc/test.txt (outside of $TMP). +# This is disabled by default because it takes a while to run, +# and will fail on a custom kernel that lacks btrfs. if [ "${RUNTESTS:-no}" = "yes" ]; then - # Tests assume wrong path (our /usr/bin/head is a symlink to /bin/head) - sed -i 's,/usr/bin/,/bin/,' tests/fatrace - sh tests/run + python3 -m unittest -v fi PKGDOC=$PKG/usr/doc/$PRGNAM-$VERSION mkdir -p $PKGDOC -cp -a COPYING NEWS $PKGDOC +cp -a COPYING README* $PKGDOC cat $CWD/$PRGNAM.SlackBuild > $PKGDOC/$PRGNAM.SlackBuild mkdir -p $PKG/install diff --git a/system/fatrace/fatrace.info b/system/fatrace/fatrace.info index 65c9f70b78..72f2420543 100644 --- a/system/fatrace/fatrace.info +++ b/system/fatrace/fatrace.info @@ -1,8 +1,8 @@ PRGNAM="fatrace" -VERSION="0.18.0" -HOMEPAGE="https://launchpad.net/fatrace" -DOWNLOAD="https://launchpad.net/debian/+archive/primary/+sourcefiles/fatrace/0.18.0-1/fatrace_0.18.0.orig.tar.gz" -MD5SUM="857235823c3a5f78f56645dafd82bd8a" +VERSION="0.19.1" +HOMEPAGE="https://github.com/martinpitt/fatrace" +DOWNLOAD="https://github.com/martinpitt/fatrace/archive/0.19.1/fatrace-0.19.1.tar.gz" +MD5SUM="b65acc61fb72ca8fd196248cb3b5d618" DOWNLOAD_x86_64="" MD5SUM_x86_64="" REQUIRES="" diff --git a/system/fatrace/patches/0001-Let-filter-distinguish-between-and.patch b/system/fatrace/patches/0001-Let-filter-distinguish-between-and.patch new file mode 100644 index 0000000000..4d427375f3 --- /dev/null +++ b/system/fatrace/patches/0001-Let-filter-distinguish-between-and.patch @@ -0,0 +1,45 @@ +From 33370a8c97086d7494c9bf0dd0ccd15cdcf497cb Mon Sep 17 00:00:00 2001 +From: Axel Svensson <mail@axelsvensson.com> +Date: Thu, 4 Sep 2025 15:33:56 +0200 +Subject: [PATCH 1/3] Let --filter distinguish between < and > + +--- + fatrace.8 | 5 ++--- + fatrace.c | 4 +++- + 2 files changed, 5 insertions(+), 4 deletions(-) + +diff --git a/fatrace.8 b/fatrace.8 +index 782669b..7117ee2 100644 +--- a/fatrace.8 ++++ b/fatrace.8 +@@ -191,9 +191,8 @@ Ignore events for this process ID. Can be specified multiple times. + .TP + .B \-f \fITYPES\fR, \fB\-\-filter=\fITYPES + Show only the given event types. \fBTYPES\fR is a list of +-.BR C ", " R ", " O ", " W ", " D ", " + ", or " < +-with the above meanings. \fB<\fR and \fB>\fR both mean "move" and will always +-enable both directions. ++.BR C ", " R ", " O ", " W ", " D ", " + ", " < ", or " > ++with the above meanings. + + E. g. use \fB\-\-filter=OC\fR to only show open and close events. + +diff --git a/fatrace.c b/fatrace.c +index bd0102f..e683535 100644 +--- a/fatrace.c ++++ b/fatrace.c +@@ -749,8 +749,10 @@ parse_args (int argc, char** argv) + option_filter_mask |= FAN_DELETE; + break; + case '<': ++ option_filter_mask |= FAN_MOVED_FROM; ++ break; + case '>': +- option_filter_mask |= FAN_MOVE; ++ option_filter_mask |= FAN_MOVED_TO; + break; + #endif + default: +-- +2.46.3 + diff --git a/system/fatrace/patches/0002-Help-text-formatting.patch b/system/fatrace/patches/0002-Help-text-formatting.patch new file mode 100644 index 0000000000..3b7f3d92de --- /dev/null +++ b/system/fatrace/patches/0002-Help-text-formatting.patch @@ -0,0 +1,54 @@ +From 0250abe3e304b348fa7ad719f035b0f4ed910365 Mon Sep 17 00:00:00 2001 +From: Axel Svensson <mail@axelsvensson.com> +Date: Fri, 5 Sep 2025 08:57:08 +0200 +Subject: [PATCH 2/3] Help text formatting + +- Reflow to output max 79 columns. +- Add a `=` to `--ignore-pid` for consistency. +--- + fatrace.c | 29 +++++++++++++++++------------ + 1 file changed, 17 insertions(+), 12 deletions(-) + +diff --git a/fatrace.c b/fatrace.c +index e683535..836c431 100644 +--- a/fatrace.c ++++ b/fatrace.c +@@ -647,18 +647,23 @@ help (void) + puts ("Usage: fatrace [options...] \n" + "\n" + "Options:\n" +-" -c, --current-mount\t\tOnly record events on partition/mount of current directory.\n" +-" -o FILE, --output=FILE\tWrite events to a file instead of standard output.\n" +-" -s SECONDS, --seconds=SECONDS\tStop after the given number of seconds.\n" +-" -t, --timestamp\t\tAdd timestamp to events. Give twice for seconds since the epoch.\n" +-" -u, --user\t\t\tAdd user ID and group ID to events.\n" +-" -p PID, --ignore-pid PID\tIgnore events for this process ID. Can be specified multiple times.\n" +-" -f TYPES, --filter=TYPES\tShow only the given event types; choose from C, R, O, W, +, D, < or >, e. g. --filter=OC.\n" +-" -C COMM, --command=COMM\tShow only events for this command.\n" +-" -j, --json\t\t\tWrite events in JSONL format.\n" +-" -P, --parents\t\t\tInclude information about all parent processes.\n" +-" -e, --exe\t\t\tAdd executable path to events.\n" +-" -h, --help\t\t\tShow help."); ++" -c, --current-mount Only record events on partition/mount of\n" ++" current directory.\n" ++" -o FILE, --output=FILE Write events to a file instead of standard\n" ++" output.\n" ++" -s SECONDS, --seconds=SECONDS Stop after the given number of seconds.\n" ++" -t, --timestamp Add timestamp to events. Give twice for seconds\n" ++" since the epoch.\n" ++" -u, --user Add user ID and group ID to events.\n" ++" -p PID, --ignore-pid=PID Ignore events for this process ID. Can be\n" ++" specified multiple times.\n" ++" -f TYPES, --filter=TYPES Show only the given event types; choose from C,\n" ++" R, O, W, +, D, < or >, e. g. --filter=OC.\n" ++" -C COMM, --command=COMM Show only events for this command.\n" ++" -j, --json Write events in JSONL format.\n" ++" -P, --parents Include information about all parent processes.\n" ++" -e, --exe Add executable path to events.\n" ++" -h, --help Show help."); + } + + /** +-- +2.46.3 + diff --git a/system/fatrace/patches/0003-Add-option-d-dir.patch b/system/fatrace/patches/0003-Add-option-d-dir.patch new file mode 100644 index 0000000000..6a251555ba --- /dev/null +++ b/system/fatrace/patches/0003-Add-option-d-dir.patch @@ -0,0 +1,320 @@ +From 21d7e40b2ccc2a3209341264bae97cc0a64cc068 Mon Sep 17 00:00:00 2001 +From: Axel Svensson <mail@axelsvensson.com> +Date: Sun, 29 Jun 2025 17:30:53 +0200 +Subject: [PATCH 3/3] Add option -d,--dir + +Fixes #48 +--- + fatrace.8 | 27 ++++++++++++- + fatrace.c | 50 ++++++++++++++++++++++- + tests/test.py | 107 ++++++++++++++++++++++++++++++++++++++++++++------ + 3 files changed, 168 insertions(+), 16 deletions(-) + +diff --git a/fatrace.8 b/fatrace.8 +index 7117ee2..aa355b1 100644 +--- a/fatrace.8 ++++ b/fatrace.8 +@@ -1,4 +1,4 @@ +-.TH fatrace 8 "August 20, 2020" "Martin Pitt" ++.TH fatrace 8 "September 5, 2025" "Martin Pitt" + + .SH NAME + +@@ -10,6 +10,10 @@ fatrace \- report system wide file access events + [ + .I OPTIONS + ] ++[ ++-- ++] ++[ \fIDIR\fR... ] + + .SH DESCRIPTION + +@@ -212,6 +216,27 @@ Print information about all parent processes. + .B \-e\fR, \fB\-\-exe + Print executable path. + ++.TP ++.B \-d \fIDIR\fR, \fB\-\-dir=\fIDIR\fR ++Show only events where the affected file is \fIdirectly\fR under this directory. ++Can be specified multiple times to include events from several directories. ++.IP ++This is \fInot\fR recursive. For example, \fB\-d /home/user\fR will show events ++for \fB/home/user/file\fR but not for \fB/home/user/subdir/file\fR. ++.IP ++\fIDIR\fRs can also be specified at the end of the command line, advisably ++preceded by the \fB\-\-\fR separator. As long as no directories will be created ++or moved under a subtree, it's possible to watch that subtree like so: ++.RS ++.IP "" 4 ++fatrace -- $(find /path/to/subtree -type d) ++.RE ++.IP ++The attachment is to a directory inode, not the path. For example, this means ++that 1) If you move a watched directory while fatrace runs, you may receive ++events for a path that is not listed on the command line; 2) If you delete and ++recreate a watched directory you will no longer receive events. ++ + .TP + .B \-h \fR, \fB\-\-help + Print help and exit. +diff --git a/fatrace.c b/fatrace.c +index 836c431..1c9042d 100644 +--- a/fatrace.c ++++ b/fatrace.c +@@ -48,6 +48,9 @@ + + #define BUFSIZE 256*1024 + ++/* Likely to be less than /proc/sys/fs/fanotify/max_user_marks */ ++#define MAX_DIRS 4096 ++ + /* https://man7.org/linux/man-pages/man5/proc_pid_comm.5.html ; not defined in any include file */ + #ifndef TASK_COMM_LEN + #define TASK_COMM_LEN 16 +@@ -73,6 +76,8 @@ static char* option_comm = NULL; + static bool option_json = false; + static bool option_parents = false; + static bool option_exe = false; ++static const char *option_dirs[MAX_DIRS]; ++static unsigned int option_dirs_len = 0; + + /* --time alarm sets this to 0 */ + static volatile int running = 1; +@@ -594,6 +599,26 @@ do_mark (int fan_fd, const char *dir, bool fatal) + static void + setup_fanotify (int fan_fd) + { ++ if (option_dirs_len > 0) { ++ mark_mode = FAN_MARK_ADD; ++ char resolved[PATH_MAX]; ++ struct stat st; ++ for (unsigned i = 0; i < option_dirs_len; i++) { ++ if (realpath(option_dirs[i], resolved) && ++ stat(resolved, &st) == 0) { ++ if (S_ISDIR(st.st_mode)) ++ do_mark (fan_fd, resolved, false); ++ else ++ errx(EXIT_FAILURE, ++ "Not a directory: %s", option_dirs[i]); ++ } ++ else ++ err(EXIT_FAILURE, ++ "Cannot resolve directory: %s", option_dirs[i]); ++ } ++ return; ++ } ++ + FILE* mounts; + struct mntent* mount; + +@@ -644,7 +669,7 @@ setup_fanotify (int fan_fd) + static void + help (void) + { +- puts ("Usage: fatrace [options...] \n" ++ puts ("Usage: fatrace [options...] [--] [DIR...]\n" + "\n" + "Options:\n" + " -c, --current-mount Only record events on partition/mount of\n" +@@ -663,6 +688,10 @@ help (void) + " -j, --json Write events in JSONL format.\n" + " -P, --parents Include information about all parent processes.\n" + " -e, --exe Add executable path to events.\n" ++" -d DIR, --dir=DIR Show only events on files directly under this\n" ++" directory. NOT recursive. Can be specified\n" ++" multiple times. DIRs can also be specified at\n" ++" the end of the command line.\n" + " -h, --help Show help."); + } + +@@ -691,12 +720,13 @@ parse_args (int argc, char** argv) + {"json", no_argument, 0, 'j'}, + {"parents", no_argument, 0, 'P'}, + {"exe", no_argument, 0, 'e'}, ++ {"dir", required_argument, 0, 'd'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0 } + }; + + while (1) { +- c = getopt_long (argc, argv, "C:co:s:tup:f:jPeh", long_options, NULL); ++ c = getopt_long (argc, argv, "C:co:s:tup:f:jPed:h", long_options, NULL); + + if (c == -1) + break; +@@ -801,6 +831,13 @@ parse_args (int argc, char** argv) + option_exe = true; + break; + ++ case 'd': ++ if (option_dirs_len >= MAX_DIRS) ++ errx (EXIT_FAILURE, "Error: Too many --dir arguments" ++ " (maximum is %d).", MAX_DIRS); ++ option_dirs[option_dirs_len++] = optarg; ++ break; ++ + case 'h': + help (); + exit (EXIT_SUCCESS); +@@ -813,6 +850,15 @@ parse_args (int argc, char** argv) + errx (EXIT_FAILURE, "Internal error: unexpected option '%c'", c); + } + } ++ for (int i = optind; i < argc; i++) { ++ if (option_dirs_len >= MAX_DIRS) ++ errx (EXIT_FAILURE, "Error: Too many --dir and DIR arguments" ++ " (maximum is %d).", MAX_DIRS); ++ option_dirs[option_dirs_len++] = argv[i]; ++ } ++ if (option_current_mount && option_dirs_len > 0) ++ errx (EXIT_FAILURE, ++ "Error: -c,--current-mount and -d,--dir are mutually exclusive."); + } + + static void +diff --git a/tests/test.py b/tests/test.py +index 0dd904a..f86c0e1 100644 +--- a/tests/test.py ++++ b/tests/test.py +@@ -72,18 +72,30 @@ class FatraceRunner: + self.log_content = f.read() + self.log_dir.cleanup() + +- def assert_log(self, pattern: str) -> None: ++ def has_log(self, pattern: str) -> bool: + """Check if a regex pattern exists in the log content.""" + + assert self.log_content, "Need to call run() first" + +- if not re.search(pattern, self.log_content, re.MULTILINE): +- raise AssertionError(f"""Pattern not found in log: {pattern} +----- Log content ---- +-{self.log_content} +------------------""") ++ return bool(re.search(pattern, self.log_content, re.MULTILINE)) + +- def assert_json(self, condition_func: Callable[[dict], bool]) -> None: ++ def assert_log(self, pattern: str) -> None: ++ if self.has_log(pattern): ++ return ++ raise AssertionError(f"Pattern not found in log: {pattern}\n" ++ "---- Log content ----\n" ++ f"{self.log_content}\n" ++ "-----------------") ++ ++ def assert_not_log(self, pattern: str) -> None: ++ if not self.has_log(pattern): ++ return ++ raise AssertionError(f"Pattern found in log: {pattern}\n" ++ "---- Log content ----\n" ++ f"{self.log_content}\n" ++ "-----------------") ++ ++ def has_json(self, condition_func: Callable[[dict], bool]) -> bool: + """Check if any JSON line matches the condition function.""" + + assert self.log_content, "Need to call run() first" +@@ -94,16 +106,27 @@ class FatraceRunner: + entry = json.loads(line) + try: + if condition_func(entry): +- return ++ return True + except KeyError: + # Ignore entries that do not match the expected structure + pass ++ return False + +- raise AssertionError(f"""No JSON entry matched condition +----- Log content ---- +-{self.log_content} +------------------""") +- ++ def assert_json(self, condition_func: Callable[[dict], bool]) -> None: ++ if self.has_json(condition_func): ++ return ++ raise AssertionError("No JSON entry matched condition\n" ++ "---- Log content ----\n" ++ f"{self.log_content}\n" ++ "-----------------") ++ ++ def assert_not_json(self, condition_func: Callable[[dict], bool]) -> None: ++ if not self.has_json(condition_func): ++ return ++ raise AssertionError("At least one JSON entry matched condition\n" ++ "---- Log content ----\n" ++ f"{self.log_content}\n" ++ "-----------------") + + class FatraceTests(unittest.TestCase): + def setUp(self): +@@ -574,6 +597,64 @@ with open("{python_pid_file}", "w") as f: f.write(f"{{os.getpid()}}\\n") + "path" not in e + )) + ++ def test_dir(self): ++ yes1 = str(self.tmp_path / "yes-1") ++ yes2 = str(self.tmp_path / "yes-2") ++ no1 = str(self.tmp_path / "no-1") ++ ++ exe(["mkdir", yes1]) ++ exe(["mkdir", yes2]) ++ exe(["mkdir", no1]) ++ ++ f = FatraceRunner(["-s", "3", "-d", yes1, f"--dir={yes2}"]) ++ f_json = FatraceRunner(["-s", "3", "--json", "--", yes1, yes2]) ++ ++ slow_exe(["mkdir", f"{yes1}/subA"]) ++ slow_exe(["mkdir", f"{no1}/subB"]) ++ ++ slow_exe(["touch", f"{yes1}/yesC"]) ++ slow_exe(["touch", f"{yes1}/subA/noD"]) ++ slow_exe(["touch", f"{yes2}/yesE"]) ++ slow_exe(["touch", f"{no1}/noF"]) ++ slow_exe(["touch", f"{no1}/subB/noG"]) ++ ++ slow_exe(["mv", yes1, yes2]) ++ new_yes1 = str(self.tmp_path / "yes-2" / "yes-1") ++ slow_exe(["mv", no1, yes2]) ++ new_no1 = str(self.tmp_path / "yes-2" / "no-1") ++ ++ slow_exe(["touch", f"{new_yes1}/yesH"]) ++ slow_exe(["touch", f"{new_yes1}/subA/noI"]) ++ slow_exe(["touch", f"{new_no1}/noJ"]) ++ slow_exe(["touch", f"{new_no1}/subB/noK"]) ++ ++ f.finish() ++ f_json.finish() ++ ++ f.assert_log (rf"^mkdir\([0-9]*\): \+ +{re.escape(yes1)}") ++ f.assert_not_log(rf"^mkdir\([0-9]*\): \+ +{re.escape(no1)}") ++ f.assert_log (rf"^touch\([0-9]*\): C?WO? +{re.escape(yes1)}/yesC") ++ f.assert_not_log(rf"^touch\([0-9]*\): C?WO? +{re.escape(yes1)}/subA/noD") ++ f.assert_log (rf"^touch\([0-9]*\): C?WO? +{re.escape(yes2)}/yesE") ++ f.assert_not_log(rf"^touch\([0-9]*\): C?WO? +{re.escape(no1)}/noF") ++ f.assert_not_log(rf"^touch\([0-9]*\): C?WO? +{re.escape(no1)}/subB/noG") ++ f.assert_log (rf"^touch\([0-9]*\): C?WO? +{re.escape(new_yes1)}/yesH") ++ f.assert_not_log(rf"^touch\([0-9]*\): C?WO? +{re.escape(new_yes1)}/subA/noI") ++ f.assert_not_log(rf"^touch\([0-9]*\): C?WO? +{re.escape(new_no1)}/noJ") ++ f.assert_not_log(rf"^touch\([0-9]*\): C?WO? +{re.escape(new_no1)}/subB/noK") ++ ++ f_json.assert_json (lambda e: e["comm"] == "mkdir" and e["types"] == "+" and e["path"] == yes1) ++ f_json.assert_not_json(lambda e: e["comm"] == "mkdir" and e["types"] == "+" and e["path"] == no1) ++ f_json.assert_json (lambda e: e["comm"] == "touch" and "W" in e["types"] and e["path"] == f"{yes1}/yesC") ++ f_json.assert_not_json(lambda e: e["comm"] == "touch" and "W" in e["types"] and e["path"] == f"{yes1}/subA/noD") ++ f_json.assert_json (lambda e: e["comm"] == "touch" and "W" in e["types"] and e["path"] == f"{yes2}/yesE") ++ f_json.assert_not_json(lambda e: e["comm"] == "touch" and "W" in e["types"] and e["path"] == f"{no1}/noF") ++ f_json.assert_not_json(lambda e: e["comm"] == "touch" and "W" in e["types"] and e["path"] == f"{no1}/subB/noG") ++ f_json.assert_json (lambda e: e["comm"] == "touch" and "W" in e["types"] and e["path"] == f"{new_yes1}/yesH") ++ f_json.assert_not_json(lambda e: e["comm"] == "touch" and "W" in e["types"] and e["path"] == f"{new_yes1}/subA/noI") ++ f_json.assert_not_json(lambda e: e["comm"] == "touch" and "W" in e["types"] and e["path"] == f"{new_no1}/noJ") ++ f_json.assert_not_json(lambda e: e["comm"] == "touch" and "W" in e["types"] and e["path"] == f"{new_no1}/subB/noK") ++ + @unittest.skipIf("container" in os.environ, "Not supported in container environment") + @unittest.skipIf(os.path.exists("/sysroot/ostree"), "Test does not work on OSTree") + @unittest.skipIf(root_is_btrfs, "FANOTIFY does not work on btrfs, https://github.com/martinpitt/fatrace/issues/3") +-- +2.46.3 + diff --git a/system/fatrace/slack-desc b/system/fatrace/slack-desc index f9527c7825..54dc4d30f8 100644 --- a/system/fatrace/slack-desc +++ b/system/fatrace/slack-desc @@ -12,7 +12,7 @@ fatrace: fatrace reports file access events from all running processes. fatrace: Its main purpose is to find processes which keep waking up the disk fatrace: unnecessarily and thus prevent some power saving. fatrace: -fatrace: Homepage: https://launchpad.net/fatrace +fatrace: fatrace: fatrace: fatrace: |