Skip to content

Instantly share code, notes, and snippets.

@vtolstov
Created March 6, 2015 14:19
Show Gist options
  • Select an option

  • Save vtolstov/97f8bc8cd2f32da42e81 to your computer and use it in GitHub Desktop.

Select an option

Save vtolstov/97f8bc8cd2f32da42e81 to your computer and use it in GitHub Desktop.
fs.go
package main
import (
"crypto/tls"
"io"
"net"
"net/http"
"os"
"strings"
"time"
"log"
"golang.org/x/net/context"
"bazil.org/fuse"
"bazil.org/fuse/fs"
)
var httpTransport *http.Transport = &http.Transport{
Dial: (&net.Dialer{DualStack: true}).Dial,
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
var httpClient *http.Client = &http.Client{Transport: httpTransport, Timeout: 100 * time.Second}
func printf(msg interface{}) {
log.Printf("%s\n", msg)
}
func main() {
var err error
fuse.Debug = printf
fuse.Unmount("/srv/iso")
_, err = os.Stat("/srv/iso")
if err != nil {
err = os.MkdirAll("/srv/iso", 0770)
if err != nil {
log.Printf("Failed to create dir: %s\n", err.Error())
os.Exit(1)
}
}
fc, err := fuse.Mount("/srv/iso/", fuse.AllowOther(), fuse.FSName("httpfs"), fuse.Subtype("http"))
if err != nil {
log.Printf("Failed to mount fuse : %s\n", err.Error())
os.Exit(1)
}
filesystem := &httpFS{dir: "20189", url: "http://mirror.yandex.ru/archlinux/iso/latest/archlinux-2015.03.01-dual.iso"}
if err = fs.Serve(fc, filesystem); err != nil {
log.Printf("Failed to serve fuse : %s\n", err.Error())
os.Exit(1)
}
}
type httpFS struct {
dir string
url string
}
type httpDir struct {
file *httpFile
}
type httpFile struct {
size int64
name string
}
var _ fs.FS = (*httpFS)(nil)
var _ fs.Node = (*httpDir)(nil)
var _ = fs.NodeRequestLookuper(&httpDir{})
var _ = fs.HandleReadDirAller(&httpDir{})
var _ fs.Node = (*httpFile)(nil)
var _ fs.Handle = (*FileHandle)(nil)
var _ fs.HandleReleaser = (*FileHandle)(nil)
var _ = fs.NodeOpener(&httpFile{})
type FileHandle struct {
r io.ReadCloser
}
func (fh *FileHandle) Release(ctx context.Context, req *fuse.ReleaseRequest) error {
return fh.r.Close()
}
func isWriteFlags(flags fuse.OpenFlags) bool {
// TODO read/writeness are not flags, use O_ACCMODE
return flags&fuse.OpenFlags(os.O_WRONLY|os.O_RDWR|os.O_APPEND|os.O_CREATE) != 0
}
func (f *httpFile) Open(ctx context.Context, req *fuse.OpenRequest, res *fuse.OpenResponse) (fs.Handle, error) {
if isWriteFlags(req.Flags) {
return nil, fuse.EPERM
}
httpreq, err := http.NewRequest("GET", "http://mirror.yandex.ru/archlinux/iso/latest/archlinux-2015.03.01-dual.iso", nil)
if err != nil {
return nil, fuse.EIO
}
httpres, err := httpClient.Do(httpreq)
if err != nil {
return nil, fuse.EIO
}
res.Flags |= fuse.OpenNonSeekable
res.Flags &= ^fuse.OpenDirectIO
return &FileHandle{r: httpres.Body}, nil
}
var _ = fs.HandleReader(&FileHandle{})
func (fh *FileHandle) Read(ctx context.Context, req *fuse.ReadRequest, res *fuse.ReadResponse) error {
// We don't actually enforce Offset to match where previous read
// ended. Maybe we should, but that would mean'd we need to track
// it. The kernel *should* do it for us, based on the
// fuse.OpenNonSeekable flag.
buf := make([]byte, req.Size)
n, err := fh.r.Read(buf)
res.Data = buf[:n]
return err
}
func (f *httpFile) Size() uint64 {
return 623902720
}
func (f *httpFile) Attr() fuse.Attr {
return httpAttr(f)
}
func (d *httpDir) Attr() fuse.Attr {
if d.file == nil {
return fuse.Attr{
Mode: os.ModeDir | 0555,
Uid: uint32(os.Getuid()),
Gid: uint32(os.Getgid()),
}
}
return httpAttr(d.file)
}
func (d *httpDir) Lookup(ctx context.Context, req *fuse.LookupRequest, res *fuse.LookupResponse) (fs.Node, error) {
path := req.Name
if d.file != nil {
path = d.file.name + path
}
for _, f := range []*httpFile{&httpFile{name: "archlinux.iso", size: 623902720}} {
switch {
case f.name == path:
child := f
return child, nil
case f.name[:len(f.name)-1] == path && f.name[len(f.name)-1] == '/':
child := &httpDir{
file: f,
}
return child, nil
}
}
return nil, fuse.ENOENT
}
func httpAttr(f *httpFile) fuse.Attr {
return fuse.Attr{
Size: f.Size(),
Mode: os.FileMode(0444),
Mtime: time.Now(),
Ctime: time.Now(),
Crtime: time.Now(),
Uid: uint32(os.Getuid()),
Gid: uint32(os.Getgid()),
}
}
func (f *httpFS) Root() (fs.Node, error) {
n := &httpDir{}
return n, nil
}
func (d *httpDir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
prefix := ""
if d.file != nil {
prefix = d.file.name
}
var res []fuse.Dirent
for _, f := range []*httpFile{&httpFile{name: "archlinux.iso", size: 623902720}} {
if !strings.HasPrefix(f.name, prefix) {
continue
}
name := f.name[len(prefix):]
if name == "" {
// the dir itself, not a child
continue
}
if strings.ContainsRune(name[:len(name)-1], '/') {
// contains slash in the middle -> is in a deeper subdir
continue
}
var de fuse.Dirent
if name[len(name)-1] == '/' {
// directory
name = name[:len(name)-1]
de.Type = fuse.DT_Dir
}
de.Name = name
res = append(res, de)
}
return res, nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment