github.com/Racer159/jackal@v0.32.7-0.20240401174413-0bd2339e4f2e/src/pkg/utils/io.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: 2021-Present The Jackal Authors 3 4 // Package utils provides generic helper functions. 5 package utils 6 7 import ( 8 "crypto/sha256" 9 "encoding/json" 10 "fmt" 11 "io" 12 "os" 13 "path/filepath" 14 15 "github.com/Racer159/jackal/src/config" 16 "github.com/Racer159/jackal/src/pkg/message" 17 "github.com/Racer159/jackal/src/types" 18 "github.com/defenseunicorns/pkg/helpers" 19 ) 20 21 const ( 22 tmpPathPrefix = "jackal-" 23 ) 24 25 // MakeTempDir creates a temp directory with the jackal- prefix. 26 func MakeTempDir(basePath string) (string, error) { 27 if basePath != "" { 28 if err := helpers.CreateDirectory(basePath, helpers.ReadWriteExecuteUser); err != nil { 29 return "", err 30 } 31 } 32 33 tmp, err := os.MkdirTemp(basePath, tmpPathPrefix) 34 if err != nil { 35 return "", err 36 } 37 38 message.Debug("Using temporary directory:", tmp) 39 40 return tmp, nil 41 } 42 43 // GetFinalExecutablePath returns the absolute path to the current executable, following any symlinks along the way. 44 func GetFinalExecutablePath() (string, error) { 45 message.Debug("utils.GetExecutablePath()") 46 47 binaryPath, err := os.Executable() 48 if err != nil { 49 return "", err 50 } 51 52 // In case the binary is symlinked somewhere else, get the final destination 53 linkedPath, err := filepath.EvalSymlinks(binaryPath) 54 return linkedPath, err 55 } 56 57 // GetFinalExecutableCommand returns the final path to the Jackal executable including and library prefixes and overrides. 58 func GetFinalExecutableCommand() (string, error) { 59 // In case the binary is symlinked somewhere else, get the final destination 60 jackalCommand, err := GetFinalExecutablePath() 61 if err != nil { 62 return jackalCommand, err 63 } 64 65 if config.ActionsCommandJackalPrefix != "" { 66 jackalCommand = fmt.Sprintf("%s %s", jackalCommand, config.ActionsCommandJackalPrefix) 67 } 68 69 // If a library user has chosen to override config to use system Jackal instead, reset the binary path. 70 if config.ActionsUseSystemJackal { 71 jackalCommand = "jackal" 72 } 73 74 return jackalCommand, err 75 } 76 77 // SplitFile will take a srcFile path and split it into files based on chunkSizeBytes 78 // the first file will be a metadata file containing: 79 // - sha256sum of the original file 80 // - number of bytes in the original file 81 // - number of files the srcFile was split into 82 // SplitFile will delete the original file 83 // 84 // Returns: 85 // - fileNames: list of file paths srcFile was split across 86 // - sha256sum: sha256sum of the srcFile before splitting 87 // - err: any errors encountered 88 func SplitFile(srcPath string, chunkSizeBytes int) (err error) { 89 var fileNames []string 90 var sha256sum string 91 hash := sha256.New() 92 93 // Set buffer size to some multiple of 4096 KiB for modern file system cluster sizes 94 bufferSize := 16 * 1024 * 1024 // 16 MiB 95 // if chunkSizeBytes is less than bufferSize, use chunkSizeBytes as bufferSize for simplicity 96 if chunkSizeBytes < bufferSize { 97 bufferSize = chunkSizeBytes 98 } 99 buf := make([]byte, bufferSize) 100 101 // get file size 102 fi, err := os.Stat(srcPath) 103 if err != nil { 104 return err 105 } 106 fileSize := fi.Size() 107 108 // start progress bar 109 title := fmt.Sprintf("[0/%d] MB bytes written", fileSize/1000/1000) 110 progressBar := message.NewProgressBar(fileSize, title) 111 defer progressBar.Stop() 112 113 // open srcFile 114 srcFile, err := os.Open(srcPath) 115 if err != nil { 116 return err 117 } 118 defer srcFile.Close() 119 120 // create file path starting from part 001 121 path := fmt.Sprintf("%s.part001", srcPath) 122 chunkFile, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, helpers.ReadAllWriteUser) 123 if err != nil { 124 return err 125 } 126 fileNames = append(fileNames, path) 127 defer chunkFile.Close() 128 129 // setup counter for tracking how many bytes are left to write to file 130 chunkBytesRemaining := chunkSizeBytes 131 // Loop over the tarball hashing as we go and breaking it into chunks based on the chunkSizeBytes 132 for { 133 bytesRead, err := srcFile.Read(buf) 134 135 if err != nil { 136 if err == io.EOF { 137 // At end of file, break out of loop 138 break 139 } 140 return err 141 } 142 143 // Pass data to hash 144 hash.Write(buf[0:bytesRead]) 145 146 // handle if we should split the data between two chunks 147 if chunkBytesRemaining < bytesRead { 148 // write the remaining chunk size to file 149 _, err := chunkFile.Write(buf[0:chunkBytesRemaining]) 150 if err != nil { 151 return err 152 } 153 err = chunkFile.Close() 154 if err != nil { 155 return err 156 } 157 158 // create new file 159 path = fmt.Sprintf("%s.part%03d", srcPath, len(fileNames)+1) 160 chunkFile, err = os.OpenFile(path, os.O_CREATE|os.O_WRONLY, helpers.ReadAllWriteUser) 161 if err != nil { 162 return err 163 } 164 fileNames = append(fileNames, path) 165 defer chunkFile.Close() 166 167 // write to new file where we left off 168 _, err = chunkFile.Write(buf[chunkBytesRemaining:bytesRead]) 169 if err != nil { 170 return err 171 } 172 173 // set chunkBytesRemaining considering how many bytes are already written to new file 174 chunkBytesRemaining = chunkSizeBytes - (bufferSize - chunkBytesRemaining) 175 } else { 176 _, err := chunkFile.Write(buf[0:bytesRead]) 177 if err != nil { 178 return err 179 } 180 chunkBytesRemaining = chunkBytesRemaining - bytesRead 181 } 182 183 // update progress bar 184 progressBar.Add(bufferSize) 185 title := fmt.Sprintf("[%d/%d] MB bytes written", progressBar.GetCurrent()/1000/1000, fileSize/1000/1000) 186 progressBar.UpdateTitle(title) 187 } 188 srcFile.Close() 189 _ = os.RemoveAll(srcPath) 190 191 // calculate sha256 sum 192 sha256sum = fmt.Sprintf("%x", hash.Sum(nil)) 193 194 // Marshal the data into a json file. 195 jsonData, err := json.Marshal(types.JackalSplitPackageData{ 196 Count: len(fileNames), 197 Bytes: fileSize, 198 Sha256Sum: sha256sum, 199 }) 200 if err != nil { 201 return fmt.Errorf("unable to marshal the split package data: %w", err) 202 } 203 204 // write header file 205 path = fmt.Sprintf("%s.part000", srcPath) 206 if err := os.WriteFile(path, jsonData, helpers.ReadAllWriteUser); err != nil { 207 return fmt.Errorf("unable to write the file %s: %w", path, err) 208 } 209 fileNames = append(fileNames, path) 210 progressBar.Successf("Package split across %d files", len(fileNames)) 211 212 return nil 213 }