blob: 165640131ae999ea3664d8aec832922e9b9b994c [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 }
Bogdan Caprita48bf8c12015-08-10 10:19:43 -0700146 fh.Method = zip.Deflate
Robin Thellend875f2592014-12-02 10:29:37 -0800147 fh.Name, _ = filepath.Rel(sourceDir, path)
148 hdr, err := w.CreateHeader(fh)
149 if err != nil {
150 return err
151 }
152 if !info.IsDir() {
153 content, err := ioutil.ReadFile(path)
154 if err != nil {
155 return err
156 }
157 if _, err = hdr.Write(content); err != nil {
158 return err
159 }
160 }
161 return nil
162 }); err != nil {
163 return err
164 }
165 if err := w.Close(); err != nil {
166 return err
167 }
168 if err := SaveMediaInfo(zipFile, repository.MediaInfo{Type: "application/zip"}); err != nil {
169 return err
170 }
171 return nil
172}
173
Robin Thellende2627892014-11-26 09:34:37 -0800174func extractZip(zipFile, installDir string) error {
Robin Thellend77c3d832015-03-04 14:10:18 -0800175 if err := os.Mkdir(installDir, os.FileMode(createDirMode)); err != nil {
Mike Burrows249cbc02015-03-16 10:51:44 -0700176 return verror.New(errMkDirFailed, nil, installDir, err)
Bogdan Caprita2751d672015-02-06 13:43:47 -0800177 }
Robin Thellende2627892014-11-26 09:34:37 -0800178 zr, err := zip.OpenReader(zipFile)
179 if err != nil {
180 return err
181 }
182 for _, file := range zr.File {
183 fi := file.FileInfo()
184 name := filepath.Join(installDir, file.Name)
185 if !strings.HasPrefix(name, installDir) {
Mike Burrows249cbc02015-03-16 10:51:44 -0700186 return verror.New(errFailedToExtract, nil, file.Name)
Robin Thellende2627892014-11-26 09:34:37 -0800187 }
188 if fi.IsDir() {
Robin Thellend77c3d832015-03-04 14:10:18 -0800189 if err := os.MkdirAll(name, os.FileMode(createDirMode)); err != nil && !os.IsExist(err) {
Robin Thellende2627892014-11-26 09:34:37 -0800190 return err
191 }
192 continue
193 }
194 in, err := file.Open()
195 if err != nil {
196 return err
197 }
198 parentName := filepath.Dir(name)
Robin Thellend77c3d832015-03-04 14:10:18 -0800199 if err := os.MkdirAll(parentName, os.FileMode(createDirMode)); err != nil {
Robin Thellende2627892014-11-26 09:34:37 -0800200 return err
201 }
Robin Thellend77c3d832015-03-04 14:10:18 -0800202 out, err := os.OpenFile(name, os.O_CREATE|os.O_WRONLY, os.FileMode(createFileMode))
Robin Thellende2627892014-11-26 09:34:37 -0800203 if err != nil {
204 in.Close()
205 return err
206 }
207 nbytes, err := io.Copy(out, in)
208 in.Close()
209 out.Close()
210 if err != nil {
211 return err
212 }
213 if nbytes != fi.Size() {
Mike Burrows249cbc02015-03-16 10:51:44 -0700214 return verror.New(errBadFileSize, nil, fi.Name(), nbytes, fi.Size())
Robin Thellende2627892014-11-26 09:34:37 -0800215 }
216 }
217 return nil
218}
219
Bogdan Caprita2751d672015-02-06 13:43:47 -0800220func extractTar(pkgFile string, encoding string, installDir string) error {
Robin Thellend77c3d832015-03-04 14:10:18 -0800221 if err := os.Mkdir(installDir, os.FileMode(createDirMode)); err != nil {
Mike Burrows249cbc02015-03-16 10:51:44 -0700222 return verror.New(errMkDirFailed, nil, installDir, err)
Bogdan Caprita2751d672015-02-06 13:43:47 -0800223 }
Robin Thellende2627892014-11-26 09:34:37 -0800224 f, err := os.Open(pkgFile)
225 if err != nil {
226 return err
227 }
228 defer f.Close()
229
230 var reader io.Reader
Bogdan Caprita2751d672015-02-06 13:43:47 -0800231 switch encoding {
Robin Thellende2627892014-11-26 09:34:37 -0800232 case "":
233 reader = f
234 case "gzip":
235 var err error
236 if reader, err = gzip.NewReader(f); err != nil {
237 return err
238 }
239 case "bzip2":
240 reader = bzip2.NewReader(f)
241 default:
Mike Burrows249cbc02015-03-16 10:51:44 -0700242 return verror.New(errBadEncoding, nil, encoding)
Robin Thellende2627892014-11-26 09:34:37 -0800243 }
244
245 tr := tar.NewReader(reader)
246 for {
247 hdr, err := tr.Next()
248 if err == io.EOF {
249 return nil
250 }
251 if err != nil {
252 return err
253 }
254 name := filepath.Join(installDir, hdr.Name)
255 if !strings.HasPrefix(name, installDir) {
Mike Burrows249cbc02015-03-16 10:51:44 -0700256 return verror.New(errFailedToExtract, nil, hdr.Name)
Robin Thellende2627892014-11-26 09:34:37 -0800257 }
258 // Regular file
259 if hdr.Typeflag == tar.TypeReg {
Robin Thellend77c3d832015-03-04 14:10:18 -0800260 out, err := os.OpenFile(name, os.O_CREATE|os.O_WRONLY, os.FileMode(createFileMode))
Robin Thellende2627892014-11-26 09:34:37 -0800261 if err != nil {
262 return err
263 }
264 nbytes, err := io.Copy(out, tr)
265 out.Close()
266 if err != nil {
267 return err
268 }
269 if nbytes != hdr.Size {
Mike Burrows249cbc02015-03-16 10:51:44 -0700270 return verror.New(errBadFileSize, nil, hdr.Name, nbytes, hdr.Size)
Robin Thellende2627892014-11-26 09:34:37 -0800271 }
272 continue
273 }
274 // Directory
275 if hdr.Typeflag == tar.TypeDir {
Robin Thellend77c3d832015-03-04 14:10:18 -0800276 if err := os.Mkdir(name, os.FileMode(createDirMode)); err != nil && !os.IsExist(err) {
Robin Thellende2627892014-11-26 09:34:37 -0800277 return err
278 }
279 continue
280 }
281 // Skip unsupported types
282 // TODO(rthellend): Consider adding support for Symlink.
283 }
284}