github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/snap/pack/pack.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2014-2017 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 pack 21 22 import ( 23 "fmt" 24 "io" 25 "io/ioutil" 26 "os" 27 "path/filepath" 28 29 "github.com/snapcore/snapd/gadget" 30 "github.com/snapcore/snapd/kernel" 31 "github.com/snapcore/snapd/logger" 32 "github.com/snapcore/snapd/snap" 33 "github.com/snapcore/snapd/snap/snapdir" 34 "github.com/snapcore/snapd/snap/squashfs" 35 ) 36 37 // this could be shipped as a file like "info", and save on the memory and the 38 // overhead of creating and removing the tempfile, but on darwin we can't AFAIK 39 // easily know where it's been placed. As it's not really that big, just 40 // shipping it in memory seems fine for now. 41 // from click's click.build.ClickBuilderBase, and there from 42 // @Dpkg::Source::Package::tar_ignore_default_pattern; 43 // changed to match squashfs's "-wildcards" syntax 44 // 45 // for anchored vs non-anchored syntax see RELEASE_README in squashfs-tools. 46 const excludesContent = ` 47 # "anchored", only match at top level 48 DEBIAN 49 .arch-ids 50 .arch-inventory 51 .bzr 52 .bzr-builddeb 53 .bzr.backup 54 .bzr.tags 55 .bzrignore 56 .cvsignore 57 .git 58 .gitattributes 59 .gitignore 60 .gitmodules 61 .hg 62 .hgignore 63 .hgsigs 64 .hgtags 65 .shelf 66 .svn 67 CVS 68 DEADJOE 69 RCS 70 _MTN 71 _darcs 72 {arch} 73 .snapignore 74 75 # "non-anchored", match anywhere 76 ... .[#~]* 77 ... *.snap 78 ... *.click 79 ... .*.sw? 80 ... *~ 81 ... ,,* 82 ` 83 84 // small helper that returns the architecture of the snap, or "multi" if it's multiple arches 85 func debArchitecture(info *snap.Info) string { 86 switch len(info.Architectures) { 87 case 0: 88 return "all" 89 case 1: 90 return info.Architectures[0] 91 default: 92 return "multi" 93 } 94 } 95 96 // CheckSkeleton attempts to validate snap data in source directory 97 func CheckSkeleton(w io.Writer, sourceDir string) error { 98 info, err := loadAndValidate(sourceDir) 99 if err == nil { 100 snap.SanitizePlugsSlots(info) 101 if len(info.BadInterfaces) > 0 { 102 fmt.Fprintln(w, snap.BadInterfacesSummary(info)) 103 } 104 } 105 return err 106 } 107 108 func loadAndValidate(sourceDir string) (*snap.Info, error) { 109 // ensure we have valid content 110 yaml, err := ioutil.ReadFile(filepath.Join(sourceDir, "meta", "snap.yaml")) 111 if err != nil { 112 return nil, err 113 } 114 115 info, err := snap.InfoFromSnapYaml(yaml) 116 if err != nil { 117 return nil, err 118 } 119 120 if err := snap.Validate(info); err != nil { 121 return nil, fmt.Errorf("cannot validate snap %q: %v", info.InstanceName(), err) 122 } 123 124 if err := snap.ValidateContainer(snapdir.New(sourceDir), info, logger.Noticef); err != nil { 125 return nil, err 126 } 127 128 if info.SnapType == snap.TypeGadget { 129 // TODO:UC20: optionally pass model 130 // TODO:UC20: pass validation constraints which indicate intent 131 // to have data encrypted 132 ginfo, err := gadget.ReadInfoAndValidate(sourceDir, nil, nil) 133 if err != nil { 134 return nil, err 135 } 136 if err := gadget.ValidateContent(ginfo, sourceDir, ""); err != nil { 137 return nil, err 138 } 139 } 140 if info.SnapType == snap.TypeKernel { 141 if err := kernel.Validate(sourceDir); err != nil { 142 return nil, err 143 } 144 } 145 146 return info, nil 147 } 148 149 func snapPath(info *snap.Info, targetDir, snapName string) string { 150 if snapName == "" { 151 snapName = fmt.Sprintf("%s_%s_%v.snap", info.InstanceName(), info.Version, debArchitecture(info)) 152 } 153 if targetDir != "" && !filepath.IsAbs(snapName) { 154 snapName = filepath.Join(targetDir, snapName) 155 } 156 return snapName 157 } 158 159 func prepare(sourceDir, targetDir string) (*snap.Info, error) { 160 info, err := loadAndValidate(sourceDir) 161 if err != nil { 162 return nil, err 163 } 164 165 if targetDir != "" { 166 if err := os.MkdirAll(targetDir, 0755); err != nil { 167 return nil, err 168 } 169 } 170 171 return info, nil 172 } 173 174 func excludesFile() (filename string, err error) { 175 tmpf, err := ioutil.TempFile("", ".snap-pack-exclude-") 176 if err != nil { 177 return "", err 178 } 179 180 // inspited by ioutil.WriteFile 181 n, err := tmpf.Write([]byte(excludesContent)) 182 if err == nil && n < len(excludesContent) { 183 err = io.ErrShortWrite 184 } 185 186 if err1 := tmpf.Close(); err == nil { 187 err = err1 188 } 189 190 if err == nil { 191 filename = tmpf.Name() 192 } 193 194 return filename, err 195 } 196 197 type Options struct { 198 // TargetDir is the direction where the snap file will be placed, or empty 199 // to use the current directory 200 TargetDir string 201 // SnapName is the name of the snap file, or empty to use the default name 202 // which is <snapname>_<version>_<architecture>.snap 203 SnapName string 204 // Compression method to use 205 Compression string 206 } 207 208 var Defaults *Options = nil 209 210 // Snap the given sourceDirectory and return the generated 211 // snap file 212 func Snap(sourceDir string, opts *Options) (string, error) { 213 if opts == nil { 214 opts = &Options{} 215 } 216 switch opts.Compression { 217 case "xz", "lzo", "": 218 // fine 219 default: 220 return "", fmt.Errorf("cannot use compression %q", opts.Compression) 221 } 222 223 info, err := prepare(sourceDir, opts.TargetDir) 224 if err != nil { 225 return "", err 226 } 227 228 excludes, err := excludesFile() 229 if err != nil { 230 return "", err 231 } 232 defer os.Remove(excludes) 233 234 snapName := snapPath(info, opts.TargetDir, opts.SnapName) 235 d := squashfs.New(snapName) 236 if err = d.Build(sourceDir, &squashfs.BuildOpts{ 237 SnapType: string(info.Type()), 238 Compression: opts.Compression, 239 ExcludeFiles: []string{excludes}, 240 }); err != nil { 241 return "", err 242 } 243 244 return snapName, nil 245 }