-
Notifications
You must be signed in to change notification settings - Fork 155
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
crfs, stargz: basics of read-only FUSE filesystem, directory support
No network support yet. But this implements the basic FUSE support reading from a local stargz file. Updates golang/go#30829 Change-Id: I342e957b3b36cded5aec8b1cdca65c3f5e788db3 Reviewed-on: https://go-review.googlesource.com/c/build/+/168799 Reviewed-by: Maisem Ali <[email protected]> Reviewed-by: Brad Fitzpatrick <[email protected]>
- Loading branch information
Showing
5 changed files
with
346 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,223 @@ | ||
// 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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
module golang.org/x/build/crfs | ||
|
||
go 1.12 | ||
|
||
require ( | ||
bazil.org/fuse v0.0.0-20180421153158-65cc252bf669 | ||
golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53 // indirect | ||
golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54 // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
bazil.org/fuse v0.0.0-20180421153158-65cc252bf669 h1:FNCRpXiquG1aoyqcIWVFmpTSKVcx2bQD38uZZeGtdlw= | ||
bazil.org/fuse v0.0.0-20180421153158-65cc252bf669/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= | ||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||
golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53 h1:kcXqo9vE6fsZY5X5Rd7R1l7fTgnWaDCVmln65REefiE= | ||
golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | ||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||
golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54 h1:xe1/2UUJRmA9iDglQSlkx8c5n3twv58+K0mPpC2zmhA= | ||
golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.