blob: 32a5b1403c8ef95a4874cd4ee7eaa74db6139697 [file] [log] [blame]
// Copyright 2017 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 blobfs provides some wrappers around interactions with the blobfs.
// TODO(raggi): add support for blob garbage collection
package blobfs
import (
"fmt"
"log"
"os"
"path/filepath"
"syscall"
"syscall/zx"
)
// Manager wraps operations for reading and writing to blobfs, and will later
// tackle more complex problems such as managing reference counting and garbage
// collection of blobs.
type Manager struct {
Root string
channel zx.Channel
}
// New constructs a new Manager for the blobfs mount at the given root.
func New(root string) (*Manager, error) {
rootFDIO, err := syscall.OpenPath(root, 0, 0644)
if err != nil {
return nil, fmt.Errorf("pkgfs: blobfs: can't open %q: %s", root, err)
}
defer rootFDIO.Close()
rootIO, err := rootFDIO.Clone()
if err != nil {
return nil, fmt.Errorf("pkgfs: blobfs: can't clone blobfs root handle: %s", err)
}
handles := rootIO.Handles()
for _, h := range handles[1:] {
h.Close()
}
// The first handle is always a channel.
channel := zx.Channel(handles[0])
return &Manager{Root: root, channel: channel}, nil
}
// Open opens a blobfs blob for reading
func (m *Manager) Open(root string) (*os.File, error) {
return os.Open(m.bpath(root))
}
// Channel returns an the FDIO directory handle for the blobfs root
func (m *Manager) Channel() zx.Channel {
return m.channel
}
// HasBlob returns true if the requested blob is available for reading, false otherwise
func (m *Manager) HasBlob(root string) bool {
// blobfs currently provides a signal handle over handle 2 in remoteio, but
// if it ever moves away from remoteio, or there are ever protocol or api
// changes this path is very brittle. There's no hard requirement for
// intermediate clients/servers to retain this handle. As such we disregard
// that option for detecting readable state.
// blobfs also does not reject open flags on in-flight blobs that only
// contain "read", even though reads on those files would return errors. This
// could be used to detect blob readability state, by opening and then issueing
// a read, but that will make blobfs do a lot of work.
// the final method chosen here for now is to open the blob for writing,
// non-exclusively. That will be rejected if the blob has already been fully
// written.
f, err := syscall.Open(m.bpath(root), syscall.O_WRONLY|syscall.O_APPEND, 0)
if os.IsNotExist(err) {
return false
}
syscall.Close(f)
// if there was no error, then we opened the file for writing and the file was
// writable, which means it exists and is being written by someone.
if err == nil {
return false
}
// Access denied indicates we explicitly know that we have opened a blob that
// already exists and it is not writable - meaning it's already written.
if e, ok := err.(zx.Error); ok && e.Status == zx.ErrAccessDenied {
return true
}
log.Printf("blobfs: unknown error asserting blob existence: %s", err)
// fall back to trying to stat the file. note that stat doesn't tell us if the
// file is readable/writable, but we best assume here that if stat succeeds
// there's something blocking the write path, and there's a good chance that
// the file exists and is readable. this could lead to premature package
// activation and associated errors, but those are hopefully better than some
// problems the other way around, such as repeatedly trying to re-download
// blobs. if the stat does not succeed however, we probably want to try to
// indicate to various systems that they should try to fetch/write the blob,
// hopefully leading to write-repair.
if _, err := os.Stat(m.bpath(root)); err == nil {
return true
}
return false
}
func (m *Manager) bpath(root string) string {
return filepath.Join(m.Root, root)
}
func (m *Manager) PathOf(root string) string {
return m.bpath(root)
}