github.com/slspeek/camlistore_namedsearch@v0.0.0-20140519202248-ed6f70f7721a/pkg/fs/roots.go (about) 1 // +build linux darwin 2 3 /* 4 Copyright 2012 Google Inc. 5 6 Licensed under the Apache License, Version 2.0 (the "License"); 7 you may not use this file except in compliance with the License. 8 You may obtain a copy of the License at 9 10 http://www.apache.org/licenses/LICENSE-2.0 11 12 Unless required by applicable law or agreed to in writing, software 13 distributed under the License is distributed on an "AS IS" BASIS, 14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 See the License for the specific language governing permissions and 16 limitations under the License. 17 */ 18 19 package fs 20 21 import ( 22 "log" 23 "os" 24 "strings" 25 "sync" 26 "time" 27 28 "camlistore.org/pkg/blob" 29 "camlistore.org/pkg/schema" 30 "camlistore.org/pkg/search" 31 "camlistore.org/pkg/syncutil" 32 "camlistore.org/third_party/bazil.org/fuse" 33 "camlistore.org/third_party/bazil.org/fuse/fs" 34 ) 35 36 const refreshTime = 1 * time.Minute 37 38 type rootsDir struct { 39 noXattr 40 fs *CamliFileSystem 41 at time.Time 42 43 mu sync.Mutex // guards following 44 lastQuery time.Time 45 m map[string]blob.Ref // ent name => permanode 46 children map[string]fs.Node // ent name => child node 47 } 48 49 func (n *rootsDir) isRO() bool { 50 return !n.at.IsZero() 51 } 52 53 func (n *rootsDir) dirMode() os.FileMode { 54 if n.isRO() { 55 return 0500 56 } 57 return 0700 58 } 59 60 func (n *rootsDir) Attr() fuse.Attr { 61 return fuse.Attr{ 62 Mode: os.ModeDir | n.dirMode(), 63 Uid: uint32(os.Getuid()), 64 Gid: uint32(os.Getgid()), 65 } 66 } 67 68 func (n *rootsDir) ReadDir(intr fs.Intr) ([]fuse.Dirent, fuse.Error) { 69 n.mu.Lock() 70 defer n.mu.Unlock() 71 if err := n.condRefresh(); err != nil { 72 return nil, fuse.EIO 73 } 74 var ents []fuse.Dirent 75 for name := range n.m { 76 ents = append(ents, fuse.Dirent{Name: name}) 77 } 78 log.Printf("rootsDir.ReadDir() -> %v", ents) 79 return ents, nil 80 } 81 82 func (n *rootsDir) Remove(req *fuse.RemoveRequest, intr fs.Intr) fuse.Error { 83 if n.isRO() { 84 return fuse.EPERM 85 } 86 n.mu.Lock() 87 defer n.mu.Unlock() 88 89 if err := n.condRefresh(); err != nil { 90 return err 91 } 92 br := n.m[req.Name] 93 if !br.Valid() { 94 return fuse.ENOENT 95 } 96 97 claim := schema.NewDelAttributeClaim(br, "camliRoot", "") 98 _, err := n.fs.client.UploadAndSignBlob(claim) 99 if err != nil { 100 log.Println("rootsDir.Remove:", err) 101 return fuse.EIO 102 } 103 104 delete(n.m, req.Name) 105 delete(n.children, req.Name) 106 107 return nil 108 } 109 110 func (n *rootsDir) Rename(req *fuse.RenameRequest, newDir fs.Node, intr fs.Intr) fuse.Error { 111 log.Printf("rootsDir.Rename %q -> %q", req.OldName, req.NewName) 112 if n.isRO() { 113 return fuse.EPERM 114 } 115 116 n.mu.Lock() 117 target, exists := n.m[req.OldName] 118 _, collision := n.m[req.NewName] 119 n.mu.Unlock() 120 if !exists { 121 log.Printf("*rootsDir.Rename src name %q isn't known", req.OldName) 122 return fuse.ENOENT 123 } 124 if collision { 125 log.Printf("*rootsDir.Rename dest %q already exists", req.NewName) 126 return fuse.EIO 127 } 128 129 // Don't allow renames if the root contains content. Rename 130 // is mostly implemented to make GUIs that create directories 131 // before asking for the directory name. 132 res, err := n.fs.client.Describe(&search.DescribeRequest{BlobRef: target}) 133 if err != nil { 134 log.Println("rootsDir.Rename:", err) 135 return fuse.EIO 136 } 137 db := res.Meta[target.String()] 138 if db == nil { 139 log.Printf("Failed to pull meta for target: %v", target) 140 return fuse.EIO 141 } 142 143 for k := range db.Permanode.Attr { 144 const p = "camliPath:" 145 if strings.HasPrefix(k, p) { 146 log.Printf("Found file in %q: %q, disallowing rename", req.OldName, k[len(p):]) 147 return fuse.EIO 148 } 149 } 150 151 claim := schema.NewSetAttributeClaim(target, "camliRoot", req.NewName) 152 _, err = n.fs.client.UploadAndSignBlob(claim) 153 if err != nil { 154 log.Printf("Upload rename link error: %v", err) 155 return fuse.EIO 156 } 157 158 // Comment transplanted from mutDir.Rename 159 // TODO(bradfitz): this locking would be racy, if the kernel 160 // doesn't do it properly. (It should) Let's just trust the 161 // kernel for now. Later we can verify and remove this 162 // comment. 163 n.mu.Lock() 164 if n.m[req.OldName] != target { 165 panic("Race.") 166 } 167 delete(n.m, req.OldName) 168 delete(n.children, req.OldName) 169 delete(n.children, req.NewName) 170 n.m[req.NewName] = target 171 n.mu.Unlock() 172 173 return nil 174 } 175 176 func (n *rootsDir) Lookup(name string, intr fs.Intr) (fs.Node, fuse.Error) { 177 log.Printf("fs.roots: Lookup(%q)", name) 178 n.mu.Lock() 179 defer n.mu.Unlock() 180 if err := n.condRefresh(); err != nil { 181 return nil, err 182 } 183 br := n.m[name] 184 if !br.Valid() { 185 return nil, fuse.ENOENT 186 } 187 188 nod, ok := n.children[name] 189 if ok { 190 return nod, nil 191 } 192 193 if n.isRO() { 194 nod = newRODir(n.fs, br, name, n.at) 195 } else { 196 nod = &mutDir{ 197 fs: n.fs, 198 permanode: br, 199 name: name, 200 xattrs: map[string][]byte{}, 201 } 202 } 203 n.children[name] = nod 204 205 return nod, nil 206 } 207 208 // requires n.mu is held 209 func (n *rootsDir) condRefresh() fuse.Error { 210 if n.lastQuery.After(time.Now().Add(-refreshTime)) { 211 return nil 212 } 213 log.Printf("fs.roots: querying") 214 215 var rootRes, impRes *search.WithAttrResponse 216 var grp syncutil.Group 217 grp.Go(func() (err error) { 218 rootRes, err = n.fs.client.GetPermanodesWithAttr(&search.WithAttrRequest{N: 100, Attr: "camliRoot"}) 219 return 220 }) 221 grp.Go(func() (err error) { 222 impRes, err = n.fs.client.GetPermanodesWithAttr(&search.WithAttrRequest{N: 100, Attr: "camliImportRoot"}) 223 return 224 }) 225 if err := grp.Err(); err != nil { 226 log.Printf("fs.recent: GetRecentPermanodes error in ReadDir: %v", err) 227 return fuse.EIO 228 } 229 230 n.m = make(map[string]blob.Ref) 231 if n.children == nil { 232 n.children = make(map[string]fs.Node) 233 } 234 235 dr := &search.DescribeRequest{ 236 Depth: 1, 237 } 238 for _, wi := range rootRes.WithAttr { 239 dr.BlobRefs = append(dr.BlobRefs, wi.Permanode) 240 } 241 for _, wi := range impRes.WithAttr { 242 dr.BlobRefs = append(dr.BlobRefs, wi.Permanode) 243 } 244 if len(dr.BlobRefs) == 0 { 245 return nil 246 } 247 248 dres, err := n.fs.client.Describe(dr) 249 if err != nil { 250 log.Printf("Describe failure: %v", err) 251 return fuse.EIO 252 } 253 254 // Roots 255 currentRoots := map[string]bool{} 256 for _, wi := range rootRes.WithAttr { 257 pn := wi.Permanode 258 db := dres.Meta[pn.String()] 259 if db != nil && db.Permanode != nil { 260 name := db.Permanode.Attr.Get("camliRoot") 261 if name != "" { 262 currentRoots[name] = true 263 n.m[name] = pn 264 } 265 } 266 } 267 268 // Remove any children objects we have mapped that are no 269 // longer relevant. 270 for name := range n.children { 271 if !currentRoots[name] { 272 delete(n.children, name) 273 } 274 } 275 276 // Importers (mapped as roots for now) 277 for _, wi := range impRes.WithAttr { 278 pn := wi.Permanode 279 db := dres.Meta[pn.String()] 280 if db != nil && db.Permanode != nil { 281 name := db.Permanode.Attr.Get("camliImportRoot") 282 if name != "" { 283 name = strings.Replace(name, ":", "-", -1) 284 name = strings.Replace(name, "/", "-", -1) 285 n.m["importer-"+name] = pn 286 } 287 } 288 } 289 290 n.lastQuery = time.Now() 291 return nil 292 } 293 294 func (n *rootsDir) Mkdir(req *fuse.MkdirRequest, intr fs.Intr) (fs.Node, fuse.Error) { 295 if n.isRO() { 296 return nil, fuse.EPERM 297 } 298 299 name := req.Name 300 301 // Create a Permanode for the root. 302 pr, err := n.fs.client.UploadNewPermanode() 303 if err != nil { 304 log.Printf("rootsDir.Create(%q): %v", name, err) 305 return nil, fuse.EIO 306 } 307 308 // Add a camliRoot attribute to the root permanode. 309 claim := schema.NewSetAttributeClaim(pr.BlobRef, "camliRoot", name) 310 _, err = n.fs.client.UploadAndSignBlob(claim) 311 if err != nil { 312 log.Printf("rootsDir.Create(%q): %v", name, err) 313 return nil, fuse.EIO 314 } 315 316 nod := &mutDir{ 317 fs: n.fs, 318 permanode: pr.BlobRef, 319 name: name, 320 xattrs: map[string][]byte{}, 321 } 322 n.mu.Lock() 323 n.m[name] = pr.BlobRef 324 n.mu.Unlock() 325 326 return nod, nil 327 }