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