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 }