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