github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/gadget/internal/mkfs.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2019 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package internal
    21  
    22  import (
    23  	"fmt"
    24  	"io/ioutil"
    25  	"os"
    26  	"os/exec"
    27  	"path/filepath"
    28  
    29  	"github.com/snapcore/snapd/osutil"
    30  )
    31  
    32  type MkfsFunc func(imgFile, label, contentsRootDir string) error
    33  
    34  var (
    35  	mkfsHandlers = map[string]MkfsFunc{
    36  		"vfat": mkfsVfat,
    37  		"ext4": mkfsExt4,
    38  	}
    39  )
    40  
    41  // Mkfs creates a filesystem of given type and provided label in the device or file.
    42  func Mkfs(typ, img, label string) error {
    43  	return MkfsWithContent(typ, img, label, "")
    44  }
    45  
    46  // Mkfs creates a filesystem of given type and provided label in the device or file.
    47  // The filesystem is populated with contents of contentRootDir.
    48  func MkfsWithContent(typ, img, label, contentRootDir string) error {
    49  	h, ok := mkfsHandlers[typ]
    50  	if !ok {
    51  		return fmt.Errorf("cannot create unsupported filesystem %q", typ)
    52  	}
    53  	return h(img, label, contentRootDir)
    54  }
    55  
    56  // mkfsExt4 creates an EXT4 filesystem in given image file, with an optional
    57  // filesystem label, and populates it with the contents of provided root
    58  // directory.
    59  func mkfsExt4(img, label, contentsRootDir string) error {
    60  	// Originally taken from ubuntu-image
    61  	// Switched to use mkfs defaults for https://bugs.launchpad.net/snappy/+bug/1878374
    62  	// For caveats/requirements in case we need support for older systems:
    63  	// https://github.com/snapcore/snapd/pull/6997#discussion_r293967140
    64  	mkfsArgs := []string{
    65  		"mkfs.ext4",
    66  		// default usage type
    67  		"-T", "default",
    68  	}
    69  	if contentsRootDir != "" {
    70  		// mkfs.ext4 can populate the filesystem with contents of given
    71  		// root directory
    72  		// TODO: support e2fsprogs 1.42 without -d in Ubuntu 16.04
    73  		mkfsArgs = append(mkfsArgs, "-d", contentsRootDir)
    74  	}
    75  	if label != "" {
    76  		mkfsArgs = append(mkfsArgs, "-L", label)
    77  	}
    78  	mkfsArgs = append(mkfsArgs, img)
    79  
    80  	var cmd *exec.Cmd
    81  	if os.Geteuid() != 0 {
    82  		// run through fakeroot so that files are owned by root
    83  		cmd = exec.Command("fakeroot", mkfsArgs...)
    84  	} else {
    85  		// no need to fake it if we're already root
    86  		cmd = exec.Command(mkfsArgs[0], mkfsArgs[1:]...)
    87  	}
    88  	out, err := cmd.CombinedOutput()
    89  	if err != nil {
    90  		return osutil.OutputErr(out, err)
    91  	}
    92  	return nil
    93  }
    94  
    95  // mkfsVfat creates a VFAT filesystem in given image file, with an optional
    96  // filesystem label, and populates it with the contents of provided root
    97  // directory.
    98  func mkfsVfat(img, label, contentsRootDir string) error {
    99  	// taken from ubuntu-image
   100  	mkfsArgs := []string{
   101  		// 512B logical sector size
   102  		"-S", "512",
   103  		// 1 sector per cluster
   104  		"-s", "1",
   105  		// 32b FAT size
   106  		"-F", "32",
   107  	}
   108  	if label != "" {
   109  		mkfsArgs = append(mkfsArgs, "-n", label)
   110  	}
   111  	mkfsArgs = append(mkfsArgs, img)
   112  
   113  	cmd := exec.Command("mkfs.vfat", mkfsArgs...)
   114  	out, err := cmd.CombinedOutput()
   115  	if err != nil {
   116  		return osutil.OutputErr(out, err)
   117  	}
   118  
   119  	// if there is no content to copy we are done now
   120  	if contentsRootDir == "" {
   121  		return nil
   122  	}
   123  
   124  	// mkfs.vfat does not know how to populate the filesystem with contents,
   125  	// we need to do the work ourselves
   126  
   127  	fis, err := ioutil.ReadDir(contentsRootDir)
   128  	if err != nil {
   129  		return fmt.Errorf("cannot list directory contents: %v", err)
   130  	}
   131  	if len(fis) == 0 {
   132  		// nothing to copy to the image
   133  		return nil
   134  	}
   135  
   136  	mcopyArgs := make([]string, 0, 4+len(fis))
   137  	mcopyArgs = append(mcopyArgs,
   138  		// recursive copy
   139  		"-s",
   140  		// image file
   141  		"-i", img)
   142  	for _, fi := range fis {
   143  		mcopyArgs = append(mcopyArgs, filepath.Join(contentsRootDir, fi.Name()))
   144  	}
   145  	mcopyArgs = append(mcopyArgs,
   146  		// place content at the / of the filesystem
   147  		"::")
   148  
   149  	cmd = exec.Command("mcopy", mcopyArgs...)
   150  	cmd.Env = os.Environ()
   151  	// skip mtools checks to avoid unnecessary warnings
   152  	cmd.Env = append(cmd.Env, "MTOOLS_SKIP_CHECK=1")
   153  
   154  	out, err = cmd.CombinedOutput()
   155  	if err != nil {
   156  		return fmt.Errorf("cannot populate vfat filesystem with contents: %v", osutil.OutputErr(out, err))
   157  	}
   158  	return nil
   159  }