From ad84603bee29574d64ada57cd97d8e2423577b96 Mon Sep 17 00:00:00 2001
From: Yorhel <git@yorhel.nl>
Date: Thu, 22 Nov 2012 13:33:32 +0100
Subject: [PATCH] Clip directory sizes to fit in a signed 64bit integer

This mostly avoids the issue of getting negative sizes. It's still
possible to get a negative size after refresh or deletion, I'll get to
that in a bit.
---
 doc/ncdu.pod  | 4 ++++
 src/dir_mem.c | 4 ++--
 src/global.h  | 1 +
 src/util.c    | 4 ++--
 src/util.h    | 6 ++++++
 5 files changed, 15 insertions(+), 4 deletions(-)

diff --git a/doc/ncdu.pod b/doc/ncdu.pod
index ef2d4b0..3d8df5b 100644
--- a/doc/ncdu.pod
+++ b/doc/ncdu.pod
@@ -276,6 +276,10 @@ links, and will thus be scanned and counted multiple times.
 Some minor glitches may appear when displaying filenames that contain multibyte
 or multicolumn characters.
 
+All sizes are internally represented as a signed 64bit integer. If you have a
+directory larger than 8 EiB minus one byte, ncdu will clip its size to 8 EiB
+minus one byte.
+
 Please report any other bugs you may find at the bug tracker, which can be
 found on the web site at http://dev.yorhel.nl/ncdu
 
diff --git a/src/dir_mem.c b/src/dir_mem.c
index 3001c01..d13ba24 100644
--- a/src/dir_mem.c
+++ b/src/dir_mem.c
@@ -84,8 +84,8 @@ static void hlink_check(struct dir *d) {
           if(pt==par)
             i=0;
     if(i) {
-      par->size += d->size;
-      par->asize += d->asize;
+      par->size = adds64(par->size, d->size);
+      par->asize = adds64(par->size, d->asize);
     }
   }
 }
diff --git a/src/global.h b/src/global.h
index 7249bcd..d6eb2cb 100644
--- a/src/global.h
+++ b/src/global.h
@@ -29,6 +29,7 @@
 #include "config.h"
 #include <stdio.h>
 #include <stddef.h>
+#include <limits.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 
diff --git a/src/util.c b/src/util.c
index d519581..ce000e6 100644
--- a/src/util.c
+++ b/src/util.c
@@ -287,8 +287,8 @@ struct dir *getroot(struct dir *d) {
 
 void addparentstats(struct dir *d, int64_t size, int64_t asize, int items) {
   while(d) {
-    d->size += size;
-    d->asize += asize;
+    d->size = adds64(d->size, size);
+    d->asize = adds64(d->asize, asize);
     d->items += items;
     d = d->parent;
   }
diff --git a/src/util.h b/src/util.h
index 8b1b959..5c63876 100644
--- a/src/util.h
+++ b/src/util.h
@@ -81,6 +81,12 @@ char *getpath(struct dir *);
 /* returns the root element of the given dir struct */
 struct dir *getroot(struct dir *);
 
+/* Add two positive signed 64-bit integers. Returns INT64_MAX if the result
+ * would overflow.
+ * I use uint64_t's to detect the overflow, as (a + b < 0) relies on undefined
+ * behaviour, and (INT64_MAX - b >= a) didn't work for some reason. */
+#define adds64(a, b) ((uint64_t)(a) + (uint64_t)(b) > (uint64_t)INT64_MAX ? INT64_MAX : (a)+(b))
+
 /* Adds a value to the size, asize and items fields of *d and its parents */
 void addparentstats(struct dir *, int64_t, int64_t, int);
 
-- 
GitLab