xref: /aosp_15_r20/build/blueprint/pathtools/fs.go (revision 1fa6dee971e1612fa5cc0aa5ca2d35a22e2c34a3)
1// Copyright 2016 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package pathtools
16
17import (
18	"bytes"
19	"fmt"
20	"io"
21	"io/fs"
22	"io/ioutil"
23	"os"
24	"path/filepath"
25	"sort"
26	"strings"
27	"sync"
28	"syscall"
29	"time"
30)
31
32// Based on Andrew Gerrand's "10 things you (probably) dont' know about Go"
33
34type ShouldFollowSymlinks bool
35
36const (
37	FollowSymlinks     = ShouldFollowSymlinks(true)
38	DontFollowSymlinks = ShouldFollowSymlinks(false)
39)
40
41var OsFs FileSystem = &osFs{}
42
43func MockFs(files map[string][]byte) FileSystem {
44	fs := &mockFs{
45		files:    make(map[string][]byte, len(files)),
46		dirs:     make(map[string]bool),
47		symlinks: make(map[string]string),
48		all:      []string(nil),
49	}
50
51	for f, b := range files {
52		if tokens := strings.SplitN(f, "->", 2); len(tokens) == 2 {
53			fs.symlinks[strings.TrimSpace(tokens[0])] = strings.TrimSpace(tokens[1])
54			continue
55		}
56
57		fs.files[filepath.Clean(f)] = b
58		dir := filepath.Dir(f)
59		for dir != "." && dir != "/" {
60			fs.dirs[dir] = true
61			dir = filepath.Dir(dir)
62		}
63		fs.dirs[dir] = true
64	}
65
66	fs.dirs["."] = true
67	fs.dirs["/"] = true
68
69	for f := range fs.files {
70		fs.all = append(fs.all, f)
71	}
72
73	for d := range fs.dirs {
74		fs.all = append(fs.all, d)
75	}
76
77	for s := range fs.symlinks {
78		fs.all = append(fs.all, s)
79	}
80
81	sort.Strings(fs.all)
82
83	return fs
84}
85
86type ReaderAtSeekerCloser interface {
87	io.Reader
88	io.ReaderAt
89	io.Seeker
90	io.Closer
91}
92
93type FileSystem interface {
94	// Open opens a file for reading. Follows symlinks.
95	Open(name string) (ReaderAtSeekerCloser, error)
96
97	// OpenFile opens a file for read/write, if the file does not exist, and the
98	// O_CREATE flag is passed, it is created with mode perm (before umask).
99	OpenFile(name string, flag int, perm fs.FileMode) (io.WriteCloser, error)
100
101	// Exists returns whether the file exists and whether it is a directory.  Follows symlinks.
102	Exists(name string) (bool, bool, error)
103
104	Glob(pattern string, excludes []string, follow ShouldFollowSymlinks) (GlobResult, error)
105	glob(pattern string) (matches []string, err error)
106
107	// IsDir returns true if the path points to a directory, false it it points to a file.  Follows symlinks.
108	// Returns os.ErrNotExist if the path does not exist or is a symlink to a path that does not exist.
109	IsDir(name string) (bool, error)
110
111	// IsSymlink returns true if the path points to a symlink, even if that symlink points to a path that does
112	// not exist.  Returns os.ErrNotExist if the path does not exist.
113	IsSymlink(name string) (bool, error)
114
115	// Lstat returns info on a file without following symlinks.
116	Lstat(name string) (os.FileInfo, error)
117
118	// Lstat returns info on a file.
119	Stat(name string) (os.FileInfo, error)
120
121	// ListDirsRecursive returns a list of all the directories in a path, following symlinks if requested.
122	ListDirsRecursive(name string, follow ShouldFollowSymlinks) (dirs []string, err error)
123
124	// ReadDirNames returns a list of everything in a directory.
125	ReadDirNames(name string) ([]string, error)
126
127	// Readlink returns the destination of the named symbolic link.
128	Readlink(name string) (string, error)
129}
130
131// osFs implements FileSystem using the local disk.
132type osFs struct {
133	srcDir        string
134	openFilesChan chan bool
135}
136
137func NewOsFs(path string) FileSystem {
138	// Darwin has a default limit of 256 open files, rate limit open files to 200
139	limit := 200
140	return &osFs{
141		srcDir:        path,
142		openFilesChan: make(chan bool, limit),
143	}
144}
145
146func (fs *osFs) acquire() {
147	if fs.openFilesChan != nil {
148		fs.openFilesChan <- true
149	}
150}
151
152func (fs *osFs) release() {
153	if fs.openFilesChan != nil {
154		<-fs.openFilesChan
155	}
156}
157
158func (fs *osFs) toAbs(path string) string {
159	if filepath.IsAbs(path) {
160		return path
161	}
162	return filepath.Join(fs.srcDir, path)
163}
164
165func (fs *osFs) removeSrcDirPrefix(path string) string {
166	if fs.srcDir == "" {
167		return path
168	}
169	rel, err := filepath.Rel(fs.srcDir, path)
170	if err != nil {
171		panic(fmt.Errorf("unexpected failure in removeSrcDirPrefix filepath.Rel(%s, %s): %s",
172			fs.srcDir, path, err))
173	}
174	if strings.HasPrefix(rel, "../") {
175		panic(fmt.Errorf("unexpected relative path outside directory in removeSrcDirPrefix filepath.Rel(%s, %s): %s",
176			fs.srcDir, path, rel))
177	}
178	return rel
179}
180
181func (fs *osFs) removeSrcDirPrefixes(paths []string) []string {
182	if fs.srcDir != "" {
183		for i, path := range paths {
184			paths[i] = fs.removeSrcDirPrefix(path)
185		}
186	}
187	return paths
188}
189
190// OsFile wraps an os.File to also release open file descriptors semaphore on close
191type OsFile struct {
192	*os.File
193	fs *osFs
194}
195
196// Close closes file and releases the open file descriptor semaphore
197func (f *OsFile) Close() error {
198	err := f.File.Close()
199	f.fs.release()
200	return err
201}
202
203func (fs *osFs) Open(name string) (ReaderAtSeekerCloser, error) {
204	fs.acquire()
205	f, err := os.Open(fs.toAbs(name))
206	if err != nil {
207		return nil, err
208	}
209	return &OsFile{f, fs}, nil
210}
211
212func (fs *osFs) OpenFile(name string, flag int, perm fs.FileMode) (io.WriteCloser, error) {
213	fs.acquire()
214	f, err := os.OpenFile(fs.toAbs(name), flag, perm)
215	if err != nil {
216		return nil, err
217	}
218	return &OsFile{f, fs}, nil
219}
220
221func (fs *osFs) Exists(name string) (bool, bool, error) {
222	stat, err := os.Stat(fs.toAbs(name))
223	if err == nil {
224		return true, stat.IsDir(), nil
225	} else if os.IsNotExist(err) {
226		return false, false, nil
227	} else {
228		return false, false, err
229	}
230}
231
232func (fs *osFs) IsDir(name string) (bool, error) {
233	info, err := os.Stat(fs.toAbs(name))
234	if err != nil {
235		return false, err
236	}
237	return info.IsDir(), nil
238}
239
240func (fs *osFs) IsSymlink(name string) (bool, error) {
241	if info, err := os.Lstat(fs.toAbs(name)); err != nil {
242		return false, err
243	} else {
244		return info.Mode()&os.ModeSymlink != 0, nil
245	}
246}
247
248func (fs *osFs) Glob(pattern string, excludes []string, follow ShouldFollowSymlinks) (GlobResult, error) {
249	return startGlob(fs, pattern, excludes, follow)
250}
251
252func (fs *osFs) glob(pattern string) ([]string, error) {
253	fs.acquire()
254	defer fs.release()
255	paths, err := filepath.Glob(fs.toAbs(pattern))
256	fs.removeSrcDirPrefixes(paths)
257	return paths, err
258}
259
260func (fs *osFs) Lstat(path string) (stats os.FileInfo, err error) {
261	return os.Lstat(fs.toAbs(path))
262}
263
264func (fs *osFs) Stat(path string) (stats os.FileInfo, err error) {
265	return os.Stat(fs.toAbs(path))
266}
267
268// Returns a list of all directories under dir
269func (fs *osFs) ListDirsRecursive(name string, follow ShouldFollowSymlinks) (dirs []string, err error) {
270	return listDirsRecursive(fs, name, follow)
271}
272
273func (fs *osFs) ReadDirNames(name string) ([]string, error) {
274	fs.acquire()
275	defer fs.release()
276	dir, err := os.Open(fs.toAbs(name))
277	if err != nil {
278		return nil, err
279	}
280	defer dir.Close()
281
282	contents, err := dir.Readdirnames(-1)
283	if err != nil {
284		return nil, err
285	}
286
287	sort.Strings(contents)
288	return contents, nil
289}
290
291func (fs *osFs) Readlink(name string) (string, error) {
292	return os.Readlink(fs.toAbs(name))
293}
294
295type mockFs struct {
296	files    map[string][]byte
297	dirs     map[string]bool
298	symlinks map[string]string
299	all      []string
300	lock     sync.RWMutex
301}
302
303func (m *mockFs) followSymlinks(name string) string {
304	dir, file := quickSplit(name)
305	if dir != "." && dir != "/" {
306		dir = m.followSymlinks(dir)
307	}
308	name = filepath.Join(dir, file)
309
310	for i := 0; i < 255; i++ {
311		i++
312		if i > 255 {
313			panic("symlink loop")
314		}
315		to, exists := m.symlinks[name]
316		if !exists {
317			break
318		}
319		if filepath.IsAbs(to) {
320			name = to
321		} else {
322			name = filepath.Join(dir, to)
323		}
324	}
325	return name
326}
327
328func (m *mockFs) Open(name string) (ReaderAtSeekerCloser, error) {
329	m.lock.RLock()
330	defer m.lock.RUnlock()
331	name = filepath.Clean(name)
332	name = m.followSymlinks(name)
333	if f, ok := m.files[name]; ok {
334		return struct {
335			io.Closer
336			*bytes.Reader
337		}{
338			ioutil.NopCloser(nil),
339			bytes.NewReader(f),
340		}, nil
341	}
342
343	return nil, &os.PathError{
344		Op:   "open",
345		Path: name,
346		Err:  os.ErrNotExist,
347	}
348}
349
350type MockFileWriter struct {
351	name string
352	fs   *mockFs
353}
354
355func (b *MockFileWriter) Write(p []byte) (n int, err error) {
356	b.fs.lock.Lock()
357	defer b.fs.lock.Unlock()
358	b.fs.files[b.name] = append(b.fs.files[b.name], p...)
359	return n, nil
360}
361func (m *mockFs) OpenFile(name string, flag int, perm fs.FileMode) (io.WriteCloser, error) {
362	// For mockFs we simplify the logic here by just either creating a new file or
363	// truncating an existing one.
364	m.lock.Lock()
365	defer m.lock.Unlock()
366	name = filepath.Clean(name)
367	name = m.followSymlinks(name)
368	m.files[name] = []byte{}
369	return struct {
370		io.Closer
371		io.Writer
372	}{
373		ioutil.NopCloser(nil),
374		&MockFileWriter{
375			name: name,
376			fs:   m,
377		},
378	}, nil
379}
380
381func (m *mockFs) Exists(name string) (bool, bool, error) {
382	m.lock.RLock()
383	defer m.lock.RUnlock()
384	name = filepath.Clean(name)
385	name = m.followSymlinks(name)
386	if _, ok := m.files[name]; ok {
387		return ok, false, nil
388	}
389	if _, ok := m.dirs[name]; ok {
390		return ok, true, nil
391	}
392	return false, false, nil
393}
394
395func (m *mockFs) IsDir(name string) (bool, error) {
396	m.lock.RLock()
397	defer m.lock.RUnlock()
398	dir := filepath.Dir(name)
399	if dir != "." && dir != "/" {
400		isDir, err := m.IsDir(dir)
401
402		if serr, ok := err.(*os.SyscallError); ok && serr.Err == syscall.ENOTDIR {
403			isDir = false
404		} else if err != nil {
405			return false, err
406		}
407
408		if !isDir {
409			return false, os.NewSyscallError("stat "+name, syscall.ENOTDIR)
410		}
411	}
412
413	name = filepath.Clean(name)
414	name = m.followSymlinks(name)
415
416	if _, ok := m.dirs[name]; ok {
417		return true, nil
418	}
419	if _, ok := m.files[name]; ok {
420		return false, nil
421	}
422	return false, os.ErrNotExist
423}
424
425func (m *mockFs) IsSymlink(name string) (bool, error) {
426	m.lock.RLock()
427	defer m.lock.RUnlock()
428	dir, file := quickSplit(name)
429	dir = m.followSymlinks(dir)
430	name = filepath.Join(dir, file)
431
432	if _, isSymlink := m.symlinks[name]; isSymlink {
433		return true, nil
434	}
435	if _, isDir := m.dirs[name]; isDir {
436		return false, nil
437	}
438	if _, isFile := m.files[name]; isFile {
439		return false, nil
440	}
441	return false, os.ErrNotExist
442}
443
444func (m *mockFs) Glob(pattern string, excludes []string, follow ShouldFollowSymlinks) (GlobResult, error) {
445	return startGlob(m, pattern, excludes, follow)
446}
447
448func unescapeGlob(s string) string {
449	i := 0
450	for i < len(s) {
451		if s[i] == '\\' {
452			s = s[:i] + s[i+1:]
453		} else {
454			i++
455		}
456	}
457	return s
458}
459
460func (m *mockFs) glob(pattern string) ([]string, error) {
461	dir, file := quickSplit(pattern)
462
463	dir = unescapeGlob(dir)
464	toDir := m.followSymlinks(dir)
465
466	var matches []string
467	for _, f := range m.all {
468		fDir, fFile := quickSplit(f)
469		if toDir == fDir {
470			match, err := filepath.Match(file, fFile)
471			if err != nil {
472				return nil, err
473			}
474			if (f == "." || f == "/") && f != pattern {
475				// filepath.Glob won't return "." or "/" unless the pattern was "." or "/"
476				match = false
477			}
478			if match {
479				matches = append(matches, filepath.Join(dir, fFile))
480			}
481		}
482	}
483	return matches, nil
484}
485
486type mockStat struct {
487	name string
488	size int64
489	mode os.FileMode
490}
491
492func (ms *mockStat) Name() string       { return ms.name }
493func (ms *mockStat) IsDir() bool        { return ms.Mode().IsDir() }
494func (ms *mockStat) Size() int64        { return ms.size }
495func (ms *mockStat) Mode() os.FileMode  { return ms.mode }
496func (ms *mockStat) ModTime() time.Time { return time.Time{} }
497func (ms *mockStat) Sys() interface{}   { return nil }
498
499func (m *mockFs) Lstat(name string) (os.FileInfo, error) {
500	m.lock.RLock()
501	defer m.lock.RUnlock()
502	dir, file := quickSplit(name)
503	dir = m.followSymlinks(dir)
504	name = filepath.Join(dir, file)
505
506	ms := mockStat{
507		name: file,
508	}
509
510	if symlink, isSymlink := m.symlinks[name]; isSymlink {
511		ms.mode = os.ModeSymlink
512		ms.size = int64(len(symlink))
513	} else if _, isDir := m.dirs[name]; isDir {
514		ms.mode = os.ModeDir
515	} else if _, isFile := m.files[name]; isFile {
516		ms.mode = 0
517		ms.size = int64(len(m.files[name]))
518	} else {
519		return nil, os.ErrNotExist
520	}
521
522	return &ms, nil
523}
524
525func (m *mockFs) Stat(name string) (os.FileInfo, error) {
526	m.lock.RLock()
527	defer m.lock.RUnlock()
528	name = filepath.Clean(name)
529	origName := name
530	name = m.followSymlinks(name)
531
532	ms := mockStat{
533		name: filepath.Base(origName),
534		size: int64(len(m.files[name])),
535	}
536
537	if _, isDir := m.dirs[name]; isDir {
538		ms.mode = os.ModeDir
539	} else if _, isFile := m.files[name]; isFile {
540		ms.mode = 0
541		ms.size = int64(len(m.files[name]))
542	} else {
543		return nil, os.ErrNotExist
544	}
545
546	return &ms, nil
547}
548
549func (m *mockFs) ReadDirNames(name string) ([]string, error) {
550	name = filepath.Clean(name)
551	name = m.followSymlinks(name)
552
553	exists, isDir, err := m.Exists(name)
554	if err != nil {
555		return nil, err
556	}
557	if !exists {
558		return nil, os.ErrNotExist
559	}
560	if !isDir {
561		return nil, os.NewSyscallError("readdir", syscall.ENOTDIR)
562	}
563
564	var ret []string
565	for _, f := range m.all {
566		dir, file := quickSplit(f)
567		if dir == name && len(file) > 0 && file[0] != '.' {
568			ret = append(ret, file)
569		}
570	}
571	return ret, nil
572}
573
574func (m *mockFs) ListDirsRecursive(name string, follow ShouldFollowSymlinks) ([]string, error) {
575	return listDirsRecursive(m, name, follow)
576}
577
578func (m *mockFs) Readlink(name string) (string, error) {
579	dir, file := quickSplit(name)
580	dir = m.followSymlinks(dir)
581
582	origName := name
583	name = filepath.Join(dir, file)
584
585	if dest, isSymlink := m.symlinks[name]; isSymlink {
586		return dest, nil
587	}
588
589	if exists, _, err := m.Exists(name); err != nil {
590		return "", err
591	} else if !exists {
592		return "", os.ErrNotExist
593	} else {
594		return "", os.NewSyscallError("readlink: "+origName, syscall.EINVAL)
595	}
596}
597
598func listDirsRecursive(fs FileSystem, name string, follow ShouldFollowSymlinks) ([]string, error) {
599	name = filepath.Clean(name)
600
601	isDir, err := fs.IsDir(name)
602	if err != nil {
603		return nil, err
604	}
605
606	if !isDir {
607		return nil, nil
608	}
609
610	dirs := []string{name}
611
612	subDirs, err := listDirsRecursiveRelative(fs, name, follow, 0)
613	if err != nil {
614		return nil, err
615	}
616
617	for _, d := range subDirs {
618		dirs = append(dirs, filepath.Join(name, d))
619	}
620
621	return dirs, nil
622}
623
624func listDirsRecursiveRelative(fs FileSystem, name string, follow ShouldFollowSymlinks, depth int) ([]string, error) {
625	depth++
626	if depth > 255 {
627		return nil, fmt.Errorf("too many symlinks")
628	}
629	contents, err := fs.ReadDirNames(name)
630	if err != nil {
631		return nil, err
632	}
633
634	var dirs []string
635	for _, f := range contents {
636		if f[0] == '.' {
637			continue
638		}
639		f = filepath.Join(name, f)
640		var info os.FileInfo
641		if follow == DontFollowSymlinks {
642			info, err = fs.Lstat(f)
643			if err != nil {
644				continue
645			}
646			if info.Mode()&os.ModeSymlink != 0 {
647				continue
648			}
649		} else {
650			info, err = fs.Stat(f)
651			if err != nil {
652				continue
653			}
654		}
655		if info.IsDir() {
656			dirs = append(dirs, f)
657			subDirs, err := listDirsRecursiveRelative(fs, f, follow, depth)
658			if err != nil {
659				return nil, err
660			}
661			for _, s := range subDirs {
662				dirs = append(dirs, filepath.Join(f, s))
663			}
664		}
665	}
666
667	for i, d := range dirs {
668		rel, err := filepath.Rel(name, d)
669		if err != nil {
670			return nil, err
671		}
672		dirs[i] = rel
673	}
674
675	return dirs, nil
676}
677