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