Permalink
Cannot retrieve contributors at this time
Fetching contributors…
| // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. | |
| // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ | |
| // See page 251. | |
| // The du4 command computes the disk usage of the files in a directory. | |
| package main | |
| // The du4 variant includes cancellation: | |
| // it terminates quickly when the user hits return. | |
| import ( | |
| "fmt" | |
| "os" | |
| "path/filepath" | |
| "sync" | |
| "time" | |
| ) | |
| //!+1 | |
| var done = make(chan struct{}) | |
| func cancelled() bool { | |
| select { | |
| case <-done: | |
| return true | |
| default: | |
| return false | |
| } | |
| } | |
| //!-1 | |
| func main() { | |
| // Determine the initial directories. | |
| roots := os.Args[1:] | |
| if len(roots) == 0 { | |
| roots = []string{"."} | |
| } | |
| //!+2 | |
| // Cancel traversal when input is detected. | |
| go func() { | |
| os.Stdin.Read(make([]byte, 1)) // read a single byte | |
| close(done) | |
| }() | |
| //!-2 | |
| // Traverse each root of the file tree in parallel. | |
| fileSizes := make(chan int64) | |
| var n sync.WaitGroup | |
| for _, root := range roots { | |
| n.Add(1) | |
| go walkDir(root, &n, fileSizes) | |
| } | |
| go func() { | |
| n.Wait() | |
| close(fileSizes) | |
| }() | |
| // Print the results periodically. | |
| tick := time.Tick(500 * time.Millisecond) | |
| var nfiles, nbytes int64 | |
| loop: | |
| //!+3 | |
| for { | |
| select { | |
| case <-done: | |
| // Drain fileSizes to allow existing goroutines to finish. | |
| for range fileSizes { | |
| // Do nothing. | |
| } | |
| return | |
| case size, ok := <-fileSizes: | |
| // ... | |
| //!-3 | |
| if !ok { | |
| break loop // fileSizes was closed | |
| } | |
| nfiles++ | |
| nbytes += size | |
| case <-tick: | |
| printDiskUsage(nfiles, nbytes) | |
| } | |
| } | |
| printDiskUsage(nfiles, nbytes) // final totals | |
| } | |
| func printDiskUsage(nfiles, nbytes int64) { | |
| fmt.Printf("%d files %.1f GB\n", nfiles, float64(nbytes)/1e9) | |
| } | |
| // walkDir recursively walks the file tree rooted at dir | |
| // and sends the size of each found file on fileSizes. | |
| //!+4 | |
| func walkDir(dir string, n *sync.WaitGroup, fileSizes chan<- int64) { | |
| defer n.Done() | |
| if cancelled() { | |
| return | |
| } | |
| for _, entry := range dirents(dir) { | |
| // ... | |
| //!-4 | |
| if entry.IsDir() { | |
| n.Add(1) | |
| subdir := filepath.Join(dir, entry.Name()) | |
| go walkDir(subdir, n, fileSizes) | |
| } else { | |
| fileSizes <- entry.Size() | |
| } | |
| //!+4 | |
| } | |
| } | |
| //!-4 | |
| var sema = make(chan struct{}, 20) // concurrency-limiting counting semaphore | |
| // dirents returns the entries of directory dir. | |
| //!+5 | |
| func dirents(dir string) []os.FileInfo { | |
| select { | |
| case sema <- struct{}{}: // acquire token | |
| case <-done: | |
| return nil // cancelled | |
| } | |
| defer func() { <-sema }() // release token | |
| // ...read directory... | |
| //!-5 | |
| f, err := os.Open(dir) | |
| if err != nil { | |
| fmt.Fprintf(os.Stderr, "du: %v\n", err) | |
| return nil | |
| } | |
| defer f.Close() | |
| entries, err := f.Readdir(0) // 0 => no limit; read all entries | |
| if err != nil { | |
| fmt.Fprintf(os.Stderr, "du: %v\n", err) | |
| // Don't return: Readdir may return partial results. | |
| } | |
| return entries | |
| } |