blob: 2d9d5fa85a86ed980fcc39bc28dbe55518a595f8 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Package install implements the `pm install` command
package install
import (
"bytes"
"flag"
"fmt"
"io"
"log"
"os"
"path/filepath"
"runtime"
"strings"
"sync"
"sync/atomic"
"fuchsia.googlesource.com/far"
"fuchsia.googlesource.com/merkle"
"fuchsia.googlesource.com/pm/build"
)
const usage = `Usage: %s install
install a single .far representation of the package
`
// Run reads an archive given in flags.Arg(1) (the first argument after
// `install`) and unpacks the archive, adding it's contents to the appropriate
// locations in /pkgfs to install the package.
func Run(cfg *build.Config, args []string) error {
fs := flag.NewFlagSet("install", flag.ExitOnError)
fs.Usage = func() {
fmt.Fprintf(os.Stderr, usage, filepath.Base(os.Args[0]))
fmt.Fprintln(os.Stderr)
fs.PrintDefaults()
}
if err := fs.Parse(args); err != nil {
return err
}
af, err := os.Open(fs.Arg(0))
if err != nil {
return err
}
defer af.Close()
pkgArchive, err := far.NewReader(af)
if err != nil {
return err
}
b, err := pkgArchive.ReadFile("meta.far")
if err != nil {
return err
}
var tree merkle.Tree
_, err = tree.ReadFrom(bytes.NewReader(b))
if err != nil {
return err
}
merkleroot := tree.Root()
f, err := os.Create(filepath.Join("/pkgfs/install/pkg", fmt.Sprintf("%x", merkleroot)))
if err != nil && !os.IsExist(err) {
return err
}
if err == nil {
f.Truncate(int64(len(b)))
_, err = f.Write(b)
if err != nil {
f.Close()
if os.IsExist(err) {
log.Printf("package already exists")
return nil
}
return err
}
if err := f.Close(); err != nil {
return err
}
}
log.Printf("meta.far written, starting blobs")
names := make(chan string, runtime.NumCPU())
var w sync.WaitGroup
var hadError atomic.Value
hadError.Store(false)
for i := 0; i < runtime.NumCPU(); i++ {
w.Add(1)
go func() {
defer w.Done()
for name := range names {
func() {
if len(name) != 64 || strings.Contains(name, "/") {
log.Printf("Invalid package blob in archive: %s", name)
hadError.Store(true)
return
}
// TODO(raggi): support a non-exclusive write where the blob exists but isn't readable, try to write it from the archive
f, err := os.OpenFile(filepath.Join("/pkgfs/install/blob", name), os.O_WRONLY|os.O_CREATE|os.O_EXCL, os.ModePerm)
if os.IsExist(err) {
return
}
if err != nil {
log.Printf("Error creating blob in blobfs: %s", err)
hadError.Store(true)
return
}
defer f.Close()
size := int64(pkgArchive.GetSize(name))
r, err := pkgArchive.Open(name)
if err != nil {
log.Printf("Error opening archive file %q: %s", name, err)
hadError.Store(true)
return
}
if err := f.Truncate(size); err != nil {
log.Printf("Error truncating blob %q to %d: %s", name, size, err)
hadError.Store(true)
return
}
var pos int64
var buf [32 * 1024]byte
for {
n, err := r.ReadAt(buf[:], pos)
if n > 0 {
if _, err := f.Write(buf[:n]); err != nil {
log.Printf("Error writing to blob %q: %s", name, err)
hadError.Store(true)
return
}
}
pos += int64(n)
if err == io.EOF {
break
}
if err != nil {
log.Printf("Error reading %q from archive: %s", name, err)
hadError.Store(true)
return
}
}
err = f.Close()
if err != nil {
log.Printf("Error closing blob %q: %s", name, err)
hadError.Store(true)
return
}
}()
}
}()
}
needs, err := filepath.Glob("/pkgfs/needs/blobs/*")
if err != nil {
return err
}
for i, need := range needs {
needs[i] = filepath.Base(need)
}
for _, name := range pkgArchive.List() {
if name == "meta.far" {
continue
}
// skip blobs that are already present
for _, need := range needs {
if name == need {
names <- name
break
}
}
}
close(names)
w.Wait()
if hadError.Load().(bool) {
return fmt.Errorf("installation finished with errors")
}
return nil
}