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 }