From de5b9a824b37b65c57a53d06259e84a7d12f7454 Mon Sep 17 00:00:00 2001 From: James Hillyerd Date: Sat, 28 Jan 2017 16:11:51 -0800 Subject: [PATCH] Remove empty intermediate directories, closes #12 --- CHANGELOG.md | 2 ++ smtpd/filestore.go | 72 +++++++++++++++++++++++++++++++++++----------- 2 files changed, 57 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f71f589..d831809 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed - No longer run out of file handles when dealing with a large number of recipients for a single message. +- Empty intermediate directories are now removed when a mailbox is deleted, + leaving less junk on your filesystem. ### Changed - Build now requires Go 1.7 or later diff --git a/smtpd/filestore.go b/smtpd/filestore.go index bdb929f..d59699a 100644 --- a/smtpd/filestore.go +++ b/smtpd/filestore.go @@ -239,19 +239,6 @@ func (mb *FileMailbox) readIndex() error { return nil } -// createDir checks for the presence of the path for this mailbox, creates it if needed -func (mb *FileMailbox) createDir() error { - dirMx.Lock() - defer dirMx.Unlock() - if _, err := os.Stat(mb.path); err != nil { - if err := os.MkdirAll(mb.path, 0770); err != nil { - log.Errorf("Failed to create directory %v, %v", mb.path, err) - return err - } - } - return nil -} - // writeIndex overwrites the index on disk with the current mailbox data func (mb *FileMailbox) writeIndex() error { // Lock for writing @@ -288,15 +275,66 @@ func (mb *FileMailbox) writeIndex() error { } else { // No messages, delete index+maildir log.Tracef("Removing mailbox %v", mb.path) - // deletes are dangerous, requires write lock - dirMx.Lock() - defer dirMx.Unlock() - return os.RemoveAll(mb.path) + return mb.removeDir() } return nil } +// createDir checks for the presence of the path for this mailbox, creates it if needed +func (mb *FileMailbox) createDir() error { + dirMx.Lock() + defer dirMx.Unlock() + if _, err := os.Stat(mb.path); err != nil { + if err := os.MkdirAll(mb.path, 0770); err != nil { + log.Errorf("Failed to create directory %v, %v", mb.path, err) + return err + } + } + return nil +} + +// removeDir removes the mailbox, plus empty higher level directories +func (mb *FileMailbox) removeDir() error { + dirMx.Lock() + defer dirMx.Unlock() + // remove mailbox dir, including index file + if err := os.RemoveAll(mb.path); err != nil { + return err + } + // remove parents if empty + dir := filepath.Dir(mb.path) + if removeDirIfEmpty(dir) { + removeDirIfEmpty(filepath.Dir(dir)) + } + return nil +} + +// removeDirIfEmpty will remove the specified directory if it contains no files or directories. +// Caller should hold dirMx. Returns true if dir was removed. +func removeDirIfEmpty(path string) (removed bool) { + f, err := os.Open(path) + if err != nil { + return false + } + files, err := f.Readdirnames(0) + _ = f.Close() + if err != nil { + return false + } + if len(files) > 0 { + // Dir not empty + return false + } + log.Tracef("Removing dir %v", path) + err = os.Remove(path) + if err != nil { + log.Errorf("Failed to remove %q: %v", path, err) + return false + } + return true +} + // generatePrefix converts a Time object into the ISO style format we use // as a prefix for message files. Note: It is used directly by unit // tests.