1// Copyright 2019 The Hugo Authors. 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// http://www.apache.org/licenses/LICENSE-2.0 7// 8// Unless required by applicable law or agreed to in writing, software 9// distributed under the License is distributed on an "AS IS" BASIS, 10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11// See the License for the specific language governing permissions and 12// limitations under the License. 13 14package hugofs 15 16import ( 17 "os" 18 "syscall" 19 "time" 20 21 "github.com/pkg/errors" 22 23 "github.com/spf13/afero" 24) 25 26var ( 27 _ afero.Fs = (*SliceFs)(nil) 28 _ afero.Lstater = (*SliceFs)(nil) 29 _ afero.File = (*sliceDir)(nil) 30) 31 32func NewSliceFs(dirs ...FileMetaInfo) (afero.Fs, error) { 33 if len(dirs) == 0 { 34 return NoOpFs, nil 35 } 36 37 for _, dir := range dirs { 38 if !dir.IsDir() { 39 return nil, errors.New("this fs supports directories only") 40 } 41 } 42 43 fs := &SliceFs{ 44 dirs: dirs, 45 } 46 47 return fs, nil 48} 49 50// SliceFs is an ordered composite filesystem. 51type SliceFs struct { 52 dirs []FileMetaInfo 53} 54 55func (fs *SliceFs) Chmod(n string, m os.FileMode) error { 56 return syscall.EPERM 57} 58 59func (fs *SliceFs) Chtimes(n string, a, m time.Time) error { 60 return syscall.EPERM 61} 62 63func (fs *SliceFs) Chown(n string, uid, gid int) error { 64 return syscall.EPERM 65} 66 67func (fs *SliceFs) LstatIfPossible(name string) (os.FileInfo, bool, error) { 68 fi, _, err := fs.pickFirst(name) 69 if err != nil { 70 return nil, false, err 71 } 72 73 if fi.IsDir() { 74 return decorateFileInfo(fi, fs, fs.getOpener(name), "", "", nil), false, nil 75 } 76 77 return nil, false, errors.Errorf("lstat: files not supported: %q", name) 78} 79 80func (fs *SliceFs) Mkdir(n string, p os.FileMode) error { 81 return syscall.EPERM 82} 83 84func (fs *SliceFs) MkdirAll(n string, p os.FileMode) error { 85 return syscall.EPERM 86} 87 88func (fs *SliceFs) Name() string { 89 return "SliceFs" 90} 91 92func (fs *SliceFs) Open(name string) (afero.File, error) { 93 fi, idx, err := fs.pickFirst(name) 94 if err != nil { 95 return nil, err 96 } 97 98 if !fi.IsDir() { 99 panic("currently only dirs in here") 100 } 101 102 return &sliceDir{ 103 lfs: fs, 104 idx: idx, 105 dirname: name, 106 }, nil 107} 108 109func (fs *SliceFs) OpenFile(name string, flag int, perm os.FileMode) (afero.File, error) { 110 panic("not implemented") 111} 112 113func (fs *SliceFs) ReadDir(name string) ([]os.FileInfo, error) { 114 panic("not implemented") 115} 116 117func (fs *SliceFs) Remove(n string) error { 118 return syscall.EPERM 119} 120 121func (fs *SliceFs) RemoveAll(p string) error { 122 return syscall.EPERM 123} 124 125func (fs *SliceFs) Rename(o, n string) error { 126 return syscall.EPERM 127} 128 129func (fs *SliceFs) Stat(name string) (os.FileInfo, error) { 130 fi, _, err := fs.LstatIfPossible(name) 131 return fi, err 132} 133 134func (fs *SliceFs) Create(n string) (afero.File, error) { 135 return nil, syscall.EPERM 136} 137 138func (fs *SliceFs) getOpener(name string) func() (afero.File, error) { 139 return func() (afero.File, error) { 140 return fs.Open(name) 141 } 142} 143 144func (fs *SliceFs) pickFirst(name string) (os.FileInfo, int, error) { 145 for i, mfs := range fs.dirs { 146 meta := mfs.Meta() 147 fs := meta.Fs 148 fi, _, err := lstatIfPossible(fs, name) 149 if err == nil { 150 // Gotta match! 151 return fi, i, nil 152 } 153 154 if !os.IsNotExist(err) { 155 // Real error 156 return nil, -1, err 157 } 158 } 159 160 // Not found 161 return nil, -1, os.ErrNotExist 162} 163 164func (fs *SliceFs) readDirs(name string, startIdx, count int) ([]os.FileInfo, error) { 165 collect := func(lfs *FileMeta) ([]os.FileInfo, error) { 166 d, err := lfs.Fs.Open(name) 167 if err != nil { 168 if !os.IsNotExist(err) { 169 return nil, err 170 } 171 return nil, nil 172 } else { 173 defer d.Close() 174 dirs, err := d.Readdir(-1) 175 if err != nil { 176 return nil, err 177 } 178 return dirs, nil 179 } 180 } 181 182 var dirs []os.FileInfo 183 184 for i := startIdx; i < len(fs.dirs); i++ { 185 mfs := fs.dirs[i] 186 187 fis, err := collect(mfs.Meta()) 188 if err != nil { 189 return nil, err 190 } 191 192 dirs = append(dirs, fis...) 193 194 } 195 196 seen := make(map[string]bool) 197 var duplicates []int 198 for i, fi := range dirs { 199 if !fi.IsDir() { 200 continue 201 } 202 203 if seen[fi.Name()] { 204 duplicates = append(duplicates, i) 205 } else { 206 // Make sure it's opened by this filesystem. 207 dirs[i] = decorateFileInfo(fi, fs, fs.getOpener(fi.(FileMetaInfo).Meta().Filename), "", "", nil) 208 seen[fi.Name()] = true 209 } 210 } 211 212 // Remove duplicate directories, keep first. 213 if len(duplicates) > 0 { 214 for i := len(duplicates) - 1; i >= 0; i-- { 215 idx := duplicates[i] 216 dirs = append(dirs[:idx], dirs[idx+1:]...) 217 } 218 } 219 220 if count > 0 && len(dirs) >= count { 221 return dirs[:count], nil 222 } 223 224 return dirs, nil 225} 226 227type sliceDir struct { 228 lfs *SliceFs 229 idx int 230 dirname string 231} 232 233func (f *sliceDir) Close() error { 234 return nil 235} 236 237func (f *sliceDir) Name() string { 238 return f.dirname 239} 240 241func (f *sliceDir) Read(p []byte) (n int, err error) { 242 panic("not implemented") 243} 244 245func (f *sliceDir) ReadAt(p []byte, off int64) (n int, err error) { 246 panic("not implemented") 247} 248 249func (f *sliceDir) Readdir(count int) ([]os.FileInfo, error) { 250 return f.lfs.readDirs(f.dirname, f.idx, count) 251} 252 253func (f *sliceDir) Readdirnames(count int) ([]string, error) { 254 dirsi, err := f.Readdir(count) 255 if err != nil { 256 return nil, err 257 } 258 259 dirs := make([]string, len(dirsi)) 260 for i, d := range dirsi { 261 dirs[i] = d.Name() 262 } 263 return dirs, nil 264} 265 266func (f *sliceDir) Seek(offset int64, whence int) (int64, error) { 267 panic("not implemented") 268} 269 270func (f *sliceDir) Stat() (os.FileInfo, error) { 271 panic("not implemented") 272} 273 274func (f *sliceDir) Sync() error { 275 panic("not implemented") 276} 277 278func (f *sliceDir) Truncate(size int64) error { 279 panic("not implemented") 280} 281 282func (f *sliceDir) Write(p []byte) (n int, err error) { 283 panic("not implemented") 284} 285 286func (f *sliceDir) WriteAt(p []byte, off int64) (n int, err error) { 287 panic("not implemented") 288} 289 290func (f *sliceDir) WriteString(s string) (ret int, err error) { 291 panic("not implemented") 292} 293