fs.go (6292B)
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 14 // Package hugofs provides the file systems used by Hugo. 15 package hugofs 16 17 import ( 18 "fmt" 19 "os" 20 "strings" 21 22 "github.com/bep/overlayfs" 23 "github.com/gohugoio/hugo/common/paths" 24 "github.com/gohugoio/hugo/config" 25 "github.com/spf13/afero" 26 ) 27 28 // Os points to the (real) Os filesystem. 29 var Os = &afero.OsFs{} 30 31 // Fs holds the core filesystems used by Hugo. 32 type Fs struct { 33 // Source is Hugo's source file system. 34 // Note that this will always be a "plain" Afero filesystem: 35 // * afero.OsFs when running in production 36 // * afero.MemMapFs for many of the tests. 37 Source afero.Fs 38 39 // PublishDir is where Hugo publishes its rendered content. 40 // It's mounted inside publishDir (default /public). 41 PublishDir afero.Fs 42 43 // PublishDirStatic is the file system used for static files when --renderStaticToDisk is set. 44 // When this is set, PublishDir is set to write to memory. 45 PublishDirStatic afero.Fs 46 47 // PublishDirServer is the file system used for serving the public directory with Hugo's development server. 48 // This will typically be the same as PublishDir, but not if --renderStaticToDisk is set. 49 PublishDirServer afero.Fs 50 51 // Os is an OS file system. 52 // NOTE: Field is currently unused. 53 Os afero.Fs 54 55 // WorkingDirReadOnly is a read-only file system 56 // restricted to the project working dir. 57 WorkingDirReadOnly afero.Fs 58 59 // WorkingDirWritable is a writable file system 60 // restricted to the project working dir. 61 WorkingDirWritable afero.Fs 62 } 63 64 // NewDefault creates a new Fs with the OS file system 65 // as source and destination file systems. 66 func NewDefault(cfg config.Provider) *Fs { 67 fs := Os 68 return newFs(fs, fs, cfg) 69 } 70 71 // NewMem creates a new Fs with the MemMapFs 72 // as source and destination file systems. 73 // Useful for testing. 74 func NewMem(cfg config.Provider) *Fs { 75 fs := &afero.MemMapFs{} 76 return newFs(fs, fs, cfg) 77 } 78 79 // NewFrom creates a new Fs based on the provided Afero Fs 80 // as source and destination file systems. 81 // Useful for testing. 82 func NewFrom(fs afero.Fs, cfg config.Provider) *Fs { 83 return newFs(fs, fs, cfg) 84 } 85 86 // NewFrom creates a new Fs based on the provided Afero Fss 87 // as the source and destination file systems. 88 func NewFromSourceAndDestination(source, destination afero.Fs, cfg config.Provider) *Fs { 89 return newFs(source, destination, cfg) 90 } 91 92 func newFs(source, destination afero.Fs, cfg config.Provider) *Fs { 93 workingDir := cfg.GetString("workingDir") 94 publishDir := cfg.GetString("publishDir") 95 if publishDir == "" { 96 panic("publishDir is empty") 97 } 98 99 // Sanity check 100 if IsOsFs(source) && len(workingDir) < 2 { 101 panic("workingDir is too short") 102 } 103 104 absPublishDir := paths.AbsPathify(workingDir, publishDir) 105 106 // Make sure we always have the /public folder ready to use. 107 if err := source.MkdirAll(absPublishDir, 0777); err != nil && !os.IsExist(err) { 108 panic(err) 109 } 110 111 pubFs := afero.NewBasePathFs(destination, absPublishDir) 112 113 return &Fs{ 114 Source: source, 115 PublishDir: pubFs, 116 PublishDirServer: pubFs, 117 PublishDirStatic: pubFs, 118 Os: &afero.OsFs{}, 119 WorkingDirReadOnly: getWorkingDirFsReadOnly(source, workingDir), 120 WorkingDirWritable: getWorkingDirFsWritable(source, workingDir), 121 } 122 } 123 124 func getWorkingDirFsReadOnly(base afero.Fs, workingDir string) afero.Fs { 125 if workingDir == "" { 126 return afero.NewReadOnlyFs(base) 127 } 128 return afero.NewBasePathFs(afero.NewReadOnlyFs(base), workingDir) 129 } 130 131 func getWorkingDirFsWritable(base afero.Fs, workingDir string) afero.Fs { 132 if workingDir == "" { 133 return base 134 } 135 return afero.NewBasePathFs(base, workingDir) 136 } 137 138 func isWrite(flag int) bool { 139 return flag&os.O_RDWR != 0 || flag&os.O_WRONLY != 0 140 } 141 142 // MakeReadableAndRemoveAllModulePkgDir makes any subdir in dir readable and then 143 // removes the root. 144 // TODO(bep) move this to a more suitable place. 145 // 146 func MakeReadableAndRemoveAllModulePkgDir(fs afero.Fs, dir string) (int, error) { 147 // Safe guard 148 if !strings.Contains(dir, "pkg") { 149 panic(fmt.Sprint("invalid dir:", dir)) 150 } 151 152 counter := 0 153 afero.Walk(fs, dir, func(path string, info os.FileInfo, err error) error { 154 if err != nil { 155 return nil 156 } 157 if info.IsDir() { 158 counter++ 159 fs.Chmod(path, 0777) 160 } 161 return nil 162 }) 163 return counter, fs.RemoveAll(dir) 164 } 165 166 // HasOsFs returns whether fs is an OsFs or if it fs wraps an OsFs. 167 // TODO(bep) make this nore robust. 168 func IsOsFs(fs afero.Fs) bool { 169 var isOsFs bool 170 WalkFilesystems(fs, func(fs afero.Fs) bool { 171 switch base := fs.(type) { 172 case *afero.MemMapFs: 173 isOsFs = false 174 case *afero.OsFs: 175 isOsFs = true 176 case *afero.BasePathFs: 177 _, supportsLstat, _ := base.LstatIfPossible("asdfasdfasdf") 178 isOsFs = supportsLstat 179 } 180 return isOsFs 181 }) 182 return isOsFs 183 } 184 185 // FilesystemsUnwrapper returns the underlying filesystems. 186 type FilesystemsUnwrapper interface { 187 UnwrapFilesystems() []afero.Fs 188 } 189 190 // FilesystemsProvider returns the underlying filesystem. 191 type FilesystemUnwrapper interface { 192 UnwrapFilesystem() afero.Fs 193 } 194 195 // WalkFn is the walk func for WalkFilesystems. 196 type WalkFn func(fs afero.Fs) bool 197 198 // WalkFilesystems walks fs recursively and calls fn. 199 // If fn returns true, walking is stopped. 200 func WalkFilesystems(fs afero.Fs, fn WalkFn) bool { 201 if fn(fs) { 202 return true 203 } 204 205 if afs, ok := fs.(FilesystemUnwrapper); ok { 206 if WalkFilesystems(afs.UnwrapFilesystem(), fn) { 207 return true 208 } 209 210 } else if bfs, ok := fs.(FilesystemsUnwrapper); ok { 211 for _, sf := range bfs.UnwrapFilesystems() { 212 if WalkFilesystems(sf, fn) { 213 return true 214 } 215 } 216 } else if cfs, ok := fs.(overlayfs.FilesystemIterator); ok { 217 for i := 0; i < cfs.NumFilesystems(); i++ { 218 if WalkFilesystems(cfs.Filesystem(i), fn) { 219 return true 220 } 221 } 222 } 223 224 return false 225 }