github.com/mondo192/jfrog-client-go@v1.0.0/utils/io/fileutils/files.go (about) 1 package fileutils 2 3 import ( 4 "bufio" 5 "bytes" 6 "encoding/json" 7 "errors" 8 "golang.org/x/exp/slices" 9 "io" 10 "net/url" 11 "os" 12 "os/user" 13 "path/filepath" 14 "reflect" 15 "strings" 16 17 "github.com/jfrog/build-info-go/entities" 18 biutils "github.com/jfrog/build-info-go/utils" 19 gofrog "github.com/jfrog/gofrog/io" 20 21 "github.com/mondo192/jfrog-client-go/utils/errorutils" 22 ) 23 24 const ( 25 SymlinkFileContent = "" 26 File ItemType = "file" 27 Dir ItemType = "dir" 28 Any ItemType = "any" 29 ) 30 31 func GetFileSeparator() string { 32 return string(os.PathSeparator) 33 } 34 35 // Check if path exists. 36 // If path points at a symlink and `preserveSymLink == true`, 37 // function will return `true` regardless of the symlink target 38 func IsPathExists(path string, preserveSymLink bool) bool { 39 _, err := GetFileInfo(path, preserveSymLink) 40 return !os.IsNotExist(err) 41 } 42 43 // Check if path points at a file. 44 // If path points at a symlink and `preserveSymLink == true`, 45 // function will return `true` regardless of the symlink target 46 func IsFileExists(path string, preserveSymLink bool) (bool, error) { 47 fileInfo, err := GetFileInfo(path, preserveSymLink) 48 if err != nil { 49 if os.IsNotExist(err) { // If doesn't exist, don't omit an error 50 return false, nil 51 } 52 return false, errorutils.CheckError(err) 53 } 54 return !fileInfo.IsDir(), nil 55 } 56 57 // Check if path points at a directory. 58 // If path points at a symlink and `preserveSymLink == true`, 59 // function will return `false` regardless of the symlink target 60 func IsDirExists(path string, preserveSymLink bool) (bool, error) { 61 fileInfo, err := GetFileInfo(path, preserveSymLink) 62 if err != nil { 63 if os.IsNotExist(err) { // If doesn't exist, don't omit an error 64 return false, nil 65 } 66 return false, errorutils.CheckError(err) 67 } 68 return fileInfo.IsDir(), nil 69 } 70 71 // Get the file info of the file in path. 72 // If path points at a symlink and `preserveSymLink == true`, return the file info of the symlink instead 73 func GetFileInfo(path string, preserveSymLink bool) (fileInfo os.FileInfo, err error) { 74 if preserveSymLink { 75 fileInfo, err = os.Lstat(path) 76 } else { 77 fileInfo, err = os.Stat(path) 78 } 79 // We should not do CheckError here, because the error is checked by the calling functions. 80 return fileInfo, err 81 } 82 83 func IsDirEmpty(path string) (isEmpty bool, err error) { 84 dir, err := os.Open(path) 85 if err != nil { 86 return false, errorutils.CheckError(err) 87 } 88 defer func() { 89 e := dir.Close() 90 if err == nil { 91 err = errorutils.CheckError(e) 92 } 93 }() 94 95 _, err = dir.Readdirnames(1) 96 if err == io.EOF { 97 return true, nil 98 } 99 return false, errorutils.CheckError(err) 100 } 101 102 func IsPathSymlink(path string) bool { 103 f, _ := os.Lstat(path) 104 return f != nil && IsFileSymlink(f) 105 } 106 107 func IsFileSymlink(file os.FileInfo) bool { 108 return file.Mode()&os.ModeSymlink != 0 109 } 110 111 // Return the file's name and dir of a given path by finding the index of the last separator in the path. 112 // Support separators : "/" , "\\" and "\\\\" 113 func GetFileAndDirFromPath(path string) (fileName, dir string) { 114 index1 := strings.LastIndex(path, "/") 115 index2 := strings.LastIndex(path, "\\") 116 var index int 117 offset := 0 118 if index1 >= index2 { 119 index = index1 120 } else { 121 index = index2 122 // Check if the last separator is "\\\\" or "\\". 123 index3 := strings.LastIndex(path, "\\\\") 124 if index3 != -1 && index2-index3 == 1 { 125 offset = 1 126 } 127 } 128 if index != -1 { 129 fileName = path[index+1:] 130 // If the last separator is "\\\\" index will contain the index of the last "\\" , 131 // to get the dir path (without separator suffix) we will use the offset's value. 132 dir = path[:index-offset] 133 return 134 } 135 fileName = path 136 dir = "" 137 return 138 } 139 140 // Get the local path and filename from original file name and path according to targetPath 141 func GetLocalPathAndFile(originalFileName, relativePath, targetPath string, flat bool, placeholdersUsed bool) (localTargetPath, fileName string) { 142 targetFileName, targetDirPath := GetFileAndDirFromPath(targetPath) 143 // Remove double slashes and double backslashes that may appear in the path 144 localTargetPath = filepath.Join(targetDirPath) 145 // When placeholders are used, the file path shouldn't be taken into account (or in other words, flat = true). 146 if !flat && !placeholdersUsed { 147 localTargetPath = filepath.Join(targetDirPath, relativePath) 148 } 149 150 fileName = originalFileName 151 // '.' as a target path is equivalent to an empty target path. 152 if targetFileName != "" && targetFileName != "." { 153 fileName = targetFileName 154 } 155 return 156 } 157 158 // Return the recursive list of files and directories in the specified path 159 func ListFilesRecursiveWalkIntoDirSymlink(path string, walkIntoDirSymlink bool) (fileList []string, err error) { 160 fileList = []string{} 161 err = gofrog.Walk(path, func(path string, f os.FileInfo, err error) error { 162 fileList = append(fileList, path) 163 return nil 164 }, walkIntoDirSymlink) 165 err = errorutils.CheckError(err) 166 return 167 } 168 169 // Return all files in the specified path who satisfy the filter func. Not recursive. 170 func ListFilesByFilterFunc(path string, filterFunc func(filePath string) (bool, error)) ([]string, error) { 171 sep := GetFileSeparator() 172 if !strings.HasSuffix(path, sep) { 173 path += sep 174 } 175 var fileList []string 176 files, _ := os.ReadDir(path) 177 path = strings.TrimPrefix(path, "."+sep) 178 179 for _, f := range files { 180 filePath := path + f.Name() 181 satisfy, err := filterFunc(filePath) 182 if err != nil { 183 return nil, err 184 } 185 if !satisfy { 186 continue 187 } 188 exists, err := IsFileExists(filePath, false) 189 if err != nil { 190 return nil, err 191 } 192 if exists { 193 fileList = append(fileList, filePath) 194 continue 195 } 196 197 // Checks if the filepath is a symlink. 198 if IsPathSymlink(filePath) { 199 // Gets the file info of the symlink. 200 file, err := GetFileInfo(filePath, false) 201 if errorutils.CheckError(err) != nil { 202 return nil, err 203 } 204 // Checks if the symlink is a file. 205 if !file.IsDir() { 206 fileList = append(fileList, filePath) 207 } 208 } 209 } 210 return fileList, nil 211 } 212 213 // Return the list of files and directories in the specified path 214 func ListFiles(path string, includeDirs bool) ([]string, error) { 215 sep := GetFileSeparator() 216 if !strings.HasSuffix(path, sep) { 217 path += sep 218 } 219 fileList := []string{} 220 files, _ := os.ReadDir(path) 221 path = strings.TrimPrefix(path, "."+sep) 222 223 for _, f := range files { 224 filePath := path + f.Name() 225 exists, err := IsFileExists(filePath, false) 226 if err != nil { 227 return nil, err 228 } 229 if exists || IsPathSymlink(filePath) { 230 fileList = append(fileList, filePath) 231 } else if includeDirs { 232 isDir, err := IsDirExists(filePath, false) 233 if err != nil { 234 return nil, err 235 } 236 if isDir { 237 fileList = append(fileList, filePath) 238 } 239 } 240 } 241 return fileList, nil 242 } 243 244 func GetUploadRequestContent(file *os.File) io.Reader { 245 if file == nil { 246 return bytes.NewBuffer([]byte(SymlinkFileContent)) 247 } 248 return bufio.NewReader(file) 249 } 250 251 func GetFileSize(file *os.File) (int64, error) { 252 size := int64(0) 253 if file != nil { 254 fileInfo, err := file.Stat() 255 if errorutils.CheckError(err) != nil { 256 return size, err 257 } 258 size = fileInfo.Size() 259 } 260 return size, nil 261 } 262 263 func CreateFilePath(localPath, fileName string) (string, error) { 264 if localPath != "" { 265 err := os.MkdirAll(localPath, 0777) 266 if errorutils.CheckError(err) != nil { 267 return "", err 268 } 269 fileName = filepath.Join(localPath, fileName) 270 } 271 return fileName, nil 272 } 273 274 func CreateDirIfNotExist(path string) error { 275 exist, err := IsDirExists(path, false) 276 if exist || err != nil { 277 return err 278 } 279 _, err = CreateFilePath(path, "") 280 return err 281 } 282 283 // Reads the content of the file in the source path and appends it to 284 // the file in the destination path. 285 func AppendFile(srcPath string, destFile *os.File) error { 286 srcFile, err := os.Open(srcPath) 287 err = errorutils.CheckError(err) 288 if err != nil { 289 return err 290 } 291 292 defer func() error { 293 err := srcFile.Close() 294 return errorutils.CheckError(err) 295 }() 296 297 reader := bufio.NewReader(srcFile) 298 299 writer := bufio.NewWriter(destFile) 300 buf := make([]byte, 1024000) 301 for { 302 n, err := reader.Read(buf) 303 if err != io.EOF { 304 err = errorutils.CheckError(err) 305 if err != nil { 306 return err 307 } 308 } 309 if n == 0 { 310 break 311 } 312 _, err = writer.Write(buf[:n]) 313 err = errorutils.CheckError(err) 314 if err != nil { 315 return err 316 } 317 } 318 err = writer.Flush() 319 return errorutils.CheckError(err) 320 } 321 322 func GetHomeDir() string { 323 home := os.Getenv("HOME") 324 if home != "" { 325 return home 326 } 327 home = os.Getenv("USERPROFILE") 328 if home != "" { 329 return home 330 } 331 currentUser, err := user.Current() 332 if err == nil { 333 return currentUser.HomeDir 334 } 335 return "" 336 } 337 338 func IsSshUrl(urlPath string) bool { 339 u, err := url.Parse(urlPath) 340 if err != nil { 341 return false 342 } 343 return strings.ToLower(u.Scheme) == "ssh" 344 } 345 346 func ReadFile(filePath string) ([]byte, error) { 347 content, err := os.ReadFile(filePath) 348 err = errorutils.CheckError(err) 349 return content, err 350 } 351 352 func GetFileDetails(filePath string, includeChecksums bool) (details *FileDetails, err error) { 353 details = new(FileDetails) 354 if includeChecksums { 355 details.Checksum, err = calcChecksumDetails(filePath) 356 if err != nil { 357 return details, err 358 } 359 } else { 360 details.Checksum = entities.Checksum{} 361 } 362 363 file, err := os.Open(filePath) 364 defer func() { 365 e := file.Close() 366 if err == nil { 367 err = errorutils.CheckError(e) 368 } 369 }() 370 if errorutils.CheckError(err) != nil { 371 return nil, err 372 } 373 fileInfo, err := file.Stat() 374 if errorutils.CheckError(err) != nil { 375 return nil, err 376 } 377 details.Size = fileInfo.Size() 378 return details, nil 379 } 380 381 func calcChecksumDetails(filePath string) (checksum entities.Checksum, err error) { 382 file, err := os.Open(filePath) 383 defer func() { 384 e := file.Close() 385 if err == nil { 386 err = errorutils.CheckError(e) 387 } 388 }() 389 if errorutils.CheckError(err) != nil { 390 return entities.Checksum{}, err 391 } 392 return calcChecksumDetailsFromReader(file) 393 } 394 395 func GetFileDetailsFromReader(reader io.Reader, includeChecksums bool) (*FileDetails, error) { 396 var err error 397 details := new(FileDetails) 398 399 pr, pw := io.Pipe() 400 defer pr.Close() 401 402 go func() { 403 defer pw.Close() 404 details.Size, err = io.Copy(pw, reader) 405 }() 406 407 if includeChecksums { 408 details.Checksum, err = calcChecksumDetailsFromReader(pr) 409 } 410 return details, err 411 } 412 413 func calcChecksumDetailsFromReader(reader io.Reader) (entities.Checksum, error) { 414 checksumInfo, err := biutils.CalcChecksums(reader) 415 if err != nil { 416 return entities.Checksum{}, errorutils.CheckError(err) 417 } 418 return entities.Checksum{Md5: checksumInfo[biutils.MD5], Sha1: checksumInfo[biutils.SHA1], Sha256: checksumInfo[biutils.SHA256]}, nil 419 } 420 421 type FileDetails struct { 422 Checksum entities.Checksum 423 Size int64 424 } 425 426 func CopyFile(dst, src string) (err error) { 427 srcFile, err := os.Open(src) 428 if err != nil { 429 return err 430 } 431 defer func() { 432 e := srcFile.Close() 433 if err == nil { 434 err = errorutils.CheckError(e) 435 } 436 }() 437 fileName, _ := GetFileAndDirFromPath(src) 438 dstPath, err := CreateFilePath(dst, fileName) 439 if err != nil { 440 return err 441 } 442 dstFile, err := os.Create(dstPath) 443 if err != nil { 444 return err 445 } 446 defer func() { 447 e := dstFile.Close() 448 if err == nil { 449 err = errorutils.CheckError(e) 450 } 451 }() 452 _, err = io.Copy(dstFile, srcFile) 453 if err != nil { 454 return err 455 } 456 return nil 457 } 458 459 // Copy directory content from one path to another. 460 // includeDirs means to copy also the dirs if presented in the src folder. 461 // excludeNames - Skip files/dirs in the src folder that match names in provided slice. ONLY excludes first layer (only in src folder). 462 func CopyDir(fromPath, toPath string, includeDirs bool, excludeNames []string) error { 463 err := CreateDirIfNotExist(toPath) 464 if err != nil { 465 return err 466 } 467 468 files, err := ListFiles(fromPath, includeDirs) 469 if err != nil { 470 return err 471 } 472 473 for _, v := range files { 474 // Skip if excluded 475 if slices.Contains(excludeNames, filepath.Base(v)) { 476 continue 477 } 478 479 dir, err := IsDirExists(v, false) 480 if err != nil { 481 return err 482 } 483 484 if dir { 485 toPath := toPath + GetFileSeparator() + filepath.Base(v) 486 err := CopyDir(v, toPath, true, nil) 487 if err != nil { 488 return err 489 } 490 continue 491 } 492 err = CopyFile(toPath, v) 493 if err != nil { 494 return err 495 } 496 } 497 return err 498 } 499 500 // Removing the provided path from the filesystem 501 func RemovePath(testPath string) error { 502 if _, err := os.Stat(testPath); err == nil { 503 // Delete the path 504 err = RemoveTempDir(testPath) 505 if err != nil { 506 return errors.New("Cannot remove path: " + testPath + " due to: " + err.Error()) 507 } 508 } 509 return nil 510 } 511 512 // Renaming from old path to new path. 513 func RenamePath(oldPath, newPath string) error { 514 err := CopyDir(oldPath, newPath, true, nil) 515 if err != nil { 516 return errors.New("Error copying directory: " + oldPath + "to" + newPath + err.Error()) 517 } 518 return RemovePath(oldPath) 519 } 520 521 // Returns the path to the directory in which itemToFind is located. 522 // Traversing through directories from current work-dir to root. 523 // itemType determines whether looking for a file or dir. 524 func FindUpstream(itemToFInd string, itemType ItemType) (wd string, exists bool, err error) { 525 // Create a map to store all paths visited, to avoid running in circles. 526 visitedPaths := make(map[string]bool) 527 // Get the current directory. 528 wd, err = os.Getwd() 529 if err != nil { 530 return 531 } 532 defer os.Chdir(wd) 533 534 // Get the OS root. 535 osRoot := os.Getenv("SYSTEMDRIVE") 536 if osRoot != "" { 537 // If this is a Windows machine: 538 osRoot += "\\" 539 } else { 540 // Unix: 541 osRoot = "/" 542 } 543 544 // Check if the current directory includes itemToFind. If not, check the parent directory 545 // and so on. 546 exists = false 547 for { 548 // If itemToFind is found in the current directory, return the path. 549 switch itemType { 550 case Any: 551 exists = IsPathExists(filepath.Join(wd, itemToFInd), false) 552 case File: 553 exists, err = IsFileExists(filepath.Join(wd, itemToFInd), false) 554 case Dir: 555 exists, err = IsDirExists(filepath.Join(wd, itemToFInd), false) 556 } 557 if err != nil || exists { 558 return 559 } 560 561 // If this the OS root, we can stop. 562 if wd == osRoot { 563 break 564 } 565 566 // Save this path. 567 visitedPaths[wd] = true 568 // CD to the parent directory. 569 wd = filepath.Dir(wd) 570 err := os.Chdir(wd) 571 if err != nil { 572 return "", false, err 573 } 574 575 // If we already visited this directory, it means that there's a loop and we can stop. 576 if visitedPaths[wd] { 577 return "", false, nil 578 } 579 } 580 581 return "", false, nil 582 } 583 584 type ItemType string 585 586 // Returns true if the two files have the same MD5 checksum. 587 func FilesIdentical(file1 string, file2 string) (bool, error) { 588 srcDetails, err := GetFileDetails(file1, true) 589 if err != nil { 590 return false, err 591 } 592 toCompareDetails, err := GetFileDetails(file2, true) 593 if err != nil { 594 return false, err 595 } 596 return srcDetails.Checksum.Md5 == toCompareDetails.Checksum.Md5, nil 597 } 598 599 // JSONEqual compares the JSON from two files. 600 func JsonEqual(filePath1, filePath2 string) (isEqual bool, err error) { 601 reader1, err := os.Open(filePath1) 602 if err != nil { 603 return false, err 604 } 605 defer func() { 606 e := reader1.Close() 607 if err == nil { 608 err = errorutils.CheckError(e) 609 } 610 }() 611 reader2, err := os.Open(filePath2) 612 if err != nil { 613 return false, err 614 } 615 defer func() { 616 e := reader2.Close() 617 if err == nil { 618 err = errorutils.CheckError(e) 619 } 620 }() 621 var j, j2 interface{} 622 d := json.NewDecoder(reader1) 623 if err := d.Decode(&j); err != nil { 624 return false, err 625 } 626 d = json.NewDecoder(reader2) 627 if err := d.Decode(&j2); err != nil { 628 return false, err 629 } 630 return reflect.DeepEqual(j2, j), nil 631 } 632 633 // Compares provided Md5 and Sha1 to those of a local file. 634 func IsEqualToLocalFile(localFilePath, md5, sha1 string) (bool, error) { 635 exists, err := IsFileExists(localFilePath, false) 636 if err != nil { 637 return false, err 638 } 639 if !exists { 640 return false, nil 641 } 642 localFileDetails, err := GetFileDetails(localFilePath, true) 643 if err != nil { 644 return false, err 645 } 646 return localFileDetails.Checksum.Md5 == md5 && localFileDetails.Checksum.Sha1 == sha1, nil 647 } 648 649 // Move directory content from one path to another. 650 func MoveDir(fromPath, toPath string) error { 651 err := CreateDirIfNotExist(toPath) 652 if err != nil { 653 return err 654 } 655 656 files, err := ListFiles(fromPath, true) 657 if err != nil { 658 return err 659 } 660 661 for _, v := range files { 662 dir, err := IsDirExists(v, true) 663 if err != nil { 664 return err 665 } 666 667 if dir { 668 toPath := toPath + GetFileSeparator() + filepath.Base(v) 669 err := MoveDir(v, toPath) 670 if err != nil { 671 return err 672 } 673 continue 674 } 675 err = MoveFile(v, filepath.Join(toPath, filepath.Base(v))) 676 if err != nil { 677 return err 678 } 679 } 680 return err 681 } 682 683 // GoLang: os.Rename() give error "invalid cross-device link" for Docker container with Volumes. 684 // MoveFile(source, destination) will work moving file between folders 685 // Therefore, we are using our own implementation (MoveFile) in order to rename files. 686 func MoveFile(sourcePath, destPath string) (err error) { 687 inputFileOpen := true 688 var inputFile *os.File 689 inputFile, err = os.Open(sourcePath) 690 if err != nil { 691 return errorutils.CheckError(err) 692 } 693 defer func() { 694 if inputFileOpen { 695 e := inputFile.Close() 696 if err == nil { 697 err = errorutils.CheckError(e) 698 } 699 } 700 }() 701 inputFileInfo, err := inputFile.Stat() 702 if err != nil { 703 return errorutils.CheckError(err) 704 } 705 706 var outputFile *os.File 707 outputFile, err = os.Create(destPath) 708 if err != nil { 709 return errorutils.CheckError(err) 710 } 711 defer func() { 712 e := outputFile.Close() 713 if err == nil { 714 err = errorutils.CheckError(e) 715 } 716 }() 717 718 _, err = io.Copy(outputFile, inputFile) 719 if err != nil { 720 return errorutils.CheckError(err) 721 } 722 err = os.Chmod(destPath, inputFileInfo.Mode()) 723 if err != nil { 724 return errorutils.CheckError(err) 725 } 726 727 // The copy was successful, so now delete the original file 728 err = inputFile.Close() 729 if err != nil { 730 return errorutils.CheckError(err) 731 } 732 inputFileOpen = false 733 err = os.Remove(sourcePath) 734 return errorutils.CheckError(err) 735 } 736 737 // RemoveDirContents removes the contents of the directory, without removing the directory itself. 738 // If it encounters an error before removing all the files, it stops and returns that error. 739 func RemoveDirContents(dirPath string) (err error) { 740 d, err := os.Open(dirPath) 741 if err != nil { 742 return errorutils.CheckError(err) 743 } 744 defer func() { 745 e := d.Close() 746 if err == nil { 747 err = errorutils.CheckError(e) 748 } 749 }() 750 names, err := d.Readdirnames(-1) 751 if err != nil { 752 return errorutils.CheckError(err) 753 } 754 for _, name := range names { 755 err = os.RemoveAll(filepath.Join(dirPath, name)) 756 if err != nil { 757 return errorutils.CheckError(err) 758 } 759 } 760 return nil 761 }