Go library shows filesystem changes across platforms

Short Shrift for Want of Space

To shorten the eternal error checks after function calls with err != nil requiring countless if conditions to a magazine-friendly code length, Listing 2 defines the dieOnErr() function in line 77; this simply aborts on any error. Under production conditions, you would want to log the error here instead and eventually handle it upstream, but then you wouldn't have to worry about the shortage of space inherent in print products. Ha!

The callback function then uses IsDir() to check for each newly discovered entry below the ~/go/ directory. If it is not a file but a subdirectory, the function sets another watcher for it with Add(). Each of these watchers consumes a file descriptor on Linux, and the operating system does not have an unlimited supply of them. The ulimit -n command shows the number of file descriptors available and gives the administrator the ability to increase the number. Under normal circumstances, however, the default of 1,024 descriptors is fine.

The call to watchInit() in line 22 of the main program determines what happens on arriving inotify events. The function starts in line 51, with an asynchronous goroutine waiting for events from all defined watchers up to this point. If the event belongs to a newly generated directory within the monitored file structure, line 67 also sets a watcher for it. Luckily, the program does not need to worry about watchers being defined twice for the same entry: fsnotify is smart enough to ignore duplicates.

Race Condition

However, this method is not completely reliable: If the tracker notices the genesis of a new directory, it needs to quickly set up a watcher for it to track future changes in the directory. But if an application creates files in the directory immediately after it is created, it could beat the tracker to it, and the tracker would not notice the change.

Also, the Walk() function originally called to collect all the subdirectories does not continue to track symbolic links. If you want to do that, you have to resolve them with the EvalSymlinks() function, but watch it carefully to prevent the walker getting stuck in an infinite loop.

Events reporting the renaming or deletion of an entry are filtered out by the if condition in line 59, because an os.Stat() for such an entry would fail. Line 63 prints all other events in a nicely formatted way.

Figure 3 shows what the watcher finds while running the Go compiler with:

go build fswatch.go
Figure 3: While the Go compiler loads sources from GitHub to compile a binary, fsnotify keeps track of the new files created in the process.

The watcher reports the creation of quite a few cache directories that help compile and include the fsnotify code downloaded from GitHub. The cr abbreviation here stands for the "create" action. Other messages have an identifier of ch for chmod when the compiler manipulates the access bits. If compiling a long Go program with many dependencies drags on and on, this little helper tells you exactly what the compiler is doing and you can guesstimate how long it will continue to run.

To neatly log the events that occur, the eventAsString() function reformats them in what is still a pretty rudimentary way starting at line 39. Line 42 truncates the event names to their first two characters and converts them to lowercase with ToLower(). Line 43 splits long directory paths into their components; line 46 shortens them to the last three partial paths (if it finds any more). Using the [m:n] array slice syntax, it extracts the last three with len(dirPaths)-3 for m and len(dirParts) for n.

The element at index m is by definition included in the result, while the one for n is not. Since Join() from the filepath package joins a variable number of individual elements rather than an array of partial paths, the final three dots turn the array slice coming from the slice operator into a flattened list of individual elements.

The whole thing could now be nicely embedded in a UI that constantly entertains the user with updates for each compiler run, so that you can immediately see whether it is just the network hanging or if the process is taking so long because of code bloat or dependency hell.


  1. "Detecting system changes with Dnotify" by Mike Schilli, Linux Magazine, issue 63, February 2006, https://www.linux-magazine.com/Issues/2006/63/Perl-noworries/(language)/eng-US
  2. Listings for this article: ftp://ftp.linux-magazine.com/pub/listings/linux-magazine.com/247/

The Author

Mike Schilli works as a software engineer in the San Francisco Bay area, California. Each month in his column, which has been running since 1997, he researches practical applications of various programming languages. If you email him at mailto:mschilli@perlmeister.com he will gladly answer any questions.

Buy this article as PDF

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy Linux Magazine

Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

  • Sweet Dreams

    Bathtub singer Mike Schilli builds a Go tool that manages song lyrics from YAML files and helps him learn them by heart, line by line.

  • Ready to Rumble

    A Go program writes a downloaded ISO file to a bootable USB stick. To prevent it from accidentally overwriting the hard disk, Mike Schilli provides it with a user interface and security checks.

  • Making History

    In the history log, the Bash shell records all commands typed by the user. Mike Schilli extracts this data with Go for a statistical analysis of his typing behavior.

  • Perl: noworries

    We'll show you how you can avoid the tragedy of lost files with a transparent, Perl-based version control system.

  • Perl: Link Spam

    Spammers don’t just send email. They exploit discussion forums and blogs, posting pseudo-messages full of links to dupe search engines. A Perl script cleans up the mess.

comments powered by Disqus

Direct Download

Read full article as PDF:

Price $2.95