From f7fe1d766b686078fb429710852a0cf6bdd08179 Mon Sep 17 00:00:00 2001 From: Claudio Bantaloukas Date: Mon, 28 Dec 2020 14:08:12 +0000 Subject: [PATCH] cmd/ncdu: highlight read errors instead of aborting - fixes #4014 When a directory cannot be walk-ed because of a permissions error - or any error for that matter -, ncdu mode keeps track of the error and highlights directories that could not be read. Previously, the error would cause ncdu to abort. Now, directories with unreadable sub-directories are displayed in yellow and a message warns that the total may be underestimated. Unreadable directories themselves are displayed in red along with the error message --- cmd/ncdu/ncdu.go | 20 ++++++++++++++++---- cmd/ncdu/scan/scan.go | 44 +++++++++++++++++++++++-------------------- 2 files changed, 40 insertions(+), 24 deletions(-) diff --git a/cmd/ncdu/ncdu.go b/cmd/ncdu/ncdu.go index b3ccc079b..4bc1d53e6 100644 --- a/cmd/ncdu/ncdu.go +++ b/cmd/ncdu/ncdu.go @@ -281,7 +281,7 @@ func (u *UI) biggestEntry() (biggest int64) { return } for i := range u.entries { - size, _, _, _ := u.d.AttrI(u.sortPerm[i]) + size, _, _, _, _, _ := u.d.AttrI(u.sortPerm[i]) if size > biggest { biggest = size } @@ -326,12 +326,18 @@ func (u *UI) Draw() error { if y >= h-1 { break } + size, count, isDir, readable, entriesHaveErrors, err := u.d.AttrI(u.sortPerm[n]) fg := termbox.ColorWhite + if entriesHaveErrors { + fg = termbox.ColorYellow + } + if err != nil { + fg = termbox.ColorRed + } bg := termbox.ColorBlack if n == dirPos.entry { fg, bg = bg, fg } - size, count, isDir, readable := u.d.AttrI(u.sortPerm[n]) mark := ' ' if isDir { mark = '/' @@ -340,6 +346,12 @@ func (u *UI) Draw() error { if !readable { message = " [not read yet]" } + if entriesHaveErrors { + message = " [some subdirectories could not be read, size may be underestimated]" + } + if err != nil { + message = fmt.Sprintf(" [%s]", err) + } extras := "" if u.showCounts { if count > 0 { @@ -512,8 +524,8 @@ type ncduSort struct { // Less is part of sort.Interface. func (ds *ncduSort) Less(i, j int) bool { var iAvgSize, jAvgSize float64 - isize, icount, _, _ := ds.d.AttrI(ds.sortPerm[i]) - jsize, jcount, _, _ := ds.d.AttrI(ds.sortPerm[j]) + isize, icount, _, _, _, _ := ds.d.AttrI(ds.sortPerm[i]) + jsize, jcount, _, _, _, _ := ds.d.AttrI(ds.sortPerm[j]) iname, jname := ds.entries[ds.sortPerm[i]].Remote(), ds.entries[ds.sortPerm[j]].Remote() if icount > 0 { iAvgSize = float64(isize / icount) diff --git a/cmd/ncdu/scan/scan.go b/cmd/ncdu/scan/scan.go index 5957e6916..77b15ff8e 100644 --- a/cmd/ncdu/scan/scan.go +++ b/cmd/ncdu/scan/scan.go @@ -13,13 +13,15 @@ import ( // Dir represents a directory found in the remote type Dir struct { - parent *Dir - path string - mu sync.Mutex - count int64 - size int64 - entries fs.DirEntries - dirs map[string]*Dir + parent *Dir + path string + mu sync.Mutex + count int64 + size int64 + entries fs.DirEntries + dirs map[string]*Dir + readError error + entriesHaveErrors bool } // Parent returns the directory above this one @@ -35,12 +37,13 @@ func (d *Dir) Path() string { } // make a new directory -func newDir(parent *Dir, dirPath string, entries fs.DirEntries) *Dir { +func newDir(parent *Dir, dirPath string, entries fs.DirEntries, err error) *Dir { d := &Dir{ - parent: parent, - path: dirPath, - entries: entries, - dirs: make(map[string]*Dir), + parent: parent, + path: dirPath, + entries: entries, + dirs: make(map[string]*Dir), + readError: err, } // Count size in this dir for _, entry := range entries { @@ -61,6 +64,9 @@ func newDir(parent *Dir, dirPath string, entries fs.DirEntries) *Dir { parent.mu.Lock() parent.count += d.count parent.size += d.size + if d.readError != nil { + parent.entriesHaveErrors = true + } parent.mu.Unlock() } return d @@ -145,18 +151,19 @@ func (d *Dir) Attr() (size int64, count int64) { } // AttrI returns the size, count and flags for the i-th directory entry -func (d *Dir) AttrI(i int) (size int64, count int64, isDir bool, readable bool) { +func (d *Dir) AttrI(i int) (size int64, count int64, isDir bool, readable bool, entriesHaveErrors bool, err error) { d.mu.Lock() defer d.mu.Unlock() subDir, isDir := d.getDir(i) + if !isDir { - return d.entries[i].Size(), 0, false, true + return d.entries[i].Size(), 0, false, true, d.entriesHaveErrors, d.readError } if subDir == nil { - return 0, 0, true, false + return 0, 0, true, false, false, nil } size, count = subDir.Attr() - return size, count, true, true + return size, count, true, true, subDir.entriesHaveErrors, subDir.readError } // Scan the Fs passed in, returning a root directory channel and an @@ -169,9 +176,6 @@ func Scan(ctx context.Context, f fs.Fs) (chan *Dir, chan error, chan struct{}) { go func() { parents := map[string]*Dir{} err := walk.Walk(ctx, f, "", false, ci.MaxDepth, func(dirPath string, entries fs.DirEntries, err error) error { - if err != nil { - return err // FIXME mark directory as errored instead of aborting - } var parent *Dir if dirPath != "" { parentPath := path.Dir(dirPath) @@ -184,7 +188,7 @@ func Scan(ctx context.Context, f fs.Fs) (chan *Dir, chan error, chan struct{}) { errChan <- errors.Errorf("couldn't find parent for %q", dirPath) } } - d := newDir(parent, dirPath, entries) + d := newDir(parent, dirPath, entries, err) parents[dirPath] = d if dirPath == "" { root <- d