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 }