github.com/angdraug/packer@v1.3.2/common/step_create_floppy.go (about) 1 package common 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "log" 9 "os" 10 "path" 11 "path/filepath" 12 "strings" 13 14 "github.com/hashicorp/packer/helper/multistep" 15 "github.com/hashicorp/packer/packer" 16 "github.com/mitchellh/go-fs" 17 "github.com/mitchellh/go-fs/fat" 18 ) 19 20 // StepCreateFloppy will create a floppy disk with the given files. 21 type StepCreateFloppy struct { 22 Files []string 23 Directories []string 24 25 floppyPath string 26 27 FilesAdded map[string]bool 28 } 29 30 func (s *StepCreateFloppy) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { 31 if len(s.Files) == 0 && len(s.Directories) == 0 { 32 log.Println("No floppy files specified. Floppy disk will not be made.") 33 return multistep.ActionContinue 34 } 35 36 s.FilesAdded = make(map[string]bool) 37 38 ui := state.Get("ui").(packer.Ui) 39 ui.Say("Creating floppy disk...") 40 41 // Create a temporary file to be our floppy drive 42 floppyF, err := ioutil.TempFile("", "packer") 43 if err != nil { 44 state.Put("error", 45 fmt.Errorf("Error creating temporary file for floppy: %s", err)) 46 return multistep.ActionHalt 47 } 48 defer floppyF.Close() 49 50 // Set the path so we can remove it later 51 s.floppyPath = floppyF.Name() 52 53 log.Printf("Floppy path: %s", s.floppyPath) 54 55 // Set the size of the file to be a floppy sized 56 if err := floppyF.Truncate(1440 * 1024); err != nil { 57 state.Put("error", fmt.Errorf("Error creating floppy: %s", err)) 58 return multistep.ActionHalt 59 } 60 61 // BlockDevice backed by the file for our filesystem 62 log.Println("Initializing block device backed by temporary file") 63 device, err := fs.NewFileDisk(floppyF) 64 if err != nil { 65 state.Put("error", fmt.Errorf("Error creating floppy: %s", err)) 66 return multistep.ActionHalt 67 } 68 69 // Format the block device so it contains a valid FAT filesystem 70 log.Println("Formatting the block device with a FAT filesystem...") 71 formatConfig := &fat.SuperFloppyConfig{ 72 FATType: fat.FAT12, 73 Label: "packer", 74 OEMName: "packer", 75 } 76 if err := fat.FormatSuperFloppy(device, formatConfig); err != nil { 77 state.Put("error", fmt.Errorf("Error creating floppy: %s", err)) 78 return multistep.ActionHalt 79 } 80 81 // The actual FAT filesystem 82 log.Println("Initializing FAT filesystem on block device") 83 fatFs, err := fat.New(device) 84 if err != nil { 85 state.Put("error", fmt.Errorf("Error creating floppy: %s", err)) 86 return multistep.ActionHalt 87 } 88 89 // Get the root directory to the filesystem and create a cache for any directories within 90 log.Println("Reading the root directory from the filesystem") 91 rootDir, err := fatFs.RootDir() 92 if err != nil { 93 state.Put("error", fmt.Errorf("Error creating floppy: %s", err)) 94 return multistep.ActionHalt 95 } 96 cache := fsDirectoryCache(rootDir) 97 98 // Utility functions for walking through a directory grabbing all files flatly 99 globFiles := func(files []string, list chan string) { 100 for _, filename := range files { 101 if strings.ContainsAny(filename, "*?[") { 102 matches, _ := filepath.Glob(filename) 103 if err != nil { 104 continue 105 } 106 107 for _, match := range matches { 108 list <- match 109 } 110 continue 111 } 112 list <- filename 113 } 114 close(list) 115 } 116 117 var crawlDirectoryFiles []string 118 crawlDirectory := func(path string, info os.FileInfo, err error) error { 119 if err != nil { 120 return err 121 } 122 if !info.IsDir() { 123 crawlDirectoryFiles = append(crawlDirectoryFiles, path) 124 ui.Message(fmt.Sprintf("Adding file: %s", path)) 125 } 126 return nil 127 } 128 crawlDirectoryFiles = []string{} 129 130 // Collect files and copy them flatly...because floppy_files is broken on purpose. 131 var filelist chan string 132 filelist = make(chan string) 133 go globFiles(s.Files, filelist) 134 135 ui.Message("Copying files flatly from floppy_files") 136 for { 137 filename, ok := <-filelist 138 if !ok { 139 break 140 } 141 142 finfo, err := os.Stat(filename) 143 if err != nil { 144 state.Put("error", fmt.Errorf("Error trying to stat : %s : %s", filename, err)) 145 return multistep.ActionHalt 146 } 147 148 // walk through directory adding files to the root of the fs 149 if finfo.IsDir() { 150 ui.Message(fmt.Sprintf("Copying directory: %s", filename)) 151 152 err := filepath.Walk(filename, crawlDirectory) 153 if err != nil { 154 state.Put("error", fmt.Errorf("Error adding file from floppy_files : %s : %s", filename, err)) 155 return multistep.ActionHalt 156 } 157 158 for _, crawlfilename := range crawlDirectoryFiles { 159 if err = s.Add(cache, crawlfilename); err != nil { 160 state.Put("error", fmt.Errorf("Error adding file from floppy_files : %s : %s", filename, err)) 161 return multistep.ActionHalt 162 } 163 s.FilesAdded[crawlfilename] = true 164 } 165 166 crawlDirectoryFiles = []string{} 167 continue 168 } 169 170 // add just a single file 171 ui.Message(fmt.Sprintf("Copying file: %s", filename)) 172 if err = s.Add(cache, filename); err != nil { 173 state.Put("error", fmt.Errorf("Error adding file from floppy_files : %s : %s", filename, err)) 174 return multistep.ActionHalt 175 } 176 s.FilesAdded[filename] = true 177 } 178 ui.Message("Done copying files from floppy_files") 179 180 // Collect all paths (expanding wildcards) into pathqueue 181 ui.Message("Collecting paths from floppy_dirs") 182 var pathqueue []string 183 for _, filename := range s.Directories { 184 if strings.ContainsAny(filename, "*?[") { 185 matches, err := filepath.Glob(filename) 186 if err != nil { 187 state.Put("error", fmt.Errorf("Error adding path %s to floppy: %s", filename, err)) 188 return multistep.ActionHalt 189 } 190 191 for _, filename := range matches { 192 pathqueue = append(pathqueue, filename) 193 } 194 continue 195 } 196 pathqueue = append(pathqueue, filename) 197 } 198 ui.Message(fmt.Sprintf("Resulting paths from floppy_dirs : %v", pathqueue)) 199 200 // Go over each path in pathqueue and copy it. 201 for _, src := range pathqueue { 202 ui.Message(fmt.Sprintf("Recursively copying : %s", src)) 203 err = s.Add(cache, src) 204 if err != nil { 205 state.Put("error", fmt.Errorf("Error adding path %s to floppy: %s", src, err)) 206 return multistep.ActionHalt 207 } 208 } 209 ui.Message("Done copying paths from floppy_dirs") 210 211 // Set the path to the floppy so it can be used later 212 state.Put("floppy_path", s.floppyPath) 213 214 return multistep.ActionContinue 215 } 216 217 func (s *StepCreateFloppy) Add(dircache directoryCache, src string) error { 218 finfo, err := os.Stat(src) 219 if err != nil { 220 return fmt.Errorf("Error adding path to floppy: %s", err) 221 } 222 223 // add a file 224 if !finfo.IsDir() { 225 inputF, err := os.Open(src) 226 if err != nil { 227 return err 228 } 229 defer inputF.Close() 230 231 d, err := dircache("") 232 if err != nil { 233 return err 234 } 235 236 entry, err := d.AddFile(path.Base(filepath.ToSlash(src))) 237 if err != nil { 238 return err 239 } 240 241 fatFile, err := entry.File() 242 if err != nil { 243 return err 244 } 245 246 _, err = io.Copy(fatFile, inputF) 247 s.FilesAdded[src] = true 248 return err 249 } 250 251 // add a directory and it's subdirectories 252 basedirectory := filepath.Join(src, "..") 253 visit := func(pathname string, fi os.FileInfo, err error) error { 254 if err != nil { 255 return err 256 } 257 if fi.Mode().IsDir() { 258 base, err := removeBase(basedirectory, pathname) 259 if err != nil { 260 return err 261 } 262 _, err = dircache(filepath.ToSlash(base)) 263 return err 264 } 265 directory, filename := filepath.Split(filepath.ToSlash(pathname)) 266 267 base, err := removeBase(basedirectory, filepath.FromSlash(directory)) 268 if err != nil { 269 return err 270 } 271 272 inputF, err := os.Open(pathname) 273 if err != nil { 274 return err 275 } 276 defer inputF.Close() 277 278 wd, err := dircache(filepath.ToSlash(base)) 279 if err != nil { 280 return err 281 } 282 283 entry, err := wd.AddFile(filename) 284 if err != nil { 285 return err 286 } 287 288 fatFile, err := entry.File() 289 if err != nil { 290 return err 291 } 292 293 _, err = io.Copy(fatFile, inputF) 294 s.FilesAdded[pathname] = true 295 return err 296 } 297 298 return filepath.Walk(src, visit) 299 } 300 301 func (s *StepCreateFloppy) Cleanup(multistep.StateBag) { 302 if s.floppyPath != "" { 303 log.Printf("Deleting floppy disk: %s", s.floppyPath) 304 os.Remove(s.floppyPath) 305 } 306 } 307 308 // removeBase will take a regular os.PathSeparator-separated path and remove the 309 // prefix directory base from it. Both paths are converted to their absolute 310 // formats before the stripping takes place. 311 func removeBase(base string, path string) (string, error) { 312 var idx int 313 var err error 314 315 if res, err := filepath.Abs(path); err == nil { 316 path = res 317 } 318 path = filepath.Clean(path) 319 320 if base, err = filepath.Abs(base); err != nil { 321 return path, err 322 } 323 324 c1, c2 := strings.Split(base, string(os.PathSeparator)), strings.Split(path, string(os.PathSeparator)) 325 for idx = 0; idx < len(c1); idx++ { 326 if len(c1[idx]) == 0 && len(c2[idx]) != 0 { 327 break 328 } 329 if c1[idx] != c2[idx] { 330 return "", fmt.Errorf("Path %s is not prefixed by Base %s", path, base) 331 } 332 } 333 return strings.Join(c2[idx:], string(os.PathSeparator)), nil 334 } 335 336 // fsDirectoryCache returns a function that can be used to grab the fs.Directory 337 // entry associated with a given path. If an fs.Directory entry is not found 338 // then it will be created relative to the rootDirectory argument that is 339 // passed. 340 type directoryCache func(string) (fs.Directory, error) 341 342 func fsDirectoryCache(rootDirectory fs.Directory) directoryCache { 343 var cache map[string]fs.Directory 344 345 cache = make(map[string]fs.Directory) 346 cache[""] = rootDirectory 347 348 Input, Output, Error := make(chan string), make(chan fs.Directory), make(chan error) 349 go func(Error chan error) { 350 for { 351 input := <-Input 352 if len(input) > 0 { 353 input = path.Clean(input) 354 } 355 356 // found a directory, so yield it 357 res, ok := cache[input] 358 if ok { 359 Output <- res 360 continue 361 } 362 component := strings.Split(input, "/") 363 364 // directory not cached, so start at the root and walk each component 365 // creating them if they're not in cache 366 var entry fs.Directory 367 for i := range component { 368 369 // join all of our components into a key 370 path := strings.Join(component[:i], "/") 371 372 // check if parent directory is cached 373 res, ok = cache[path] 374 if !ok { 375 // add directory into cache 376 directory, err := entry.AddDirectory(component[i-1]) 377 if err != nil { 378 Error <- err 379 continue 380 } 381 res, err = directory.Dir() 382 if err != nil { 383 Error <- err 384 continue 385 } 386 cache[path] = res 387 } 388 // cool, found a directory 389 entry = res 390 } 391 392 // finally create our directory 393 directory, err := entry.AddDirectory(component[len(component)-1]) 394 if err != nil { 395 Error <- err 396 continue 397 } 398 res, err = directory.Dir() 399 if err != nil { 400 Error <- err 401 continue 402 } 403 cache[input] = res 404 405 // ..and yield it 406 Output <- entry 407 } 408 }(Error) 409 410 getFilesystemDirectory := func(input string) (fs.Directory, error) { 411 Input <- input 412 select { 413 case res := <-Output: 414 return res, nil 415 case err := <-Error: 416 return *new(fs.Directory), err 417 } 418 } 419 return getFilesystemDirectory 420 }