github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/updater/util/unzip.go (about) 1 // Copyright 2015 Keybase, Inc. All rights reserved. Use of 2 // this source code is governed by the included BSD license. 3 4 package util 5 6 import ( 7 "archive/zip" 8 "fmt" 9 "io" 10 "os" 11 "path/filepath" 12 ) 13 14 // UnzipOver safely unzips a file and copies it contents to a destination path. 15 // If destination path exists, it will be removed first. 16 // The filename must have a ".zip" extension. 17 // You can specify a check function, which will run before moving the unzipped 18 // directory into place. 19 // If you specify a tmpDir and destination path exists, it will be moved there 20 // instead of being removed. 21 // 22 // To unzip Keybase-1.2.3.zip and move the contents Keybase.app to /Applications/Keybase.app 23 // 24 // UnzipOver("/tmp/Keybase-1.2.3.zip", "Keybase.app", "/Applications/Keybase.app", check, "", log) 25 func UnzipOver(sourcePath string, path string, destinationPath string, check func(sourcePath, destinationPath string) error, tmpDir string, log Log) error { 26 unzipPath := fmt.Sprintf("%s.unzipped", sourcePath) 27 defer RemoveFileAtPath(unzipPath) 28 err := unzipOver(sourcePath, unzipPath, log) 29 if err != nil { 30 return err 31 } 32 33 contentPath := filepath.Join(unzipPath, path) 34 35 err = check(contentPath, destinationPath) 36 if err != nil { 37 return err 38 } 39 40 return MoveFile(contentPath, destinationPath, tmpDir, log) 41 } 42 43 // UnzipPath unzips and returns path to unzipped directory 44 func UnzipPath(sourcePath string, log Log) (string, error) { 45 unzipPath := fmt.Sprintf("%s.unzipped", sourcePath) 46 err := unzipOver(sourcePath, unzipPath, log) 47 if err != nil { 48 return "", err 49 } 50 return unzipPath, nil 51 } 52 53 func unzipOver(sourcePath string, destinationPath string, log Log) error { 54 if destinationPath == "" { 55 return fmt.Errorf("Invalid destination %q", destinationPath) 56 } 57 58 if _, ferr := os.Stat(destinationPath); ferr == nil { 59 log.Infof("Removing existing unzip destination path: %s", destinationPath) 60 err := os.RemoveAll(destinationPath) 61 if err != nil { 62 return err 63 } 64 } 65 66 log.Infof("Unzipping %q to %q", sourcePath, destinationPath) 67 return Unzip(sourcePath, destinationPath, log) 68 } 69 70 // Unzip unpacks a zip file to a destination. 71 // This unpacks files using the current user and time (it doesn't preserve). 72 // This code was modified from https://stackoverflow.com/questions/20357223/easy-way-to-unzip-file-with-golang/20357902 73 func Unzip(sourcePath, destinationPath string, log Log) error { 74 r, err := zip.OpenReader(sourcePath) 75 if err != nil { 76 return err 77 } 78 defer func() { 79 if closeErr := r.Close(); closeErr != nil { 80 log.Warningf("Error in unzip closing zip file: %s", closeErr) 81 } 82 }() 83 84 err = os.MkdirAll(destinationPath, 0755) 85 if err != nil { 86 return err 87 } 88 89 // Closure to address file descriptors issue with all the deferred .Close() methods 90 extractAndWriteFile := func(f *zip.File) error { 91 rc, err := f.Open() 92 if err != nil { 93 return err 94 } 95 defer func() { 96 if err := rc.Close(); err != nil { 97 log.Warningf("Error in unzip closing file: %s", err) 98 } 99 }() 100 101 filePath := filepath.Join(destinationPath, f.Name) 102 fileInfo := f.FileInfo() 103 104 if fileInfo.IsDir() { 105 err := os.MkdirAll(filePath, fileInfo.Mode()) 106 if err != nil { 107 return err 108 } 109 } else { 110 err := os.MkdirAll(filepath.Dir(filePath), 0755) 111 if err != nil { 112 return err 113 } 114 115 if fileInfo.Mode()&os.ModeSymlink != 0 { 116 linkName, readErr := io.ReadAll(rc) 117 if readErr != nil { 118 return readErr 119 } 120 return os.Symlink(string(linkName), filePath) 121 } 122 123 fileCopy, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, fileInfo.Mode()) 124 if err != nil { 125 return err 126 } 127 defer Close(fileCopy) 128 129 _, err = io.Copy(fileCopy, rc) 130 if err != nil { 131 return err 132 } 133 } 134 135 return nil 136 } 137 138 for _, f := range r.File { 139 err := extractAndWriteFile(f) 140 if err != nil { 141 return err 142 } 143 } 144 145 return nil 146 }