Skip to content

Commit 0922717

Browse files
committed
util: Add afero relpath implementation
This new filesystem implements a relative filesystem modifier which can be useful for converting between absolute filesystems rooted at / and relative ones. This is particularly useful when interfacing with the golang embed package. The upstream requires a CLA, so we'll just store this here instead. spf13/afero#417
1 parent 5a8310f commit 0922717

File tree

1 file changed

+266
-0
lines changed

1 file changed

+266
-0
lines changed

util/afero_relpath.go

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
// Mgmt
2+
// Copyright (C) 2013-2024+ James Shubin and the project contributors
3+
// Written by James Shubin <[email protected]> and the project contributors
4+
//
5+
// This program is free software: you can redistribute it and/or modify
6+
// it under the terms of the GNU General Public License as published by
7+
// the Free Software Foundation, either version 3 of the License, or
8+
// (at your option) any later version.
9+
//
10+
// This program is distributed in the hope that it will be useful,
11+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
// GNU General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU General Public License
16+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
18+
// This filesystem implementation is based on the afero.BasePathFs code.
19+
20+
package util
21+
22+
import (
23+
"io/fs"
24+
"os"
25+
"path/filepath"
26+
"strings"
27+
"time"
28+
29+
"github.com/spf13/afero"
30+
)
31+
32+
var (
33+
_ afero.Lstater = (*RelPathFs)(nil)
34+
_ fs.ReadDirFile = (*RelPathFile)(nil)
35+
)
36+
37+
// RelPathFs removes a prefix from all operations to a given path within an Fs.
38+
// The given file name to the operations on this Fs will have a prefix removed
39+
// before calling the base Fs.
40+
//
41+
// When initializing it with "/", a call to `/foo` turns into `foo`.
42+
//
43+
// Note that it does not clean the error messages on return, so you may reveal
44+
// the real path on errors.
45+
type RelPathFs struct {
46+
source afero.Fs
47+
48+
prefix string
49+
}
50+
51+
type RelPathFile struct {
52+
afero.File
53+
54+
prefix string
55+
}
56+
57+
func (obj *RelPathFile) Name() string {
58+
sourcename := obj.File.Name()
59+
//return strings.TrimPrefix(sourcename, filepath.Clean(obj.prefix))
60+
return filepath.Clean(obj.prefix) + sourcename // add prefix back on
61+
}
62+
63+
func (obj *RelPathFile) ReadDir(n int) ([]fs.DirEntry, error) {
64+
if rdf, ok := obj.File.(fs.ReadDirFile); ok {
65+
return rdf.ReadDir(n)
66+
}
67+
return readDirFile{obj.File}.ReadDir(n)
68+
}
69+
70+
// NewRelPathFs creates a new RelPathFs.
71+
func NewRelPathFs(source afero.Fs, prefix string) afero.Fs {
72+
return &RelPathFs{source: source, prefix: prefix}
73+
}
74+
75+
// on a file outside the base path it returns the given file name and an error,
76+
// else the given file with the base path removed
77+
func (obj *RelPathFs) RealPath(name string) (string, error) {
78+
if name == "/" {
79+
return ".", nil // special trim
80+
}
81+
if name == "" {
82+
return filepath.Clean(name), nil // returns a single period
83+
}
84+
path := filepath.Clean(name) // actual path
85+
prefix := filepath.Clean(obj.prefix) // is often a / and we trim it off
86+
87+
//if strings.HasPrefix(path, prefix) { // redundant
88+
path = strings.TrimPrefix(path, prefix)
89+
//}
90+
91+
return path, nil
92+
}
93+
94+
func (obj *RelPathFs) Chtimes(name string, atime, mtime time.Time) (err error) {
95+
if name, err = obj.RealPath(name); err != nil {
96+
return &os.PathError{Op: "chtimes", Path: name, Err: err}
97+
}
98+
return obj.source.Chtimes(name, atime, mtime)
99+
}
100+
101+
func (obj *RelPathFs) Chmod(name string, mode os.FileMode) (err error) {
102+
if name, err = obj.RealPath(name); err != nil {
103+
return &os.PathError{Op: "chmod", Path: name, Err: err}
104+
}
105+
return obj.source.Chmod(name, mode)
106+
}
107+
108+
func (obj *RelPathFs) Chown(name string, uid, gid int) (err error) {
109+
if name, err = obj.RealPath(name); err != nil {
110+
return &os.PathError{Op: "chown", Path: name, Err: err}
111+
}
112+
return obj.source.Chown(name, uid, gid)
113+
}
114+
115+
func (obj *RelPathFs) Name() string {
116+
return "RelPathFs"
117+
}
118+
119+
func (obj *RelPathFs) Stat(name string) (fi os.FileInfo, err error) {
120+
if name, err = obj.RealPath(name); err != nil {
121+
return nil, &os.PathError{Op: "stat", Path: name, Err: err}
122+
}
123+
return obj.source.Stat(name)
124+
}
125+
126+
func (obj *RelPathFs) Rename(oldname, newname string) (err error) {
127+
if oldname, err = obj.RealPath(oldname); err != nil {
128+
return &os.PathError{Op: "rename", Path: oldname, Err: err}
129+
}
130+
if newname, err = obj.RealPath(newname); err != nil {
131+
return &os.PathError{Op: "rename", Path: newname, Err: err}
132+
}
133+
return obj.source.Rename(oldname, newname)
134+
}
135+
136+
func (obj *RelPathFs) RemoveAll(name string) (err error) {
137+
if name, err = obj.RealPath(name); err != nil {
138+
return &os.PathError{Op: "remove_all", Path: name, Err: err}
139+
}
140+
return obj.source.RemoveAll(name)
141+
}
142+
143+
func (obj *RelPathFs) Remove(name string) (err error) {
144+
if name, err = obj.RealPath(name); err != nil {
145+
return &os.PathError{Op: "remove", Path: name, Err: err}
146+
}
147+
return obj.source.Remove(name)
148+
}
149+
150+
func (obj *RelPathFs) OpenFile(name string, flag int, mode os.FileMode) (f afero.File, err error) {
151+
if name, err = obj.RealPath(name); err != nil {
152+
return nil, &os.PathError{Op: "openfile", Path: name, Err: err}
153+
}
154+
sourcef, err := obj.source.OpenFile(name, flag, mode)
155+
if err != nil {
156+
return nil, err
157+
}
158+
return &RelPathFile{File: sourcef, prefix: obj.prefix}, nil
159+
}
160+
161+
func (obj *RelPathFs) Open(name string) (f afero.File, err error) {
162+
if name, err = obj.RealPath(name); err != nil {
163+
return nil, &os.PathError{Op: "open", Path: name, Err: err}
164+
}
165+
sourcef, err := obj.source.Open(name)
166+
if err != nil {
167+
return nil, err
168+
}
169+
return &RelPathFile{File: sourcef, prefix: obj.prefix}, nil
170+
}
171+
172+
func (obj *RelPathFs) Mkdir(name string, mode os.FileMode) (err error) {
173+
if name, err = obj.RealPath(name); err != nil {
174+
return &os.PathError{Op: "mkdir", Path: name, Err: err}
175+
}
176+
return obj.source.Mkdir(name, mode)
177+
}
178+
179+
func (obj *RelPathFs) MkdirAll(name string, mode os.FileMode) (err error) {
180+
if name, err = obj.RealPath(name); err != nil {
181+
return &os.PathError{Op: "mkdir", Path: name, Err: err}
182+
}
183+
return obj.source.MkdirAll(name, mode)
184+
}
185+
186+
func (obj *RelPathFs) Create(name string) (f afero.File, err error) {
187+
if name, err = obj.RealPath(name); err != nil {
188+
return nil, &os.PathError{Op: "create", Path: name, Err: err}
189+
}
190+
sourcef, err := obj.source.Create(name)
191+
if err != nil {
192+
return nil, err
193+
}
194+
return &RelPathFile{File: sourcef, prefix: obj.prefix}, nil
195+
}
196+
197+
func (obj *RelPathFs) LstatIfPossible(name string) (os.FileInfo, bool, error) {
198+
name, err := obj.RealPath(name)
199+
if err != nil {
200+
return nil, false, &os.PathError{Op: "lstat", Path: name, Err: err}
201+
}
202+
if lstater, ok := obj.source.(afero.Lstater); ok {
203+
return lstater.LstatIfPossible(name)
204+
}
205+
fi, err := obj.source.Stat(name)
206+
return fi, false, err
207+
}
208+
209+
func (obj *RelPathFs) SymlinkIfPossible(oldname, newname string) error {
210+
oldname, err := obj.RealPath(oldname)
211+
if err != nil {
212+
return &os.LinkError{Op: "symlink", Old: oldname, New: newname, Err: err}
213+
}
214+
newname, err = obj.RealPath(newname)
215+
if err != nil {
216+
return &os.LinkError{Op: "symlink", Old: oldname, New: newname, Err: err}
217+
}
218+
if linker, ok := obj.source.(afero.Linker); ok {
219+
return linker.SymlinkIfPossible(oldname, newname)
220+
}
221+
return &os.LinkError{Op: "symlink", Old: oldname, New: newname, Err: afero.ErrNoSymlink}
222+
}
223+
224+
func (obj *RelPathFs) ReadlinkIfPossible(name string) (string, error) {
225+
name, err := obj.RealPath(name)
226+
if err != nil {
227+
return "", &os.PathError{Op: "readlink", Path: name, Err: err}
228+
}
229+
if reader, ok := obj.source.(afero.LinkReader); ok {
230+
return reader.ReadlinkIfPossible(name)
231+
}
232+
return "", &os.PathError{Op: "readlink", Path: name, Err: afero.ErrNoReadlink}
233+
}
234+
235+
// readDirFile provides adapter from afero.File to fs.ReadDirFile needed for correct Open
236+
type readDirFile struct {
237+
afero.File
238+
}
239+
240+
var _ fs.ReadDirFile = readDirFile{}
241+
242+
func (r readDirFile) ReadDir(n int) ([]fs.DirEntry, error) {
243+
items, err := r.File.Readdir(n)
244+
if err != nil {
245+
return nil, err
246+
}
247+
248+
ret := make([]fs.DirEntry, len(items))
249+
for i := range items {
250+
//ret[i] = common.FileInfoDirEntry{FileInfo: items[i]}
251+
ret[i] = FileInfoDirEntry{FileInfo: items[i]}
252+
}
253+
254+
return ret, nil
255+
}
256+
257+
// FileInfoDirEntry provides an adapter from os.FileInfo to fs.DirEntry
258+
type FileInfoDirEntry struct {
259+
fs.FileInfo
260+
}
261+
262+
var _ fs.DirEntry = FileInfoDirEntry{}
263+
264+
func (d FileInfoDirEntry) Type() fs.FileMode { return d.FileInfo.Mode().Type() }
265+
266+
func (d FileInfoDirEntry) Info() (fs.FileInfo, error) { return d.FileInfo, nil }

0 commit comments

Comments
 (0)