1//go:build !js
  2// +build !js
  3
  4// Package osfs provides a billy filesystem for the OS.
  5package osfs
  6
  7import (
  8	"fmt"
  9	"io/fs"
 10	"os"
 11	"sync"
 12
 13	"github.com/go-git/go-billy/v5"
 14)
 15
 16const (
 17	defaultDirectoryMode = 0o755
 18	defaultCreateMode    = 0o666
 19)
 20
 21// Default Filesystem representing the root of the os filesystem.
 22var Default = &ChrootOS{}
 23
 24// New returns a new OS filesystem.
 25// By default paths are deduplicated, but still enforced
 26// under baseDir. For more info refer to WithDeduplicatePath.
 27//
 28// New returns ChrootOS by default for v5 compatibility. Users should prefer
 29// New with WithBoundOS.
 30func New(baseDir string, opts ...Option) billy.Filesystem {
 31	o := &options{
 32		deduplicatePath: true,
 33	}
 34	for _, opt := range opts {
 35		opt(o)
 36	}
 37
 38	if o.Type == BoundOSFS {
 39		return newBoundOS(baseDir, o.deduplicatePath)
 40	}
 41
 42	return newChrootOS(baseDir)
 43}
 44
 45// WithBoundOS returns the option of using a Bound filesystem OS.
 46func WithBoundOS() Option {
 47	return func(o *options) {
 48		o.Type = BoundOSFS
 49	}
 50}
 51
 52// WithChrootOS returns the option of using a Chroot filesystem OS.
 53//
 54// Deprecated: use WithBoundOS instead.
 55func WithChrootOS() Option {
 56	return func(o *options) {
 57		o.Type = ChrootOSFS
 58	}
 59}
 60
 61// WithDeduplicatePath toggles the deduplication of the base dir in the path.
 62// This occurs when absolute links are being used.
 63// Assuming base dir /base/dir and an absolute symlink /base/dir/target:
 64//
 65// With DeduplicatePath (default): /base/dir/target
 66// Without DeduplicatePath: /base/dir/base/dir/target
 67//
 68// This option is only used by the BoundOS OS type.
 69func WithDeduplicatePath(enabled bool) Option {
 70	return func(o *options) {
 71		o.deduplicatePath = enabled
 72	}
 73}
 74
 75type options struct {
 76	Type
 77	deduplicatePath bool
 78}
 79
 80type Type int
 81
 82const (
 83	ChrootOSFS Type = iota
 84	BoundOSFS
 85)
 86
 87func readDir(dir string) ([]os.FileInfo, error) {
 88	entries, err := os.ReadDir(dir)
 89	if err != nil {
 90		return nil, err
 91	}
 92	infos := make([]fs.FileInfo, 0, len(entries))
 93	for _, entry := range entries {
 94		fi, err := entry.Info()
 95		if err != nil {
 96			return nil, err
 97		}
 98		infos = append(infos, fi)
 99	}
100	return infos, nil
101}
102
103func tempFile(dir, prefix string) (billy.File, error) {
104	f, err := os.CreateTemp(dir, prefix)
105	if err != nil {
106		return nil, err
107	}
108	return &file{File: f}, nil
109}
110
111func openFile(fn string, flag int, perm os.FileMode, createDir func(string) error) (billy.File, error) {
112	if flag&os.O_CREATE != 0 {
113		if createDir == nil {
114			return nil, fmt.Errorf("createDir func cannot be nil if file needs to be opened in create mode")
115		}
116		if err := createDir(fn); err != nil {
117			return nil, err
118		}
119	}
120
121	f, err := os.OpenFile(fn, flag, perm)
122	if err != nil {
123		return nil, err
124	}
125	return &file{File: f}, err
126}
127
128// file is a wrapper for an os.File which adds support for file locking.
129type file struct {
130	*os.File
131	m sync.Mutex
132}