blob: 3f9af83f242abeadc354fead8fbc59d06a6ad848 [file] [log] [blame]
Jiri Simsad7616c92015-03-24 23:44:30 -07001// Copyright 2015 The Vanadium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
Robin Thellende2627892014-11-26 09:34:37 -08005// Package packages provides functionality to install ZIP and TAR packages.
6package packages
7
8import (
9 "archive/tar"
10 "archive/zip"
11 "compress/bzip2"
12 "compress/gzip"
13 "encoding/json"
Robin Thellende2627892014-11-26 09:34:37 -080014 "io"
15 "io/ioutil"
16 "os"
17 "path/filepath"
18 "strings"
19
Todd Wang94c9d0b2015-04-01 14:27:00 -070020 "v.io/v23/services/repository"
Mike Burrows249cbc02015-03-16 10:51:44 -070021 "v.io/v23/verror"
Robin Thellende2627892014-11-26 09:34:37 -080022)
23
Bogdan Capritad2cdd532015-02-25 11:51:19 -080024const (
25 defaultType = "application/octet-stream"
Robin Thellend77c3d832015-03-04 14:10:18 -080026 createDirMode = 0755
27 createFileMode = 0644
Bogdan Capritad2cdd532015-02-25 11:51:19 -080028)
Robin Thellende2627892014-11-26 09:34:37 -080029
30var typemap = map[string]repository.MediaInfo{
31 ".zip": repository.MediaInfo{Type: "application/zip"},
32 ".tar": repository.MediaInfo{Type: "application/x-tar"},
33 ".tgz": repository.MediaInfo{Type: "application/x-tar", Encoding: "gzip"},
34 ".tar.gz": repository.MediaInfo{Type: "application/x-tar", Encoding: "gzip"},
35 ".tbz2": repository.MediaInfo{Type: "application/x-tar", Encoding: "bzip2"},
36 ".tb2": repository.MediaInfo{Type: "application/x-tar", Encoding: "bzip2"},
37 ".tbz": repository.MediaInfo{Type: "application/x-tar", Encoding: "bzip2"},
38 ".tar.bz2": repository.MediaInfo{Type: "application/x-tar", Encoding: "bzip2"},
39}
40
Todd Wang5fc36442015-04-07 15:15:27 -070041const pkgPath = "v.io/x/ref/services/internal/packages"
Mike Burrows249cbc02015-03-16 10:51:44 -070042
43var (
44 errBadMediaType = verror.Register(pkgPath+".errBadMediaType", verror.NoRetry, "{1:}{2:} unsupported media type{:_}")
45 errMkDirFailed = verror.Register(pkgPath+".errMkDirFailed", verror.NoRetry, "{1:}{2:} os.Mkdir({3}) failed{:_}")
46 errFailedToExtract = verror.Register(pkgPath+".errFailedToExtract", verror.NoRetry, "{1:}{2:} failed to extract file {3} outside of install directory{:_}")
47 errBadFileSize = verror.Register(pkgPath+".errBadFileSize", verror.NoRetry, "{1:}{2:} file size doesn't match for {3}: {4} != {5}{:_}")
48 errBadEncoding = verror.Register(pkgPath+".errBadEncoding", verror.NoRetry, "{1:}{2:} unsupported encoding{:_}")
49)
50
Robin Thellende2627892014-11-26 09:34:37 -080051// MediaInfoForFileName returns the MediaInfo based on the file's extension.
52func MediaInfoForFileName(fileName string) repository.MediaInfo {
53 fileName = strings.ToLower(fileName)
54 for k, v := range typemap {
55 if strings.HasSuffix(fileName, k) {
56 return v
57 }
58 }
59 return repository.MediaInfo{Type: defaultType}
60}
61
Bogdan Caprita2751d672015-02-06 13:43:47 -080062func copyFile(src, dst string) error {
63 s, err := os.Open(src)
64 if err != nil {
65 return err
66 }
67 defer s.Close()
Robin Thellend77c3d832015-03-04 14:10:18 -080068 d, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY, createFileMode)
Bogdan Caprita2751d672015-02-06 13:43:47 -080069 if err != nil {
70 return err
71 }
72 defer d.Close()
73 if _, err = io.Copy(d, s); err != nil {
74 return err
75 }
76 return d.Sync()
77}
78
79// Install installs a package in the given destination. If the package is a TAR
80// or ZIP archive, the destination becomes a directory where the archive content
81// is extracted. Otherwise, the package file itself is copied to the
82// destination.
83func Install(pkgFile, destination string) error {
Robin Thellende2627892014-11-26 09:34:37 -080084 mediaInfo, err := LoadMediaInfo(pkgFile)
85 if err != nil {
86 return err
87 }
88 switch mediaInfo.Type {
89 case "application/x-tar":
Bogdan Caprita2751d672015-02-06 13:43:47 -080090 return extractTar(pkgFile, mediaInfo.Encoding, destination)
Robin Thellende2627892014-11-26 09:34:37 -080091 case "application/zip":
Bogdan Caprita2751d672015-02-06 13:43:47 -080092 return extractZip(pkgFile, destination)
93 case defaultType:
94 return copyFile(pkgFile, destination)
Robin Thellende2627892014-11-26 09:34:37 -080095 default:
Mike Burrows249cbc02015-03-16 10:51:44 -070096 return verror.New(errBadMediaType, nil, mediaInfo.Type)
Robin Thellende2627892014-11-26 09:34:37 -080097 }
98}
99
100// LoadMediaInfo returns the MediaInfo for the given package file.
101func LoadMediaInfo(pkgFile string) (repository.MediaInfo, error) {
102 jInfo, err := ioutil.ReadFile(pkgFile + ".__info")
103 if err != nil {
104 return repository.MediaInfo{}, err
105 }
106 var info repository.MediaInfo
107 if err := json.Unmarshal(jInfo, &info); err != nil {
108 return repository.MediaInfo{}, err
109 }
110 return info, nil
111}
112
113// SaveMediaInfo saves the media info for a package.
114func SaveMediaInfo(pkgFile string, mediaInfo repository.MediaInfo) error {
115 jInfo, err := json.Marshal(mediaInfo)
116 if err != nil {
117 return err
118 }
119 infoFile := pkgFile + ".__info"
120 if err := ioutil.WriteFile(infoFile, jInfo, os.FileMode(0600)); err != nil {
121 return err
122 }
123 return nil
124}
125
Robin Thellend875f2592014-12-02 10:29:37 -0800126// CreateZip creates a package from the files in the source directory. The
127// created package is a Zip file.
128func CreateZip(zipFile, sourceDir string) error {
129 z, err := os.OpenFile(zipFile, os.O_CREATE|os.O_WRONLY, os.FileMode(0644))
130 if err != nil {
131 return err
132 }
133 defer z.Close()
134 w := zip.NewWriter(z)
135 if err := filepath.Walk(sourceDir, func(path string, info os.FileInfo, err error) error {
136 if err != nil {
137 return err
138 }
139 if sourceDir == path {
140 return nil
141 }
142 fh, err := zip.FileInfoHeader(info)
143 if err != nil {
144 return err
145 }
146 fh.Name, _ = filepath.Rel(sourceDir, path)
147 hdr, err := w.CreateHeader(fh)
148 if err != nil {
149 return err
150 }
151 if !info.IsDir() {
152 content, err := ioutil.ReadFile(path)
153 if err != nil {
154 return err
155 }
156 if _, err = hdr.Write(content); err != nil {
157 return err
158 }
159 }
160 return nil
161 }); err != nil {
162 return err
163 }
164 if err := w.Close(); err != nil {
165 return err
166 }
167 if err := SaveMediaInfo(zipFile, repository.MediaInfo{Type: "application/zip"}); err != nil {
168 return err
169 }
170 return nil
171}
172
Robin Thellende2627892014-11-26 09:34:37 -0800173func extractZip(zipFile, installDir string) error {
Robin Thellend77c3d832015-03-04 14:10:18 -0800174 if err := os.Mkdir(installDir, os.FileMode(createDirMode)); err != nil {
Mike Burrows249cbc02015-03-16 10:51:44 -0700175 return verror.New(errMkDirFailed, nil, installDir, err)
Bogdan Caprita2751d672015-02-06 13:43:47 -0800176 }
Robin Thellende2627892014-11-26 09:34:37 -0800177 zr, err := zip.OpenReader(zipFile)
178 if err != nil {
179 return err
180 }
181 for _, file := range zr.File {
182 fi := file.FileInfo()
183 name := filepath.Join(installDir, file.Name)
184 if !strings.HasPrefix(name, installDir) {
Mike Burrows249cbc02015-03-16 10:51:44 -0700185 return verror.New(errFailedToExtract, nil, file.Name)
Robin Thellende2627892014-11-26 09:34:37 -0800186 }
187 if fi.IsDir() {
Robin Thellend77c3d832015-03-04 14:10:18 -0800188 if err := os.MkdirAll(name, os.FileMode(createDirMode)); err != nil && !os.IsExist(err) {
Robin Thellende2627892014-11-26 09:34:37 -0800189 return err
190 }
191 continue
192 }
193 in, err := file.Open()
194 if err != nil {
195 return err
196 }
197 parentName := filepath.Dir(name)
Robin Thellend77c3d832015-03-04 14:10:18 -0800198 if err := os.MkdirAll(parentName, os.FileMode(createDirMode)); err != nil {
Robin Thellende2627892014-11-26 09:34:37 -0800199 return err
200 }
Robin Thellend77c3d832015-03-04 14:10:18 -0800201 out, err := os.OpenFile(name, os.O_CREATE|os.O_WRONLY, os.FileMode(createFileMode))
Robin Thellende2627892014-11-26 09:34:37 -0800202 if err != nil {
203 in.Close()
204 return err
205 }
206 nbytes, err := io.Copy(out, in)
207 in.Close()
208 out.Close()
209 if err != nil {
210 return err
211 }
212 if nbytes != fi.Size() {
Mike Burrows249cbc02015-03-16 10:51:44 -0700213 return verror.New(errBadFileSize, nil, fi.Name(), nbytes, fi.Size())
Robin Thellende2627892014-11-26 09:34:37 -0800214 }
215 }
216 return nil
217}
218
Bogdan Caprita2751d672015-02-06 13:43:47 -0800219func extractTar(pkgFile string, encoding string, installDir string) error {
Robin Thellend77c3d832015-03-04 14:10:18 -0800220 if err := os.Mkdir(installDir, os.FileMode(createDirMode)); err != nil {
Mike Burrows249cbc02015-03-16 10:51:44 -0700221 return verror.New(errMkDirFailed, nil, installDir, err)
Bogdan Caprita2751d672015-02-06 13:43:47 -0800222 }
Robin Thellende2627892014-11-26 09:34:37 -0800223 f, err := os.Open(pkgFile)
224 if err != nil {
225 return err
226 }
227 defer f.Close()
228
229 var reader io.Reader
Bogdan Caprita2751d672015-02-06 13:43:47 -0800230 switch encoding {
Robin Thellende2627892014-11-26 09:34:37 -0800231 case "":
232 reader = f
233 case "gzip":
234 var err error
235 if reader, err = gzip.NewReader(f); err != nil {
236 return err
237 }
238 case "bzip2":
239 reader = bzip2.NewReader(f)
240 default:
Mike Burrows249cbc02015-03-16 10:51:44 -0700241 return verror.New(errBadEncoding, nil, encoding)
Robin Thellende2627892014-11-26 09:34:37 -0800242 }
243
244 tr := tar.NewReader(reader)
245 for {
246 hdr, err := tr.Next()
247 if err == io.EOF {
248 return nil
249 }
250 if err != nil {
251 return err
252 }
253 name := filepath.Join(installDir, hdr.Name)
254 if !strings.HasPrefix(name, installDir) {
Mike Burrows249cbc02015-03-16 10:51:44 -0700255 return verror.New(errFailedToExtract, nil, hdr.Name)
Robin Thellende2627892014-11-26 09:34:37 -0800256 }
257 // Regular file
258 if hdr.Typeflag == tar.TypeReg {
Robin Thellend77c3d832015-03-04 14:10:18 -0800259 out, err := os.OpenFile(name, os.O_CREATE|os.O_WRONLY, os.FileMode(createFileMode))
Robin Thellende2627892014-11-26 09:34:37 -0800260 if err != nil {
261 return err
262 }
263 nbytes, err := io.Copy(out, tr)
264 out.Close()
265 if err != nil {
266 return err
267 }
268 if nbytes != hdr.Size {
Mike Burrows249cbc02015-03-16 10:51:44 -0700269 return verror.New(errBadFileSize, nil, hdr.Name, nbytes, hdr.Size)
Robin Thellende2627892014-11-26 09:34:37 -0800270 }
271 continue
272 }
273 // Directory
274 if hdr.Typeflag == tar.TypeDir {
Robin Thellend77c3d832015-03-04 14:10:18 -0800275 if err := os.Mkdir(name, os.FileMode(createDirMode)); err != nil && !os.IsExist(err) {
Robin Thellende2627892014-11-26 09:34:37 -0800276 return err
277 }
278 continue
279 }
280 // Skip unsupported types
281 // TODO(rthellend): Consider adding support for Symlink.
282 }
283}