github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libfs/root_fs.go (about) 1 // Copyright 2019 Keybase Inc. All rights reserved. 2 // Use of this source code is governed by a BSD 3 // license that can be found in the LICENSE file. 4 5 package libfs 6 7 import ( 8 "context" 9 "fmt" 10 "net/http" 11 "os" 12 "path" 13 "time" 14 15 "github.com/keybase/client/go/kbfs/libkbfs" 16 "github.com/keybase/client/go/logger" 17 "github.com/pkg/errors" 18 billy "gopkg.in/src-d/go-billy.v4" 19 ) 20 21 func newStatusFileNode( 22 config libkbfs.Config, log logger.Logger) *namedFileNode { 23 return &namedFileNode{ 24 Node: nil, 25 log: log, 26 name: StatusFileName, 27 reader: func(ctx context.Context) ([]byte, time.Time, error) { 28 return GetEncodedStatus(ctx, config) 29 }, 30 } 31 } 32 33 func newUserEditHistoryFileNode( 34 config libkbfs.Config, log logger.Logger) *namedFileNode { 35 return &namedFileNode{ 36 Node: nil, 37 log: log, 38 name: EditHistoryName, 39 reader: func(ctx context.Context) ([]byte, time.Time, error) { 40 return GetEncodedUserEditHistory(ctx, config) 41 }, 42 } 43 } 44 45 // RootFS is a browseable (read-only) version of `/keybase`. It 46 // does not support traversal into any subdirectories. 47 type RootFS struct { 48 config libkbfs.Config 49 log logger.Logger 50 } 51 52 // NewRootFS creates a new RootFS instance. 53 func NewRootFS(config libkbfs.Config) *RootFS { 54 return &RootFS{ 55 config: config, 56 log: config.MakeLogger(""), 57 } 58 } 59 60 var _ billy.Filesystem = (*RootFS)(nil) 61 62 ///// Read-only functions: 63 64 var rootWrappedNodeNames = map[string]bool{ 65 StatusFileName: true, 66 MetricsFileName: true, 67 ErrorFileName: true, 68 EditHistoryName: true, 69 ProfileListDirName: true, 70 } 71 72 // Open implements the billy.Filesystem interface for RootFS. 73 func (rfs *RootFS) Open(filename string) (f billy.File, err error) { 74 if !rootWrappedNodeNames[filename] { 75 // In particular, this FS doesn't let you open the folderlist 76 // directories or anything in them. 77 return nil, os.ErrNotExist 78 } 79 80 ctx := context.TODO() 81 switch filename { 82 case StatusFileName: 83 return newStatusFileNode(rfs.config, rfs.log).GetFile(ctx), nil 84 case MetricsFileName: 85 return newMetricsFileNode(rfs.config, nil, rfs.log).GetFile(ctx), nil 86 case ErrorFileName: 87 return newErrorFileNode(rfs.config, nil, rfs.log).GetFile(ctx), nil 88 case EditHistoryName: 89 return newUserEditHistoryFileNode(rfs.config, rfs.log).GetFile(ctx), nil 90 default: 91 panic(fmt.Sprintf("Name %s was in map, but not in switch", filename)) 92 } 93 } 94 95 // OpenFile implements the billy.Filesystem interface for RootFS. 96 func (rfs *RootFS) OpenFile(filename string, flag int, _ os.FileMode) ( 97 f billy.File, err error) { 98 if flag&os.O_CREATE != 0 { 99 return nil, errors.New("RootFS can't create files") 100 } 101 102 return rfs.Open(filename) 103 } 104 105 // Lstat implements the billy.Filesystem interface for RootFS. 106 func (rfs *RootFS) Lstat(filename string) (fi os.FileInfo, err error) { 107 if filename == "" { 108 filename = "." 109 } 110 if filename == "." { 111 return &wrappedReadFileInfo{ 112 "keybase", 0, rfs.config.Clock().Now(), true}, nil 113 } 114 if !rootWrappedNodeNames[filename] { 115 return nil, os.ErrNotExist 116 } 117 118 ctx := context.TODO() 119 switch filename { 120 case StatusFileName: 121 sfn := newStatusFileNode(rfs.config, rfs.log).GetFile(ctx) 122 return sfn.(*wrappedReadFile).GetInfo(), nil 123 case MetricsFileName: 124 mfn := newMetricsFileNode(rfs.config, nil, rfs.log).GetFile(ctx) 125 return mfn.(*wrappedReadFile).GetInfo(), nil 126 case ErrorFileName: 127 efn := newErrorFileNode(rfs.config, nil, rfs.log).GetFile(ctx) 128 return efn.(*wrappedReadFile).GetInfo(), nil 129 case EditHistoryName: 130 uehfn := newUserEditHistoryFileNode(rfs.config, rfs.log).GetFile(ctx) 131 return uehfn.(*wrappedReadFile).GetInfo(), nil 132 case ProfileListDirName: 133 return &wrappedReadFileInfo{ 134 filename, 0, rfs.config.Clock().Now(), true}, nil 135 default: 136 panic(fmt.Sprintf("Name %s was in map, but not in switch", filename)) 137 } 138 } 139 140 // Stat implements the billy.Filesystem interface for RootFS. 141 func (rfs *RootFS) Stat(filename string) (fi os.FileInfo, err error) { 142 return rfs.Lstat(filename) 143 } 144 145 // Join implements the billy.Filesystem interface for RootFS. 146 func (rfs *RootFS) Join(elem ...string) string { 147 return path.Clean(path.Join(elem...)) 148 } 149 150 // ReadDir implements the billy.Filesystem interface for RootFS. 151 func (rfs *RootFS) ReadDir(p string) (fis []os.FileInfo, err error) { 152 switch p { 153 case ProfileListDirName: 154 return NewProfileFS(rfs.config).ReadDir("") 155 case "", ".": 156 // Fall through. 157 default: 158 return nil, os.ErrNotExist 159 } 160 161 now := rfs.config.Clock().Now() 162 return []os.FileInfo{ 163 &wrappedReadFileInfo{"private", 0, now, true}, 164 &wrappedReadFileInfo{"public", 0, now, true}, 165 &wrappedReadFileInfo{"team", 0, now, true}, 166 }, nil 167 } 168 169 // Readlink implements the billy.Filesystem interface for RootFS. 170 func (rfs *RootFS) Readlink(_ string) (target string, err error) { 171 return "", errors.New("RootFS cannot read links") 172 } 173 174 // Chroot implements the billy.Filesystem interface for RootFS. 175 func (rfs *RootFS) Chroot(p string) (newFS billy.Filesystem, err error) { 176 if p == ProfileListDirName { 177 return dummyFSReadOnly{ProfileFS{rfs.config}}, nil 178 } 179 // Don't allow chroot'ing anywhere elsewhere outside of the root 180 // FS since we haven't yet implemented folderlist browsing. 181 return nil, errors.New("RootFS cannot chroot") 182 } 183 184 // Root implements the billy.Filesystem interface for RootFS. 185 func (rfs *RootFS) Root() string { 186 return "/keybase" 187 } 188 189 ///// Modifying functions (not supported): 190 191 // Create implements the billy.Filesystem interface for RootFS. 192 func (rfs *RootFS) Create(_ string) (billy.File, error) { 193 return nil, errors.New("RootFS cannot create files") 194 } 195 196 // Rename implements the billy.Filesystem interface for RootFS. 197 func (rfs *RootFS) Rename(_, _ string) (err error) { 198 return errors.New("RootFS cannot rename files") 199 } 200 201 // Remove implements the billy.Filesystem interface for RootFS. 202 func (rfs *RootFS) Remove(_ string) (err error) { 203 return errors.New("RootFS cannot remove files") 204 } 205 206 // TempFile implements the billy.Filesystem interface for RootFS. 207 func (rfs *RootFS) TempFile(_, _ string) (billy.File, error) { 208 return nil, errors.New("RootFS cannot make temp files") 209 } 210 211 // MkdirAll implements the billy.Filesystem interface for RootFS. 212 func (rfs *RootFS) MkdirAll(_ string, _ os.FileMode) (err error) { 213 return errors.New("RootFS cannot mkdir") 214 } 215 216 // Symlink implements the billy.Filesystem interface for RootFS. 217 func (rfs *RootFS) Symlink(_, _ string) (err error) { 218 return errors.New("RootFS cannot make symlinks") 219 } 220 221 // ToHTTPFileSystem calls fs.WithCtx with ctx to create a *RootFS with the new 222 // ctx, and returns a wrapper around it that satisfies the http.FileSystem 223 // interface. ctx is ignored here. 224 func (rfs *RootFS) ToHTTPFileSystem(ctx context.Context) http.FileSystem { 225 return httpRootFileSystem{rfs: &RootFS{ 226 config: rfs.config, 227 log: rfs.log, 228 }} 229 }