github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/osutil/mkfs/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 mkfs
    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  // MakeFunc defines a function signature that is used by all of the mkfs.<filesystem>
    34  // functions supported in this package. This is done to allow them to be defined
    35  // in the mkfsHandlers map
    36  type MakeFunc func(imgFile, label, contentsRootDir string, deviceSize, sectorSize quantity.Size) error
    37  
    38  var (
    39  	mkfsHandlers = map[string]MakeFunc{
    40  		"vfat": mkfsVfat,
    41  		"ext4": mkfsExt4,
    42  	}
    43  )
    44  
    45  // Make creates a filesystem of given type and provided label in the device or
    46  // file. The device size and sector size provides hints for additional tuning of
    47  // the created filesystem.
    48  func Make(typ, img, label string, deviceSize, sectorSize quantity.Size) error {
    49  	return MakeWithContent(typ, img, label, "", deviceSize, sectorSize)
    50  }
    51  
    52  // MakeWithContent creates a filesystem of given type and provided label in the
    53  // device or file. The filesystem is populated with contents of contentRootDir.
    54  // The device size provides hints for additional tuning of the created
    55  // filesystem.
    56  func MakeWithContent(typ, img, label, contentRootDir string, deviceSize, sectorSize quantity.Size) error {
    57  	h, ok := mkfsHandlers[typ]
    58  	if !ok {
    59  		return fmt.Errorf("cannot create unsupported filesystem %q", typ)
    60  	}
    61  	return h(img, label, contentRootDir, deviceSize, sectorSize)
    62  }
    63  
    64  // mkfsExt4 creates an EXT4 filesystem in given image file, with an optional
    65  // filesystem label, and populates it with the contents of provided root
    66  // directory.
    67  func mkfsExt4(img, label, contentsRootDir string, deviceSize, sectorSize quantity.Size) error {
    68  	// Originally taken from ubuntu-image
    69  	// Switched to use mkfs defaults for https://bugs.launchpad.net/snappy/+bug/1878374
    70  	// For caveats/requirements in case we need support for older systems:
    71  	// https://github.com/snapcore/snapd/pull/6997#discussion_r293967140
    72  	mkfsArgs := []string{"mkfs.ext4"}
    73  
    74  	const size32MiB = 32 * quantity.SizeMiB
    75  	if deviceSize != 0 && deviceSize <= size32MiB {
    76  		// With the default block size of 4096 bytes, the minimal journal size
    77  		// is 4M, meaning we loose a lot of usable space. Try to follow the
    78  		// e2fsprogs upstream and use a 1k block size for smaller
    79  		// filesystems, note that this may cause issues like
    80  		// https://bugs.launchpad.net/ubuntu/+source/lvm2/+bug/1817097
    81  		// if one migrates the filesystem to a device with a different
    82  		// block size
    83  
    84  		// though note if the sector size was specified (i.e. non-zero) and
    85  		// larger than 1K, then we need to use that, since you can't create
    86  		// a filesystem with a block-size smaller than the sector-size
    87  		// see e2fsprogs source code:
    88  		// https://github.com/tytso/e2fsprogs/blob/0d47f5ab05177c1861f16bb3644a47018e6be1d0/misc/mke2fs.c#L2151-L2156
    89  		defaultSectorSize := 1 * quantity.SizeKiB
    90  		if sectorSize > 1024 {
    91  			defaultSectorSize = sectorSize
    92  		}
    93  		mkfsArgs = append(mkfsArgs, "-b", defaultSectorSize.String())
    94  	}
    95  	if contentsRootDir != "" {
    96  		// mkfs.ext4 can populate the filesystem with contents of given
    97  		// root directory
    98  		// TODO: support e2fsprogs 1.42 without -d in Ubuntu 16.04
    99  		mkfsArgs = append(mkfsArgs, "-d", contentsRootDir)
   100  	}
   101  	if label != "" {
   102  		mkfsArgs = append(mkfsArgs, "-L", label)
   103  	}
   104  	mkfsArgs = append(mkfsArgs, img)
   105  
   106  	var cmd *exec.Cmd
   107  	if os.Geteuid() != 0 {
   108  		// run through fakeroot so that files are owned by root
   109  		cmd = exec.Command("fakeroot", mkfsArgs...)
   110  	} else {
   111  		// no need to fake it if we're already root
   112  		cmd = exec.Command(mkfsArgs[0], mkfsArgs[1:]...)
   113  	}
   114  	out, err := cmd.CombinedOutput()
   115  	if err != nil {
   116  		return osutil.OutputErr(out, err)
   117  	}
   118  	return nil
   119  }
   120  
   121  // mkfsVfat creates a VFAT filesystem in given image file, with an optional
   122  // filesystem label, and populates it with the contents of provided root
   123  // directory.
   124  func mkfsVfat(img, label, contentsRootDir string, deviceSize, sectorSize quantity.Size) error {
   125  	// 512B logical sector size by default, unless the specified sector size is
   126  	// larger than 512, in which case use the sector size
   127  	// mkfs.vfat will automatically increase the block size to the internal
   128  	// sector size of the disk if the specified block size is too small, but
   129  	// be paranoid and always set the block size to that of the sector size if
   130  	// we know the sector size is larger than the default 512 (originally from
   131  	// ubuntu-image). see dosfstools:
   132  	// https://github.com/dosfstools/dosfstools/blob/e579a7df89bb3a6df08847d45c70c8ebfabca7d2/src/mkfs.fat.c#L1892-L1898
   133  	defaultSectorSize := quantity.Size(512)
   134  	if sectorSize > defaultSectorSize {
   135  		defaultSectorSize = sectorSize
   136  	}
   137  	mkfsArgs := []string{
   138  		// options taken from ubuntu-image, except the sector size
   139  		"-S", defaultSectorSize.String(),
   140  		// 1 sector per cluster
   141  		"-s", "1",
   142  		// 32b FAT size
   143  		"-F", "32",
   144  	}
   145  	if label != "" {
   146  		mkfsArgs = append(mkfsArgs, "-n", label)
   147  	}
   148  	mkfsArgs = append(mkfsArgs, img)
   149  
   150  	cmd := exec.Command("mkfs.vfat", mkfsArgs...)
   151  	out, err := cmd.CombinedOutput()
   152  	if err != nil {
   153  		return osutil.OutputErr(out, err)
   154  	}
   155  
   156  	// if there is no content to copy we are done now
   157  	if contentsRootDir == "" {
   158  		return nil
   159  	}
   160  
   161  	// mkfs.vfat does not know how to populate the filesystem with contents,
   162  	// we need to do the work ourselves
   163  
   164  	fis, err := ioutil.ReadDir(contentsRootDir)
   165  	if err != nil {
   166  		return fmt.Errorf("cannot list directory contents: %v", err)
   167  	}
   168  	if len(fis) == 0 {
   169  		// nothing to copy to the image
   170  		return nil
   171  	}
   172  
   173  	mcopyArgs := make([]string, 0, 4+len(fis))
   174  	mcopyArgs = append(mcopyArgs,
   175  		// recursive copy
   176  		"-s",
   177  		// image file
   178  		"-i", img)
   179  	for _, fi := range fis {
   180  		mcopyArgs = append(mcopyArgs, filepath.Join(contentsRootDir, fi.Name()))
   181  	}
   182  	mcopyArgs = append(mcopyArgs,
   183  		// place content at the / of the filesystem
   184  		"::")
   185  
   186  	cmd = exec.Command("mcopy", mcopyArgs...)
   187  	cmd.Env = os.Environ()
   188  	// skip mtools checks to avoid unnecessary warnings
   189  	cmd.Env = append(cmd.Env, "MTOOLS_SKIP_CHECK=1")
   190  
   191  	out, err = cmd.CombinedOutput()
   192  	if err != nil {
   193  		return fmt.Errorf("cannot populate vfat filesystem with contents: %v", osutil.OutputErr(out, err))
   194  	}
   195  	return nil
   196  }