github.com/hanwen/go-fuse@v1.0.0/unionfs/autounion.go (about) 1 // Copyright 2016 the Go-FUSE Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package unionfs 6 7 import ( 8 "fmt" 9 "io/ioutil" 10 "log" 11 "os" 12 "path/filepath" 13 "strings" 14 "sync" 15 "syscall" 16 "time" 17 18 "github.com/hanwen/go-fuse/fuse" 19 "github.com/hanwen/go-fuse/fuse/nodefs" 20 "github.com/hanwen/go-fuse/fuse/pathfs" 21 ) 22 23 type knownFs struct { 24 unionFS pathfs.FileSystem 25 nodeFS *pathfs.PathNodeFs 26 } 27 28 // Creates unions for all files under a given directory, 29 // walking the tree and looking for directories D which have a 30 // D/READONLY symlink. 31 // 32 // A union for A/B/C will placed under directory A-B-C. 33 type autoUnionFs struct { 34 pathfs.FileSystem 35 debug bool 36 37 lock sync.RWMutex 38 zombies map[string]bool 39 knownFileSystems map[string]knownFs 40 nameRootMap map[string]string 41 root string 42 43 nodeFs *pathfs.PathNodeFs 44 options *AutoUnionFsOptions 45 } 46 47 type AutoUnionFsOptions struct { 48 UnionFsOptions 49 50 nodefs.Options 51 pathfs.PathNodeFsOptions 52 53 // If set, run updateKnownFses() after mounting. 54 UpdateOnMount bool 55 56 // If set hides the _READONLY file. 57 HideReadonly bool 58 59 // Expose this version in /status/gounionfs_version 60 Version string 61 } 62 63 const ( 64 _READONLY = "READONLY" 65 _STATUS = "status" 66 _CONFIG = "config" 67 _DEBUG = "debug" 68 _ROOT = "root" 69 _VERSION = "gounionfs_version" 70 _SCAN_CONFIG = ".scan_config" 71 ) 72 73 func NewAutoUnionFs(directory string, options AutoUnionFsOptions) pathfs.FileSystem { 74 if options.HideReadonly { 75 options.HiddenFiles = append(options.HiddenFiles, _READONLY) 76 } 77 a := &autoUnionFs{ 78 knownFileSystems: make(map[string]knownFs), 79 nameRootMap: make(map[string]string), 80 zombies: make(map[string]bool), 81 options: &options, 82 FileSystem: pathfs.NewDefaultFileSystem(), 83 } 84 85 directory, err := filepath.Abs(directory) 86 if err != nil { 87 panic("filepath.Abs returned err") 88 } 89 a.root = directory 90 return a 91 } 92 93 func (fs *autoUnionFs) String() string { 94 return fmt.Sprintf("autoUnionFs(%s)", fs.root) 95 } 96 97 func (fs *autoUnionFs) OnMount(nodeFs *pathfs.PathNodeFs) { 98 fs.nodeFs = nodeFs 99 if fs.options.UpdateOnMount { 100 time.AfterFunc(100*time.Millisecond, func() { fs.updateKnownFses() }) 101 } 102 } 103 104 func (fs *autoUnionFs) addAutomaticFs(roots []string) { 105 relative := strings.TrimLeft(strings.Replace(roots[0], fs.root, "", -1), "/") 106 name := strings.Replace(relative, "/", "-", -1) 107 108 if fs.getUnionFs(name) == nil { 109 fs.addFs(name, roots) 110 } 111 } 112 113 func (fs *autoUnionFs) createFs(name string, roots []string) fuse.Status { 114 fs.lock.Lock() 115 defer fs.lock.Unlock() 116 117 if fs.zombies[name] { 118 log.Printf("filesystem named %q is being removed", name) 119 return fuse.EBUSY 120 } 121 122 for workspace, root := range fs.nameRootMap { 123 if root == roots[0] && workspace != name { 124 log.Printf("Already have a union FS for directory %s in workspace %s", 125 roots[0], workspace) 126 return fuse.EBUSY 127 } 128 } 129 130 known := fs.knownFileSystems[name] 131 if known.unionFS != nil { 132 log.Println("Already have a workspace:", name) 133 return fuse.EBUSY 134 } 135 136 ufs, err := NewUnionFsFromRoots(roots, &fs.options.UnionFsOptions, true) 137 if err != nil { 138 log.Println("Could not create UnionFs:", err) 139 return fuse.EPERM 140 } 141 142 log.Printf("Adding workspace %v for roots %v", name, ufs.String()) 143 nfs := pathfs.NewPathNodeFs(ufs, &fs.options.PathNodeFsOptions) 144 code := fs.nodeFs.Mount(name, nfs.Root(), &fs.options.Options) 145 if code.Ok() { 146 fs.knownFileSystems[name] = knownFs{ 147 ufs, 148 nfs, 149 } 150 fs.nameRootMap[name] = roots[0] 151 } 152 return code 153 } 154 155 func (fs *autoUnionFs) rmFs(name string) (code fuse.Status) { 156 fs.lock.Lock() 157 defer fs.lock.Unlock() 158 159 if fs.zombies[name] { 160 return fuse.ENOENT 161 } 162 163 known := fs.knownFileSystems[name] 164 if known.unionFS == nil { 165 return fuse.ENOENT 166 } 167 168 root := fs.nameRootMap[name] 169 delete(fs.knownFileSystems, name) 170 delete(fs.nameRootMap, name) 171 fs.zombies[name] = true 172 fs.lock.Unlock() 173 code = fs.nodeFs.Unmount(name) 174 175 fs.lock.Lock() 176 delete(fs.zombies, name) 177 if !code.Ok() { 178 // Reinstate. 179 log.Printf("Unmount failed for %s. Code %v", name, code) 180 fs.knownFileSystems[name] = known 181 fs.nameRootMap[name] = root 182 } 183 return code 184 } 185 186 func (fs *autoUnionFs) addFs(name string, roots []string) (code fuse.Status) { 187 if name == _CONFIG || name == _STATUS || name == _SCAN_CONFIG { 188 return fuse.EINVAL 189 } 190 return fs.createFs(name, roots) 191 } 192 193 func (fs *autoUnionFs) getRoots(path string) []string { 194 ro := filepath.Join(path, _READONLY) 195 fi, err := os.Lstat(ro) 196 fiDir, errDir := os.Stat(ro) 197 if err != nil || errDir != nil { 198 return nil 199 } 200 201 if fi.Mode()&os.ModeSymlink != 0 && fiDir.IsDir() { 202 // TODO - should recurse and chain all READONLYs 203 // together. 204 return []string{path, ro} 205 } 206 return nil 207 } 208 209 func (fs *autoUnionFs) visit(path string, fi os.FileInfo, err error) error { 210 if fi != nil && fi.IsDir() { 211 roots := fs.getRoots(path) 212 if roots != nil { 213 fs.addAutomaticFs(roots) 214 } 215 } 216 return nil 217 } 218 219 func (fs *autoUnionFs) updateKnownFses() { 220 // We unroll the first level of entries in the root manually in order 221 // to allow symbolic links on that level. 222 directoryEntries, err := ioutil.ReadDir(fs.root) 223 if err == nil { 224 for _, dir := range directoryEntries { 225 if dir.IsDir() || dir.Mode()&os.ModeSymlink != 0 { 226 path := filepath.Join(fs.root, dir.Name()) 227 dir, _ = os.Stat(path) 228 fs.visit(path, dir, nil) 229 filepath.Walk(path, 230 func(path string, fi os.FileInfo, err error) error { 231 return fs.visit(path, fi, err) 232 }) 233 } 234 } 235 } 236 } 237 238 func (fs *autoUnionFs) Readlink(path string, context *fuse.Context) (out string, code fuse.Status) { 239 comps := strings.Split(path, string(filepath.Separator)) 240 if comps[0] == _STATUS && comps[1] == _ROOT { 241 return fs.root, fuse.OK 242 } 243 244 if comps[0] != _CONFIG { 245 return "", fuse.ENOENT 246 } 247 248 name := comps[1] 249 250 fs.lock.RLock() 251 defer fs.lock.RUnlock() 252 253 root, ok := fs.nameRootMap[name] 254 if ok { 255 return root, fuse.OK 256 } 257 return "", fuse.ENOENT 258 } 259 260 func (fs *autoUnionFs) getUnionFs(name string) pathfs.FileSystem { 261 fs.lock.RLock() 262 defer fs.lock.RUnlock() 263 return fs.knownFileSystems[name].unionFS 264 } 265 266 func (fs *autoUnionFs) Symlink(pointedTo string, linkName string, context *fuse.Context) (code fuse.Status) { 267 comps := strings.Split(linkName, "/") 268 if len(comps) != 2 { 269 return fuse.EPERM 270 } 271 272 if comps[0] == _CONFIG { 273 roots := fs.getRoots(pointedTo) 274 if roots == nil { 275 return fuse.Status(syscall.ENOTDIR) 276 } 277 278 name := comps[1] 279 return fs.addFs(name, roots) 280 } 281 return fuse.EPERM 282 } 283 284 func (fs *autoUnionFs) Unlink(path string, context *fuse.Context) (code fuse.Status) { 285 comps := strings.Split(path, "/") 286 if len(comps) != 2 { 287 return fuse.EPERM 288 } 289 290 if comps[0] == _CONFIG && comps[1] != _SCAN_CONFIG { 291 code = fs.rmFs(comps[1]) 292 } else { 293 code = fuse.ENOENT 294 } 295 return code 296 } 297 298 // Must define this, because ENOSYS will suspend all GetXAttr calls. 299 func (fs *autoUnionFs) GetXAttr(name string, attr string, context *fuse.Context) ([]byte, fuse.Status) { 300 return nil, fuse.ENOATTR 301 } 302 303 func (fs *autoUnionFs) GetAttr(path string, context *fuse.Context) (*fuse.Attr, fuse.Status) { 304 a := &fuse.Attr{ 305 Owner: *fuse.CurrentOwner(), 306 } 307 if path == "" || path == _CONFIG || path == _STATUS { 308 a.Mode = fuse.S_IFDIR | 0755 309 return a, fuse.OK 310 } 311 312 if path == filepath.Join(_STATUS, _VERSION) { 313 a.Mode = fuse.S_IFREG | 0644 314 a.Size = uint64(len(fs.options.Version)) 315 return a, fuse.OK 316 } 317 318 if path == filepath.Join(_STATUS, _DEBUG) { 319 a.Mode = fuse.S_IFREG | 0644 320 a.Size = uint64(len(fs.DebugData())) 321 return a, fuse.OK 322 } 323 324 if path == filepath.Join(_STATUS, _ROOT) { 325 a.Mode = syscall.S_IFLNK | 0644 326 return a, fuse.OK 327 } 328 329 if path == filepath.Join(_CONFIG, _SCAN_CONFIG) { 330 a.Mode = fuse.S_IFREG | 0644 331 return a, fuse.OK 332 } 333 comps := strings.Split(path, string(filepath.Separator)) 334 335 if len(comps) > 1 && comps[0] == _CONFIG { 336 fs := fs.getUnionFs(comps[1]) 337 338 if fs == nil { 339 return nil, fuse.ENOENT 340 } 341 342 a.Mode = syscall.S_IFLNK | 0644 343 return a, fuse.OK 344 } 345 346 return nil, fuse.ENOENT 347 } 348 349 func (fs *autoUnionFs) StatusDir() (stream []fuse.DirEntry, status fuse.Status) { 350 stream = make([]fuse.DirEntry, 0, 10) 351 stream = []fuse.DirEntry{ 352 {Name: _VERSION, Mode: fuse.S_IFREG | 0644}, 353 {Name: _DEBUG, Mode: fuse.S_IFREG | 0644}, 354 {Name: _ROOT, Mode: syscall.S_IFLNK | 0644}, 355 } 356 return stream, fuse.OK 357 } 358 359 func (fs *autoUnionFs) DebugData() string { 360 conn := fs.nodeFs.Connector() 361 if conn.Server() == nil { 362 return "autoUnionFs.mountState not set" 363 } 364 setting := conn.Server().KernelSettings() 365 msg := fmt.Sprintf( 366 "Version: %v\n"+ 367 "Bufferpool: %v\n"+ 368 "Kernel: %v\n", 369 fs.options.Version, 370 conn.Server().DebugData(), 371 &setting) 372 373 if conn != nil { 374 msg += fmt.Sprintf("Live inodes: %d\n", conn.InodeHandleCount()) 375 } 376 377 return msg 378 } 379 380 func (fs *autoUnionFs) Open(path string, flags uint32, context *fuse.Context) (nodefs.File, fuse.Status) { 381 if path == filepath.Join(_STATUS, _DEBUG) { 382 if flags&fuse.O_ANYWRITE != 0 { 383 return nil, fuse.EPERM 384 } 385 386 return nodefs.NewDataFile([]byte(fs.DebugData())), fuse.OK 387 } 388 if path == filepath.Join(_STATUS, _VERSION) { 389 if flags&fuse.O_ANYWRITE != 0 { 390 return nil, fuse.EPERM 391 } 392 return nodefs.NewDataFile([]byte(fs.options.Version)), fuse.OK 393 } 394 if path == filepath.Join(_CONFIG, _SCAN_CONFIG) { 395 if flags&fuse.O_ANYWRITE != 0 { 396 fs.updateKnownFses() 397 } 398 return nodefs.NewDevNullFile(), fuse.OK 399 } 400 return nil, fuse.ENOENT 401 } 402 403 func (fs *autoUnionFs) Truncate(name string, offset uint64, context *fuse.Context) (code fuse.Status) { 404 if name != filepath.Join(_CONFIG, _SCAN_CONFIG) { 405 log.Println("Huh? Truncating unsupported write file", name) 406 return fuse.EPERM 407 } 408 return fuse.OK 409 } 410 411 func (fs *autoUnionFs) OpenDir(name string, context *fuse.Context) (stream []fuse.DirEntry, status fuse.Status) { 412 switch name { 413 case _STATUS: 414 return fs.StatusDir() 415 case _CONFIG: 416 case "/": 417 name = "" 418 case "": 419 default: 420 log.Printf("Argh! Don't know how to list dir %v", name) 421 return nil, fuse.ENOSYS 422 } 423 424 fs.lock.RLock() 425 defer fs.lock.RUnlock() 426 427 stream = make([]fuse.DirEntry, 0, len(fs.knownFileSystems)+5) 428 if name == _CONFIG { 429 for k := range fs.knownFileSystems { 430 stream = append(stream, fuse.DirEntry{ 431 Name: k, 432 Mode: syscall.S_IFLNK | 0644, 433 }) 434 } 435 } 436 437 if name == "" { 438 stream = append(stream, fuse.DirEntry{ 439 Name: _CONFIG, 440 Mode: uint32(fuse.S_IFDIR | 0755), 441 }, 442 fuse.DirEntry{ 443 Name: _STATUS, 444 Mode: uint32(fuse.S_IFDIR | 0755), 445 }) 446 } 447 return stream, status 448 } 449 450 func (fs *autoUnionFs) StatFs(name string) *fuse.StatfsOut { 451 return &fuse.StatfsOut{} 452 }