Add a limit to blueprint filesystem accesses

Mac builds keep running into too many files open. Restrict access of
filesystem to the current limit.

Test: m nothing
Change-Id: I2365da7c641f7c7f5d948396c6862eb3a0d1d8b9
diff --git a/pathtools/fs.go b/pathtools/fs.go
index ed1251b..b959289 100644
--- a/pathtools/fs.go
+++ b/pathtools/fs.go
@@ -89,7 +89,7 @@
 }
 
 type FileSystem interface {
-	// Open opens a file for reading.  Follows symlinks.
+	// Open opens a file for reading. Follows symlinks.
 	Open(name string) (ReaderAtSeekerCloser, error)
 
 	// Exists returns whether the file exists and whether it is a directory.  Follows symlinks.
@@ -124,11 +124,29 @@
 
 // osFs implements FileSystem using the local disk.
 type osFs struct {
-	srcDir string
+	srcDir        string
+	openFilesChan chan bool
 }
 
 func NewOsFs(path string) FileSystem {
-	return &osFs{srcDir: path}
+	// Darwin has a default limit of 256 open files, rate limit open files to 200
+	limit := 200
+	return &osFs{
+		srcDir:        path,
+		openFilesChan: make(chan bool, limit),
+	}
+}
+
+func (fs *osFs) acquire() {
+	if fs.openFilesChan != nil {
+		fs.openFilesChan <- true
+	}
+}
+
+func (fs *osFs) release() {
+	if fs.openFilesChan != nil {
+		<-fs.openFilesChan
+	}
 }
 
 func (fs *osFs) toAbs(path string) string {
@@ -163,11 +181,31 @@
 	return paths
 }
 
+// OsFile wraps an os.File to also release open file descriptors semaphore on close
+type OsFile struct {
+	*os.File
+	fs *osFs
+}
+
+// Close closes file and releases the open file descriptor semaphore
+func (f *OsFile) Close() error {
+	err := f.File.Close()
+	f.fs.release()
+	return err
+}
+
 func (fs *osFs) Open(name string) (ReaderAtSeekerCloser, error) {
-	return os.Open(fs.toAbs(name))
+	fs.acquire()
+	f, err := os.Open(fs.toAbs(name))
+	if err != nil {
+		return nil, err
+	}
+	return &OsFile{f, fs}, nil
 }
 
 func (fs *osFs) Exists(name string) (bool, bool, error) {
+	fs.acquire()
+	defer fs.release()
 	stat, err := os.Stat(fs.toAbs(name))
 	if err == nil {
 		return true, stat.IsDir(), nil
@@ -179,6 +217,8 @@
 }
 
 func (fs *osFs) IsDir(name string) (bool, error) {
+	fs.acquire()
+	defer fs.release()
 	info, err := os.Stat(fs.toAbs(name))
 	if err != nil {
 		return false, err
@@ -187,6 +227,8 @@
 }
 
 func (fs *osFs) IsSymlink(name string) (bool, error) {
+	fs.acquire()
+	defer fs.release()
 	if info, err := os.Lstat(fs.toAbs(name)); err != nil {
 		return false, err
 	} else {
@@ -199,16 +241,22 @@
 }
 
 func (fs *osFs) glob(pattern string) ([]string, error) {
+	fs.acquire()
+	defer fs.release()
 	paths, err := filepath.Glob(fs.toAbs(pattern))
 	fs.removeSrcDirPrefixes(paths)
 	return paths, err
 }
 
 func (fs *osFs) Lstat(path string) (stats os.FileInfo, err error) {
+	fs.acquire()
+	defer fs.release()
 	return os.Lstat(fs.toAbs(path))
 }
 
 func (fs *osFs) Stat(path string) (stats os.FileInfo, err error) {
+	fs.acquire()
+	defer fs.release()
 	return os.Stat(fs.toAbs(path))
 }
 
@@ -218,6 +266,8 @@
 }
 
 func (fs *osFs) ReadDirNames(name string) ([]string, error) {
+	fs.acquire()
+	defer fs.release()
 	dir, err := os.Open(fs.toAbs(name))
 	if err != nil {
 		return nil, err
@@ -234,6 +284,8 @@
 }
 
 func (fs *osFs) Readlink(name string) (string, error) {
+	fs.acquire()
+	defer fs.release()
 	return os.Readlink(fs.toAbs(name))
 }