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