зеркало из https://github.com/golang/build.git
224 строки
5.3 KiB
Go
224 строки
5.3 KiB
Go
// Copyright 2019 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// The crfs command runs the Container Registry Filesystem, providing a read-only
|
|
// FUSE filesystem for container images.
|
|
//
|
|
// For purposes of documentation, we'll assume you've mounted this at /crfs.
|
|
//
|
|
// Currently (as of 2019-03-21) it only mounts a single layer at the top level.
|
|
// In the future it'll have paths like:
|
|
//
|
|
// /crfs/image/gcr.io/foo-proj/image/latest
|
|
// /crfs/layer/gcr.io/foo-proj/image/latest/xxxxxxxxxxxxxx
|
|
//
|
|
// For mounting a squashed image and a layer, respectively, with the
|
|
// host, owner, image name, and version encoded in the path
|
|
// components.
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"sort"
|
|
"syscall"
|
|
"time"
|
|
"unsafe"
|
|
|
|
"bazil.org/fuse"
|
|
fspkg "bazil.org/fuse/fs"
|
|
"golang.org/x/build/crfs/stargz"
|
|
)
|
|
|
|
const debug = false
|
|
|
|
func usage() {
|
|
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
|
|
fmt.Fprintf(os.Stderr, " %s <MOUNT_POINT> (defaults to /crfs)\n", os.Args[0])
|
|
flag.PrintDefaults()
|
|
}
|
|
|
|
var stargzFile = flag.String("test_stargz", "", "local stargz file for testing a single layer mount, without hitting a container registry")
|
|
|
|
func main() {
|
|
flag.Parse()
|
|
mntPoint := "/crfs"
|
|
if flag.NArg() > 1 {
|
|
usage()
|
|
os.Exit(2)
|
|
}
|
|
if flag.NArg() == 1 {
|
|
mntPoint = flag.Arg(0)
|
|
}
|
|
|
|
if *stargzFile == "" {
|
|
log.Fatalf("TODO: network mode not done yet. Use --test_stargz for now")
|
|
}
|
|
fs, err := NewLocalStargzFileFS(*stargzFile)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
c, err := fuse.Mount(mntPoint, fuse.FSName("crfs"), fuse.Subtype("crfs"))
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer c.Close()
|
|
|
|
err = fspkg.Serve(c, fs)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
// check if the mount process has an error to report
|
|
<-c.Ready
|
|
if err := c.MountError; err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// FS is the CRFS filesystem.
|
|
// It implements https://godoc.org/bazil.org/fuse/fs#FS
|
|
type FS struct {
|
|
r *stargz.Reader
|
|
}
|
|
|
|
func NewLocalStargzFileFS(file string) (*FS, error) {
|
|
f, err := os.Open(file)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fi, err := f.Stat()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
r, err := stargz.Open(io.NewSectionReader(f, 0, fi.Size()))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &FS{r: r}, nil
|
|
}
|
|
|
|
// Root returns the root filesystem node for the CRFS filesystem.
|
|
// See https://godoc.org/bazil.org/fuse/fs#FS
|
|
func (fs *FS) Root() (fspkg.Node, error) {
|
|
te, ok := fs.r.Lookup("")
|
|
if !ok {
|
|
return nil, errors.New("failed to find root in stargz")
|
|
}
|
|
return &node{fs, te}, nil
|
|
}
|
|
|
|
func inodeOfEnt(ent *stargz.TOCEntry) uint64 {
|
|
return uint64(uintptr(unsafe.Pointer(ent)))
|
|
}
|
|
|
|
func direntType(ent *stargz.TOCEntry) fuse.DirentType {
|
|
switch ent.Type {
|
|
case "dir":
|
|
return fuse.DT_Dir
|
|
case "reg":
|
|
return fuse.DT_File
|
|
case "symlink":
|
|
return fuse.DT_Link
|
|
}
|
|
// TODO: socket, block, char, fifo as needed
|
|
return fuse.DT_Unknown
|
|
}
|
|
|
|
// node is a CRFS node in the FUSE filesystem.
|
|
// See https://godoc.org/bazil.org/fuse/fs#Node
|
|
type node struct {
|
|
fs *FS
|
|
te *stargz.TOCEntry
|
|
}
|
|
|
|
var (
|
|
_ fspkg.HandleReadDirAller = (*node)(nil)
|
|
_ fspkg.Node = (*node)(nil)
|
|
_ fspkg.NodeStringLookuper = (*node)(nil)
|
|
_ fspkg.NodeReadlinker = (*node)(nil)
|
|
_ fspkg.HandleReader = (*node)(nil)
|
|
)
|
|
|
|
// Attr populates a with the attributes of n.
|
|
// See https://godoc.org/bazil.org/fuse/fs#Node
|
|
func (n *node) Attr(ctx context.Context, a *fuse.Attr) error {
|
|
fi := n.te.Stat()
|
|
a.Valid = 30 * 24 * time.Hour
|
|
a.Inode = inodeOfEnt(n.te)
|
|
a.Size = uint64(fi.Size())
|
|
a.Blocks = a.Size / 512
|
|
a.Mtime = fi.ModTime()
|
|
a.Mode = fi.Mode()
|
|
a.Uid = uint32(n.te.Uid)
|
|
a.Gid = uint32(n.te.Gid)
|
|
if debug {
|
|
log.Printf("attr of %s: %s", n.te.Name, *a)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ReadDirAll returns all directory entries in the directory node n.
|
|
//
|
|
// https://godoc.org/bazil.org/fuse/fs#HandleReadDirAller
|
|
func (n *node) ReadDirAll(ctx context.Context) (ents []fuse.Dirent, err error) {
|
|
n.te.ForeachChild(func(baseName string, ent *stargz.TOCEntry) bool {
|
|
ents = append(ents, fuse.Dirent{
|
|
Inode: inodeOfEnt(ent),
|
|
Type: direntType(ent),
|
|
Name: baseName,
|
|
})
|
|
return true
|
|
})
|
|
sort.Slice(ents, func(i, j int) bool { return ents[i].Name < ents[j].Name })
|
|
return ents, nil
|
|
}
|
|
|
|
// Lookup looks up a child entry of the directory node n.
|
|
//
|
|
// See https://godoc.org/bazil.org/fuse/fs#NodeStringLookuper
|
|
func (n *node) Lookup(ctx context.Context, name string) (fspkg.Node, error) {
|
|
e, ok := n.te.LookupChild(name)
|
|
if !ok {
|
|
return nil, syscall.ENOENT
|
|
}
|
|
return &node{n.fs, e}, nil
|
|
}
|
|
|
|
// Readlink reads the target of a symlink.
|
|
//
|
|
// See https://godoc.org/bazil.org/fuse/fs#NodeReadlinker
|
|
func (n *node) Readlink(ctx context.Context, req *fuse.ReadlinkRequest) (string, error) {
|
|
if n.te.Type != "symlink" {
|
|
return "", syscall.EINVAL
|
|
}
|
|
return n.te.LinkName, nil
|
|
}
|
|
|
|
// Read reads data from a regular file n.
|
|
//
|
|
// See https://godoc.org/bazil.org/fuse/fs#HandleReader
|
|
func (n *node) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {
|
|
sr, err := n.fs.r.OpenFile(n.te.Name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
resp.Data = make([]byte, req.Size)
|
|
nr, err := sr.ReadAt(resp.Data, req.Offset)
|
|
if nr < req.Size {
|
|
resp.Data = resp.Data[:nr]
|
|
}
|
|
if debug {
|
|
log.Printf("Read response: size=%d @ %d, read %d", req.Size, req.Offset, nr)
|
|
}
|
|
return nil
|
|
}
|