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