github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/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/gadget/quantity"
    30  	"github.com/snapcore/snapd/osutil"
    31  )
    32  
    33  type MkfsFunc func(imgFile, label, contentsRootDir string, deviceSize quantity.Size) error
    34  
    35  var (
    36  	mkfsHandlers = map[string]MkfsFunc{
    37  		"vfat": mkfsVfat,
    38  		"ext4": mkfsExt4,
    39  	}
    40  )
    41  
    42  // Mkfs creates a filesystem of given type and provided label in the device or
    43  // file. The device size provides hints for additional tuning of the created
    44  // filesystem.
    45  func Mkfs(typ, img, label string, deviceSize quantity.Size) error {
    46  	return MkfsWithContent(typ, img, label, "", deviceSize)
    47  }
    48  
    49  // Mkfs creates a filesystem of given type and provided label in the device or
    50  // file. The filesystem is populated with contents of contentRootDir. The device
    51  // size provides hints for additional tuning of the created filesystem.
    52  func MkfsWithContent(typ, img, label, contentRootDir string, deviceSize quantity.Size) error {
    53  	h, ok := mkfsHandlers[typ]
    54  	if !ok {
    55  		return fmt.Errorf("cannot create unsupported filesystem %q", typ)
    56  	}
    57  	return h(img, label, contentRootDir, deviceSize)
    58  }
    59  
    60  // mkfsExt4 creates an EXT4 filesystem in given image file, with an optional
    61  // filesystem label, and populates it with the contents of provided root
    62  // directory.
    63  func mkfsExt4(img, label, contentsRootDir string, deviceSize quantity.Size) error {
    64  	// Originally taken from ubuntu-image
    65  	// Switched to use mkfs defaults for https://bugs.launchpad.net/snappy/+bug/1878374
    66  	// For caveats/requirements in case we need support for older systems:
    67  	// https://github.com/snapcore/snapd/pull/6997#discussion_r293967140
    68  	mkfsArgs := []string{"mkfs.ext4"}
    69  	const size32MiB = 32 * quantity.SizeMiB
    70  	if deviceSize != 0 && deviceSize <= size32MiB {
    71  		// With the default of 4096 bytes, the minimal journal size is
    72  		// 4M, meaning we loose a lot of usable space. Try to follow the
    73  		// e2fsprogs upstream and use a 1k block size for smaller
    74  		// filesystems, note that this may cause issues like
    75  		// https://bugs.launchpad.net/ubuntu/+source/lvm2/+bug/1817097
    76  		// if one migrates the filesystem to a device with a different
    77  		// block size
    78  		mkfsArgs = append(mkfsArgs, "-b", "1024")
    79  	}
    80  	if contentsRootDir != "" {
    81  		// mkfs.ext4 can populate the filesystem with contents of given
    82  		// root directory
    83  		// TODO: support e2fsprogs 1.42 without -d in Ubuntu 16.04
    84  		mkfsArgs = append(mkfsArgs, "-d", contentsRootDir)
    85  	}
    86  	if label != "" {
    87  		mkfsArgs = append(mkfsArgs, "-L", label)
    88  	}
    89  	mkfsArgs = append(mkfsArgs, img)
    90  
    91  	var cmd *exec.Cmd
    92  	if os.Geteuid() != 0 {
    93  		// run through fakeroot so that files are owned by root
    94  		cmd = exec.Command("fakeroot", mkfsArgs...)
    95  	} else {
    96  		// no need to fake it if we're already root
    97  		cmd = exec.Command(mkfsArgs[0], mkfsArgs[1:]...)
    98  	}
    99  	out, err := cmd.CombinedOutput()
   100  	if err != nil {
   101  		return osutil.OutputErr(out, err)
   102  	}
   103  	return nil
   104  }
   105  
   106  // mkfsVfat creates a VFAT filesystem in given image file, with an optional
   107  // filesystem label, and populates it with the contents of provided root
   108  // directory.
   109  func mkfsVfat(img, label, contentsRootDir string, deviceSize quantity.Size) error {
   110  	// taken from ubuntu-image
   111  	mkfsArgs := []string{
   112  		// 512B logical sector size
   113  		"-S", "512",
   114  		// 1 sector per cluster
   115  		"-s", "1",
   116  		// 32b FAT size
   117  		"-F", "32",
   118  	}
   119  	if label != "" {
   120  		mkfsArgs = append(mkfsArgs, "-n", label)
   121  	}
   122  	mkfsArgs = append(mkfsArgs, img)
   123  
   124  	cmd := exec.Command("mkfs.vfat", mkfsArgs...)
   125  	out, err := cmd.CombinedOutput()
   126  	if err != nil {
   127  		return osutil.OutputErr(out, err)
   128  	}
   129  
   130  	// if there is no content to copy we are done now
   131  	if contentsRootDir == "" {
   132  		return nil
   133  	}
   134  
   135  	// mkfs.vfat does not know how to populate the filesystem with contents,
   136  	// we need to do the work ourselves
   137  
   138  	fis, err := ioutil.ReadDir(contentsRootDir)
   139  	if err != nil {
   140  		return fmt.Errorf("cannot list directory contents: %v", err)
   141  	}
   142  	if len(fis) == 0 {
   143  		// nothing to copy to the image
   144  		return nil
   145  	}
   146  
   147  	mcopyArgs := make([]string, 0, 4+len(fis))
   148  	mcopyArgs = append(mcopyArgs,
   149  		// recursive copy
   150  		"-s",
   151  		// image file
   152  		"-i", img)
   153  	for _, fi := range fis {
   154  		mcopyArgs = append(mcopyArgs, filepath.Join(contentsRootDir, fi.Name()))
   155  	}
   156  	mcopyArgs = append(mcopyArgs,
   157  		// place content at the / of the filesystem
   158  		"::")
   159  
   160  	cmd = exec.Command("mcopy", mcopyArgs...)
   161  	cmd.Env = os.Environ()
   162  	// skip mtools checks to avoid unnecessary warnings
   163  	cmd.Env = append(cmd.Env, "MTOOLS_SKIP_CHECK=1")
   164  
   165  	out, err = cmd.CombinedOutput()
   166  	if err != nil {
   167  		return fmt.Errorf("cannot populate vfat filesystem with contents: %v", osutil.OutputErr(out, err))
   168  	}
   169  	return nil
   170  }