github.com/aclaygray/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  }