github.com/amanya/packer@v0.12.1-0.20161117214323-902ac5ab2eb6/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  }