zotregistry.dev/zot@v1.4.4-0.20240314164342-eec277e14d20/pkg/storage/local/driver.go (about) 1 package local 2 3 import ( 4 "bufio" 5 "bytes" 6 "errors" 7 "io" 8 "os" 9 "path" 10 "sort" 11 "time" 12 "unicode/utf8" 13 14 storagedriver "github.com/docker/distribution/registry/storage/driver" 15 16 zerr "zotregistry.dev/zot/errors" 17 storageConstants "zotregistry.dev/zot/pkg/storage/constants" 18 "zotregistry.dev/zot/pkg/test/inject" 19 ) 20 21 type Driver struct { 22 commit bool 23 } 24 25 func New(commit bool) *Driver { 26 return &Driver{commit: commit} 27 } 28 29 func (driver *Driver) Name() string { 30 return storageConstants.LocalStorageDriverName 31 } 32 33 func (driver *Driver) EnsureDir(path string) error { 34 err := os.MkdirAll(path, storageConstants.DefaultDirPerms) 35 36 return driver.formatErr(err) 37 } 38 39 func (driver *Driver) DirExists(path string) bool { 40 if !utf8.ValidString(path) { 41 return false 42 } 43 44 fileInfo, err := os.Stat(path) 45 if err != nil { 46 // if os.Stat returns any error, fileInfo will be nil 47 // we can't check if the path is a directory using fileInfo if we received an error 48 // let's assume the directory doesn't exist in all error cases 49 // see possible errors http://man.he.net/man2/newfstatat 50 return false 51 } 52 53 if !fileInfo.IsDir() { 54 return false 55 } 56 57 return true 58 } 59 60 func (driver *Driver) Reader(path string, offset int64) (io.ReadCloser, error) { 61 file, err := os.OpenFile(path, os.O_RDONLY, storageConstants.DefaultFilePerms) 62 if err != nil { 63 if os.IsNotExist(err) { 64 return nil, storagedriver.PathNotFoundError{Path: path} 65 } 66 67 return nil, driver.formatErr(err) 68 } 69 70 seekPos, err := file.Seek(offset, io.SeekStart) 71 if err != nil { 72 file.Close() 73 74 return nil, driver.formatErr(err) 75 } else if seekPos < offset { 76 file.Close() 77 78 return nil, storagedriver.InvalidOffsetError{Path: path, Offset: offset} 79 } 80 81 return file, nil 82 } 83 84 func (driver *Driver) ReadFile(path string) ([]byte, error) { 85 reader, err := driver.Reader(path, 0) 86 if err != nil { 87 return nil, err 88 } 89 90 defer reader.Close() 91 92 buf, err := io.ReadAll(reader) 93 if err != nil { 94 return nil, driver.formatErr(err) 95 } 96 97 return buf, nil 98 } 99 100 func (driver *Driver) Delete(path string) error { 101 _, err := os.Stat(path) 102 if err != nil && !os.IsNotExist(err) { 103 return driver.formatErr(err) 104 } else if err != nil { 105 return storagedriver.PathNotFoundError{Path: path} 106 } 107 108 return os.RemoveAll(path) 109 } 110 111 func (driver *Driver) Stat(path string) (storagedriver.FileInfo, error) { 112 fi, err := os.Stat(path) //nolint: varnamelen 113 if err != nil { 114 if os.IsNotExist(err) { 115 return nil, storagedriver.PathNotFoundError{Path: path} 116 } 117 118 return nil, driver.formatErr(err) 119 } 120 121 return fileInfo{ 122 path: path, 123 FileInfo: fi, 124 }, nil 125 } 126 127 func (driver *Driver) Writer(filepath string, append bool) (storagedriver.FileWriter, error) { //nolint:predeclared 128 if append { 129 _, err := os.Stat(filepath) 130 if err != nil { 131 if os.IsNotExist(err) { 132 return nil, storagedriver.PathNotFoundError{Path: filepath} 133 } 134 135 return nil, driver.formatErr(err) 136 } 137 } 138 139 parentDir := path.Dir(filepath) 140 if err := os.MkdirAll(parentDir, storageConstants.DefaultDirPerms); err != nil { 141 return nil, driver.formatErr(err) 142 } 143 144 file, err := os.OpenFile(filepath, os.O_WRONLY|os.O_CREATE, storageConstants.DefaultFilePerms) 145 if err != nil { 146 return nil, driver.formatErr(err) 147 } 148 149 var offset int64 150 151 if !append { 152 err := file.Truncate(0) 153 if err != nil { 154 file.Close() 155 156 return nil, driver.formatErr(err) 157 } 158 } else { 159 n, err := file.Seek(0, io.SeekEnd) //nolint: varnamelen 160 if err != nil { 161 file.Close() 162 163 return nil, driver.formatErr(err) 164 } 165 offset = n 166 } 167 168 return newFileWriter(file, offset, driver.commit), nil 169 } 170 171 func (driver *Driver) WriteFile(filepath string, content []byte) (int, error) { 172 writer, err := driver.Writer(filepath, false) 173 if err != nil { 174 return -1, err 175 } 176 177 nbytes, err := io.Copy(writer, bytes.NewReader(content)) 178 if err != nil { 179 _ = writer.Cancel() 180 181 return -1, driver.formatErr(err) 182 } 183 184 return int(nbytes), writer.Close() 185 } 186 187 func (driver *Driver) Walk(path string, walkFn storagedriver.WalkFn) error { 188 children, err := driver.List(path) 189 if err != nil { 190 return err 191 } 192 193 sort.Stable(sort.StringSlice(children)) 194 195 for _, child := range children { 196 // Calling driver.Stat for every entry is quite 197 // expensive when running against backends with a slow Stat 198 // implementation, such as s3. This is very likely a serious 199 // performance bottleneck. 200 fileInfo, err := driver.Stat(child) 201 if err != nil { 202 switch errors.As(err, &storagedriver.PathNotFoundError{}) { 203 case true: 204 // repository was removed in between listing and enumeration. Ignore it. 205 continue 206 default: 207 return err 208 } 209 } 210 err = walkFn(fileInfo) 211 //nolint: gocritic 212 if err == nil && fileInfo.IsDir() { 213 if err := driver.Walk(child, walkFn); err != nil { 214 return err 215 } 216 } else if errors.Is(err, storagedriver.ErrSkipDir) { 217 // Stop iteration if it's a file, otherwise noop if it's a directory 218 if !fileInfo.IsDir() { 219 return nil 220 } 221 } else if err != nil { 222 return driver.formatErr(err) 223 } 224 } 225 226 return nil 227 } 228 229 func (driver *Driver) List(fullpath string) ([]string, error) { 230 dir, err := os.Open(fullpath) 231 if err != nil { 232 if os.IsNotExist(err) { 233 return nil, storagedriver.PathNotFoundError{Path: fullpath} 234 } 235 236 return nil, driver.formatErr(err) 237 } 238 239 defer dir.Close() 240 241 fileNames, err := dir.Readdirnames(0) 242 if err != nil { 243 return nil, driver.formatErr(err) 244 } 245 246 keys := make([]string, 0, len(fileNames)) 247 for _, fileName := range fileNames { 248 keys = append(keys, path.Join(fullpath, fileName)) 249 } 250 251 return keys, nil 252 } 253 254 func (driver *Driver) Move(sourcePath string, destPath string) error { 255 if _, err := os.Stat(sourcePath); os.IsNotExist(err) { 256 return storagedriver.PathNotFoundError{Path: sourcePath} 257 } 258 259 if err := os.MkdirAll(path.Dir(destPath), storageConstants.DefaultDirPerms); err != nil { 260 return driver.formatErr(err) 261 } 262 263 return driver.formatErr(os.Rename(sourcePath, destPath)) 264 } 265 266 func (driver *Driver) SameFile(path1, path2 string) bool { 267 file1, err := os.Stat(path1) 268 if err != nil { 269 return false 270 } 271 272 file2, err := os.Stat(path2) 273 if err != nil { 274 return false 275 } 276 277 return os.SameFile(file1, file2) 278 } 279 280 func (driver *Driver) Link(src, dest string) error { 281 if err := os.Remove(dest); err != nil && !os.IsNotExist(err) { 282 return err 283 } 284 285 if err := os.Link(src, dest); err != nil { 286 return driver.formatErr(err) 287 } 288 289 /* also update the modtime, so that gc won't remove recently linked blobs 290 otherwise ifBlobOlderThan(gcDelay) will return the modtime of the inode */ 291 currentTime := time.Now() //nolint: gosmopolitan 292 if err := os.Chtimes(dest, currentTime, currentTime); err != nil { 293 return driver.formatErr(err) 294 } 295 296 return nil 297 } 298 299 func (driver *Driver) formatErr(err error) error { 300 switch actual := err.(type) { //nolint: errorlint 301 case nil: 302 return nil 303 case storagedriver.PathNotFoundError: 304 actual.DriverName = driver.Name() 305 306 return actual 307 case storagedriver.InvalidPathError: 308 actual.DriverName = driver.Name() 309 310 return actual 311 case storagedriver.InvalidOffsetError: 312 actual.DriverName = driver.Name() 313 314 return actual 315 default: 316 storageError := storagedriver.Error{ 317 DriverName: driver.Name(), 318 Enclosed: err, 319 } 320 321 return storageError 322 } 323 } 324 325 type fileInfo struct { 326 os.FileInfo 327 path string 328 } 329 330 // asserts fileInfo implements storagedriver.FileInfo. 331 var _ storagedriver.FileInfo = fileInfo{} 332 333 // Path provides the full path of the target of this file info. 334 func (fi fileInfo) Path() string { 335 return fi.path 336 } 337 338 // Size returns current length in bytes of the file. The return value can 339 // be used to write to the end of the file at path. The value is 340 // meaningless if IsDir returns true. 341 func (fi fileInfo) Size() int64 { 342 if fi.IsDir() { 343 return 0 344 } 345 346 return fi.FileInfo.Size() 347 } 348 349 // ModTime returns the modification time for the file. For backends that 350 // don't have a modification time, the creation time should be returned. 351 func (fi fileInfo) ModTime() time.Time { 352 return fi.FileInfo.ModTime() 353 } 354 355 // IsDir returns true if the path is a directory. 356 func (fi fileInfo) IsDir() bool { 357 return fi.FileInfo.IsDir() 358 } 359 360 type fileWriter struct { 361 file *os.File 362 size int64 363 bw *bufio.Writer 364 closed bool 365 committed bool 366 cancelled bool 367 commit bool 368 } 369 370 func newFileWriter(file *os.File, size int64, commit bool) *fileWriter { 371 return &fileWriter{ 372 file: file, 373 size: size, 374 commit: commit, 375 bw: bufio.NewWriter(file), 376 } 377 } 378 379 func (fw *fileWriter) Write(buf []byte) (int, error) { 380 //nolint: gocritic 381 if fw.closed { 382 return 0, zerr.ErrFileAlreadyClosed 383 } else if fw.committed { 384 return 0, zerr.ErrFileAlreadyCommitted 385 } else if fw.cancelled { 386 return 0, zerr.ErrFileAlreadyCancelled 387 } 388 389 n, err := fw.bw.Write(buf) 390 fw.size += int64(n) 391 392 return n, err 393 } 394 395 func (fw *fileWriter) Size() int64 { 396 return fw.size 397 } 398 399 func (fw *fileWriter) Close() error { 400 if fw.closed { 401 return zerr.ErrFileAlreadyClosed 402 } 403 404 if err := fw.bw.Flush(); err != nil { 405 return err 406 } 407 408 if fw.commit { 409 if err := inject.Error(fw.file.Sync()); err != nil { 410 return err 411 } 412 } 413 414 if err := inject.Error(fw.file.Close()); err != nil { 415 return err 416 } 417 418 fw.closed = true 419 420 return nil 421 } 422 423 func (fw *fileWriter) Cancel() error { 424 if fw.closed { 425 return zerr.ErrFileAlreadyClosed 426 } 427 428 fw.cancelled = true 429 fw.file.Close() 430 431 return os.Remove(fw.file.Name()) 432 } 433 434 func (fw *fileWriter) Commit() error { 435 //nolint: gocritic 436 if fw.closed { 437 return zerr.ErrFileAlreadyClosed 438 } else if fw.committed { 439 return zerr.ErrFileAlreadyCommitted 440 } else if fw.cancelled { 441 return zerr.ErrFileAlreadyCancelled 442 } 443 444 if err := fw.bw.Flush(); err != nil { 445 return err 446 } 447 448 if fw.commit { 449 if err := fw.file.Sync(); err != nil { 450 return err 451 } 452 } 453 454 fw.committed = true 455 456 return nil 457 } 458 459 func ValidateHardLink(rootDir string) error { 460 if err := os.MkdirAll(rootDir, storageConstants.DefaultDirPerms); err != nil { 461 return err 462 } 463 464 err := os.WriteFile(path.Join(rootDir, "hardlinkcheck.txt"), 465 []byte("check whether hardlinks work on filesystem"), storageConstants.DefaultFilePerms) 466 if err != nil { 467 return err 468 } 469 470 err = os.Link(path.Join(rootDir, "hardlinkcheck.txt"), path.Join(rootDir, "duphardlinkcheck.txt")) 471 if err != nil { 472 // Remove hardlinkcheck.txt if hardlink fails 473 zerr := os.RemoveAll(path.Join(rootDir, "hardlinkcheck.txt")) 474 if zerr != nil { 475 return zerr 476 } 477 478 return err 479 } 480 481 err = os.RemoveAll(path.Join(rootDir, "hardlinkcheck.txt")) 482 if err != nil { 483 return err 484 } 485 486 return os.RemoveAll(path.Join(rootDir, "duphardlinkcheck.txt")) 487 }