github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/fs/fqn.go (about)

     1  // Package fs provides mountpath and FQN abstractions and methods to resolve/map stored content
     2  /*
     3   * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved.
     4   */
     5  package fs
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"io/fs"
    11  	"path/filepath"
    12  	"strings"
    13  
    14  	"github.com/NVIDIA/aistore/api/apc"
    15  	"github.com/NVIDIA/aistore/cmn"
    16  	"github.com/NVIDIA/aistore/cmn/cos"
    17  )
    18  
    19  // for background, see: docs/on_disk_layout.md
    20  
    21  const (
    22  	prefCT       = '%'
    23  	prefProvider = '@'
    24  	prefNsUUID   = apc.NsUUIDPrefix
    25  	prefNsName   = apc.NsNamePrefix
    26  )
    27  
    28  const (
    29  	// prefixes for workfiles created by various services
    30  	WorkfileRemote       = "remote"         // getting object from neighbor target when rebalancing
    31  	WorkfileColdget      = "cold"           // object GET: coldget
    32  	WorkfilePut          = "put"            // object PUT
    33  	WorkfileCopy         = "copy"           // copy object
    34  	WorkfileAppend       = "append"         // APPEND to object (as file)
    35  	WorkfileAppendToArch = "append-to-arch" // APPEND to existing archive
    36  	WorkfileCreateArch   = "create-arch"    // CREATE multi-object archive
    37  )
    38  
    39  type ParsedFQN struct {
    40  	Mountpath   *Mountpath
    41  	ContentType string
    42  	ObjName     string
    43  	Digest      uint64
    44  	Bck         cmn.Bck
    45  }
    46  
    47  ///////////////
    48  // ParsedFQN //
    49  ///////////////
    50  
    51  func (parsed *ParsedFQN) Init(fqn string) (err error) {
    52  	var (
    53  		rel           string
    54  		itemIdx, prev int
    55  	)
    56  	parsed.Mountpath, rel, err = FQN2Mpath(fqn)
    57  	if err != nil {
    58  		return
    59  	}
    60  	for i := range len(rel) {
    61  		if rel[i] != filepath.Separator {
    62  			continue
    63  		}
    64  
    65  		item := rel[prev:i]
    66  		switch itemIdx {
    67  		case 0: // backend provider
    68  			if item[0] != prefProvider {
    69  				err = fmt.Errorf("invalid fqn %s: bad provider %q", fqn, item)
    70  				return
    71  			}
    72  			provider := item[1:]
    73  			parsed.Bck.Provider = provider
    74  			if !apc.IsProvider(provider) {
    75  				err = fmt.Errorf("invalid fqn %s: unknown provider %q", fqn, provider)
    76  				return
    77  			}
    78  		case 1: // namespace or bucket name
    79  			if item == "" {
    80  				err = fmt.Errorf("invalid fqn %s: bad bucket name (or namespace)", fqn)
    81  				return
    82  			}
    83  
    84  			switch item[0] {
    85  			case prefNsName:
    86  				parsed.Bck.Ns = cmn.Ns{
    87  					Name: item[1:],
    88  				}
    89  				itemIdx-- // we must visit this case again
    90  			case prefNsUUID:
    91  				ns := item[1:]
    92  				idx := strings.IndexRune(ns, prefNsName)
    93  				if idx == -1 {
    94  					err = fmt.Errorf("invalid fqn %s: bad namespace %q", fqn, ns)
    95  				}
    96  				parsed.Bck.Ns = cmn.Ns{
    97  					UUID: ns[:idx],
    98  					Name: ns[idx+1:],
    99  				}
   100  				itemIdx-- // we must visit this case again
   101  			default:
   102  				parsed.Bck.Name = item
   103  			}
   104  		case 2: // content type and object name
   105  			if item[0] != prefCT {
   106  				err = fmt.Errorf("invalid fqn %s: bad content type %q", fqn, item)
   107  				return
   108  			}
   109  
   110  			item = item[1:]
   111  			if _, ok := CSM.m[item]; !ok {
   112  				err = fmt.Errorf("invalid fqn %s: bad content type %q", fqn, item)
   113  				return
   114  			}
   115  			parsed.ContentType = item
   116  
   117  			// Object name
   118  			objName := rel[i+1:]
   119  			if objName == "" {
   120  				err = fmt.Errorf("invalid fqn %s: bad object name", fqn)
   121  			}
   122  			parsed.ObjName = objName
   123  			return
   124  		}
   125  
   126  		itemIdx++
   127  		prev = i + 1
   128  	}
   129  
   130  	return fmt.Errorf("fqn %s is invalid", fqn)
   131  }
   132  
   133  //
   134  // supporting helpers
   135  //
   136  
   137  // match FQN to mountpath and return the former and the relative path
   138  func FQN2Mpath(fqn string) (found *Mountpath, relativePath string, err error) {
   139  	avail := GetAvail()
   140  	if len(avail) == 0 {
   141  		err = cmn.ErrNoMountpaths
   142  		return
   143  	}
   144  	for mpath, mi := range avail {
   145  		l := len(mpath)
   146  		if len(fqn) > l && fqn[0:l] == mpath && fqn[l] == filepath.Separator {
   147  			found = mi
   148  			relativePath = fqn[l+1:]
   149  			return
   150  		}
   151  	}
   152  
   153  	// make an extra effort to lookup in disabled
   154  	_, disabled := Get()
   155  	for mpath := range disabled {
   156  		l := len(mpath)
   157  		if len(fqn) > l && fqn[0:l] == mpath && fqn[l] == filepath.Separator {
   158  			err = cmn.NewErrMountpathNotFound("" /*mpath*/, fqn, true /*disabled*/)
   159  			return
   160  		}
   161  	}
   162  	err = cmn.NewErrMountpathNotFound("" /*mpath*/, fqn, false /*disabled*/)
   163  	return
   164  }
   165  
   166  // Path2Mpath takes in any file path (e.g., ../../a/b/c) and returns the matching `mi`,
   167  // if exists
   168  func Path2Mpath(path string) (found *Mountpath, err error) {
   169  	found, _, err = FQN2Mpath(filepath.Clean(path))
   170  	return
   171  }
   172  
   173  func CleanPathErr(err error) {
   174  	var (
   175  		pathErr *fs.PathError
   176  		what    string
   177  		parsed  ParsedFQN
   178  	)
   179  	if !errors.As(err, &pathErr) {
   180  		return
   181  	}
   182  	if errV := parsed.Init(pathErr.Path); errV != nil {
   183  		return
   184  	}
   185  	pathErr.Path = parsed.Bck.Cname(parsed.ObjName)
   186  	pathErr.Op = "[fs-path]"
   187  	if strings.Contains(pathErr.Err.Error(), "no such file") {
   188  		switch parsed.ContentType {
   189  		case ObjectType:
   190  			what = "object"
   191  		case WorkfileType:
   192  			what = "work file"
   193  		case ECSliceType:
   194  			what = "ec slice"
   195  		case ECMetaType:
   196  			what = "ec metadata"
   197  		default:
   198  			what = parsed.ContentType + "(?)"
   199  		}
   200  		pathErr.Err = cos.NewErrNotFound(nil, "content type "+what)
   201  	}
   202  }