github.com/cs3org/reva/v2@v2.27.7/pkg/storage/fs/posix/lookup/lookup.go (about) 1 // Copyright 2018-2021 CERN 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 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 // In applying this license, CERN does not waive the privileges and immunities 16 // granted to it by virtue of its status as an Intergovernmental Organization 17 // or submit itself to any jurisdiction. 18 19 package lookup 20 21 import ( 22 "context" 23 "fmt" 24 "os" 25 "path/filepath" 26 "strings" 27 28 user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" 29 provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" 30 "github.com/cs3org/reva/v2/pkg/appctx" 31 "github.com/cs3org/reva/v2/pkg/errtypes" 32 "github.com/cs3org/reva/v2/pkg/storage/fs/posix/options" 33 "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata" 34 "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata/prefixes" 35 "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node" 36 "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/usermapper" 37 "github.com/cs3org/reva/v2/pkg/storage/utils/templates" 38 "github.com/cs3org/reva/v2/pkg/storagespace" 39 "github.com/google/uuid" 40 "github.com/pkg/errors" 41 "github.com/rogpeppe/go-internal/lockedfile" 42 "go.opentelemetry.io/otel" 43 "go.opentelemetry.io/otel/trace" 44 ) 45 46 var tracer trace.Tracer 47 48 var _spaceTypePersonal = "personal" 49 var _spaceTypeProject = "project" 50 51 func init() { 52 tracer = otel.Tracer("github.com/cs3org/reva/pkg/storage/utils/decomposedfs/lookup") 53 } 54 55 // IDCache is a cache for node ids 56 type IDCache interface { 57 Get(ctx context.Context, spaceID, nodeID string) (string, bool) 58 GetByPath(ctx context.Context, path string) (string, string, bool) 59 60 Set(ctx context.Context, spaceID, nodeID, val string) error 61 62 Delete(ctx context.Context, spaceID, nodeID string) error 63 DeleteByPath(ctx context.Context, path string) error 64 65 DeletePath(ctx context.Context, path string) error 66 } 67 68 // Lookup implements transformations from filepath to node and back 69 type Lookup struct { 70 Options *options.Options 71 72 IDCache IDCache 73 metadataBackend metadata.Backend 74 userMapper usermapper.Mapper 75 tm node.TimeManager 76 } 77 78 // New returns a new Lookup instance 79 func New(b metadata.Backend, um usermapper.Mapper, o *options.Options, tm node.TimeManager) *Lookup { 80 lu := &Lookup{ 81 Options: o, 82 metadataBackend: b, 83 IDCache: NewStoreIDCache(&o.Options), 84 userMapper: um, 85 tm: tm, 86 } 87 88 return lu 89 } 90 91 // CacheID caches the path for the given space and node id 92 func (lu *Lookup) CacheID(ctx context.Context, spaceID, nodeID, val string) error { 93 return lu.IDCache.Set(ctx, spaceID, nodeID, val) 94 } 95 96 // GetCachedID returns the cached path for the given space and node id 97 func (lu *Lookup) GetCachedID(ctx context.Context, spaceID, nodeID string) (string, bool) { 98 return lu.IDCache.Get(ctx, spaceID, nodeID) 99 } 100 101 // IDsForPath returns the space and opaque id for the given path 102 func (lu *Lookup) IDsForPath(ctx context.Context, path string) (string, string, error) { 103 spaceID, nodeID, ok := lu.IDCache.GetByPath(ctx, path) 104 if !ok { 105 return "", "", fmt.Errorf("path %s not found in cache", path) 106 } 107 return spaceID, nodeID, nil 108 } 109 110 // NodeFromPath returns the node for the given path 111 func (lu *Lookup) NodeIDFromParentAndName(ctx context.Context, parent *node.Node, name string) (string, error) { 112 id, err := lu.metadataBackend.Get(ctx, filepath.Join(parent.InternalPath(), name), prefixes.IDAttr) 113 if err != nil { 114 if metadata.IsNotExist(err) { 115 return "", errtypes.NotFound(name) 116 } 117 return "", err 118 } 119 _, err = os.Stat(filepath.Join(parent.InternalPath(), name)) 120 if err != nil { 121 return "", err 122 } 123 124 return string(id), nil 125 } 126 127 // MetadataBackend returns the metadata backend 128 func (lu *Lookup) MetadataBackend() metadata.Backend { 129 return lu.metadataBackend 130 } 131 132 func (lu *Lookup) ReadBlobIDAndSizeAttr(ctx context.Context, path string, _ node.Attributes) (string, int64, error) { 133 fi, err := os.Stat(path) 134 if err != nil { 135 return "", 0, errors.Wrap(err, "error stating file") 136 } 137 return "", fi.Size(), nil 138 } 139 140 // TypeFromPath returns the type of the node at the given path 141 func (lu *Lookup) TypeFromPath(ctx context.Context, path string) provider.ResourceType { 142 // Try to read from xattrs 143 typeAttr, err := lu.metadataBackend.GetInt64(ctx, path, prefixes.TypeAttr) 144 if err == nil { 145 return provider.ResourceType(int32(typeAttr)) 146 } 147 148 t := provider.ResourceType_RESOURCE_TYPE_INVALID 149 // Fall back to checking on disk 150 fi, err := os.Lstat(path) 151 if err != nil { 152 return t 153 } 154 155 switch { 156 case fi.IsDir(): 157 if _, err = lu.metadataBackend.Get(ctx, path, prefixes.ReferenceAttr); err == nil { 158 t = provider.ResourceType_RESOURCE_TYPE_REFERENCE 159 } else { 160 t = provider.ResourceType_RESOURCE_TYPE_CONTAINER 161 } 162 case fi.Mode().IsRegular(): 163 t = provider.ResourceType_RESOURCE_TYPE_FILE 164 case fi.Mode()&os.ModeSymlink != 0: 165 t = provider.ResourceType_RESOURCE_TYPE_SYMLINK 166 // TODO reference using ext attr on a symlink 167 // nodeType = provider.ResourceType_RESOURCE_TYPE_REFERENCE 168 } 169 return t 170 } 171 172 // NodeFromResource takes in a request path or request id and converts it to a Node 173 func (lu *Lookup) NodeFromResource(ctx context.Context, ref *provider.Reference) (*node.Node, error) { 174 ctx, span := tracer.Start(ctx, "NodeFromResource") 175 defer span.End() 176 177 if ref.ResourceId != nil { 178 // check if a storage space reference is used 179 // currently, the decomposed fs uses the root node id as the space id 180 n, err := lu.NodeFromID(ctx, ref.ResourceId) 181 if err != nil { 182 return nil, err 183 } 184 // is this a relative reference? 185 if ref.Path != "" { 186 p := filepath.Clean(ref.Path) 187 if p != "." && p != "/" { 188 // walk the relative path 189 n, err = lu.WalkPath(ctx, n, p, false, func(ctx context.Context, n *node.Node) error { return nil }) 190 if err != nil { 191 return nil, err 192 } 193 n.SpaceID = ref.ResourceId.SpaceId 194 } 195 } 196 return n, nil 197 } 198 199 // reference is invalid 200 return nil, fmt.Errorf("invalid reference %+v. resource_id must be set", ref) 201 } 202 203 // NodeFromID returns the internal path for the id 204 func (lu *Lookup) NodeFromID(ctx context.Context, id *provider.ResourceId) (n *node.Node, err error) { 205 ctx, span := tracer.Start(ctx, "NodeFromID") 206 defer span.End() 207 if id == nil { 208 return nil, fmt.Errorf("invalid resource id %+v", id) 209 } 210 if id.OpaqueId == "" { 211 // The Resource references the root of a space 212 return lu.NodeFromSpaceID(ctx, id.SpaceId) 213 } 214 return node.ReadNode(ctx, lu, id.SpaceId, id.OpaqueId, false, nil, false) 215 } 216 217 // Pathify segments the beginning of a string into depth segments of width length 218 // Pathify("aabbccdd", 3, 1) will return "a/a/b/bccdd" 219 func Pathify(id string, depth, width int) string { 220 b := strings.Builder{} 221 i := 0 222 for ; i < depth; i++ { 223 if len(id) <= i*width+width { 224 break 225 } 226 b.WriteString(id[i*width : i*width+width]) 227 b.WriteRune(filepath.Separator) 228 } 229 b.WriteString(id[i*width:]) 230 return b.String() 231 } 232 233 // NodeFromSpaceID converts a resource id into a Node 234 func (lu *Lookup) NodeFromSpaceID(ctx context.Context, spaceID string) (n *node.Node, err error) { 235 node, err := node.ReadNode(ctx, lu, spaceID, spaceID, false, nil, false) 236 if err != nil { 237 return nil, err 238 } 239 240 node.SpaceRoot = node 241 return node, nil 242 } 243 244 // Path returns the path for node 245 func (lu *Lookup) Path(ctx context.Context, n *node.Node, hasPermission node.PermissionFunc) (p string, err error) { 246 root := n.SpaceRoot 247 for n.ID != root.ID { 248 p = filepath.Join(n.Name, p) 249 if n, err = n.Parent(ctx); err != nil { 250 appctx.GetLogger(ctx). 251 Error().Err(err). 252 Str("path", p). 253 Msg("Path()") 254 return 255 } 256 257 if !hasPermission(n) { 258 break 259 } 260 } 261 p = filepath.Join("/", p) 262 return 263 } 264 265 // WalkPath calls n.Child(segment) on every path segment in p starting at the node r. 266 // If a function f is given it will be executed for every segment node, but not the root node r. 267 // If followReferences is given the current visited reference node is replaced by the referenced node. 268 func (lu *Lookup) WalkPath(ctx context.Context, r *node.Node, p string, followReferences bool, f func(ctx context.Context, n *node.Node) error) (*node.Node, error) { 269 segments := strings.Split(strings.Trim(p, "/"), "/") 270 var err error 271 for i := range segments { 272 if r, err = r.Child(ctx, segments[i]); err != nil { 273 return r, err 274 } 275 276 if followReferences { 277 if attrBytes, err := r.Xattr(ctx, prefixes.ReferenceAttr); err == nil { 278 realNodeID := attrBytes 279 ref, err := refFromCS3(realNodeID) 280 if err != nil { 281 return nil, err 282 } 283 284 r, err = lu.NodeFromID(ctx, ref.ResourceId) 285 if err != nil { 286 return nil, err 287 } 288 } 289 } 290 if r.IsSpaceRoot(ctx) { 291 r.SpaceRoot = r 292 } 293 294 if !r.Exists && i < len(segments)-1 { 295 return r, errtypes.NotFound(segments[i]) 296 } 297 if f != nil { 298 if err = f(ctx, r); err != nil { 299 return r, err 300 } 301 } 302 } 303 return r, nil 304 } 305 306 // InternalRoot returns the internal storage root directory 307 func (lu *Lookup) InternalRoot() string { 308 return lu.Options.Root 309 } 310 311 // InternalPath returns the internal path for a given ID 312 func (lu *Lookup) InternalPath(spaceID, nodeID string) string { 313 path, _ := lu.IDCache.Get(context.Background(), spaceID, nodeID) 314 315 return path 316 } 317 318 // // ReferenceFromAttr returns a CS3 reference from xattr of a node. 319 // // Supported formats are: "cs3:storageid/nodeid" 320 // func ReferenceFromAttr(b []byte) (*provider.Reference, error) { 321 // return refFromCS3(b) 322 // } 323 324 // refFromCS3 creates a CS3 reference from a set of bytes. This method should remain private 325 // and only be called after validation because it can potentially panic. 326 func refFromCS3(b []byte) (*provider.Reference, error) { 327 parts := string(b[4:]) 328 return &provider.Reference{ 329 ResourceId: &provider.ResourceId{ 330 StorageId: strings.Split(parts, "/")[0], 331 OpaqueId: strings.Split(parts, "/")[1], 332 }, 333 }, nil 334 } 335 336 // CopyMetadata copies all extended attributes from source to target. 337 // The optional filter function can be used to filter by attribute name, e.g. by checking a prefix 338 // For the source file, a shared lock is acquired. 339 // NOTE: target resource will be write locked! 340 func (lu *Lookup) CopyMetadata(ctx context.Context, src, target string, filter func(attributeName string, value []byte) (newValue []byte, copy bool), acquireTargetLock bool) (err error) { 341 // Acquire a read log on the source node 342 // write lock existing node before reading treesize or tree time 343 lock, err := lockedfile.OpenFile(lu.MetadataBackend().LockfilePath(src), os.O_RDONLY|os.O_CREATE, 0600) 344 if err != nil { 345 return err 346 } 347 348 if err != nil { 349 return errors.Wrap(err, "xattrs: Unable to lock source to read") 350 } 351 defer func() { 352 rerr := lock.Close() 353 354 // if err is non nil we do not overwrite that 355 if err == nil { 356 err = rerr 357 } 358 }() 359 360 return lu.CopyMetadataWithSourceLock(ctx, src, target, filter, lock, acquireTargetLock) 361 } 362 363 // CopyMetadataWithSourceLock copies all extended attributes from source to target. 364 // The optional filter function can be used to filter by attribute name, e.g. by checking a prefix 365 // For the source file, a matching lockedfile is required. 366 // NOTE: target resource will be write locked! 367 func (lu *Lookup) CopyMetadataWithSourceLock(ctx context.Context, sourcePath, targetPath string, filter func(attributeName string, value []byte) (newValue []byte, copy bool), lockedSource *lockedfile.File, acquireTargetLock bool) (err error) { 368 switch { 369 case lockedSource == nil: 370 return errors.New("no lock provided") 371 case lockedSource.File.Name() != lu.MetadataBackend().LockfilePath(sourcePath): 372 return errors.New("lockpath does not match filepath") 373 } 374 375 attrs, err := lu.metadataBackend.All(ctx, sourcePath) 376 if err != nil { 377 return err 378 } 379 380 newAttrs := make(map[string][]byte, 0) 381 for attrName, val := range attrs { 382 if filter != nil { 383 var ok bool 384 if val, ok = filter(attrName, val); !ok { 385 continue 386 } 387 } 388 newAttrs[attrName] = val 389 } 390 391 return lu.MetadataBackend().SetMultiple(ctx, targetPath, newAttrs, acquireTargetLock) 392 } 393 394 // GenerateSpaceID generates a space id for the given space type and owner 395 func (lu *Lookup) GenerateSpaceID(spaceType string, owner *user.User) (string, error) { 396 switch spaceType { 397 case _spaceTypeProject: 398 return uuid.New().String(), nil 399 case _spaceTypePersonal: 400 path := templates.WithUser(owner, lu.Options.UserLayout) 401 402 spaceID, err := lu.metadataBackend.Get(context.Background(), filepath.Join(lu.Options.Root, path), prefixes.IDAttr) 403 if err != nil { 404 if metadata.IsNotExist(err) || metadata.IsAttrUnset(err) { 405 return uuid.New().String(), nil 406 } else { 407 return "", err 408 } 409 } 410 resID, err := storagespace.ParseID(string(spaceID)) 411 if err != nil { 412 return "", err 413 } 414 return resID.SpaceId, nil 415 default: 416 return "", fmt.Errorf("unsupported space type: %s", spaceType) 417 } 418 } 419 420 // TimeManager returns the time manager 421 func (lu *Lookup) TimeManager() node.TimeManager { 422 return lu.tm 423 }