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  }