Files

151 lines
3.4 KiB
Go

// Copyright 2016 the Go-FUSE 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 zipfs
import (
"archive/zip"
"context"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"sync"
"syscall"
"github.com/hanzoai/go-fuse/v2/fs"
"github.com/hanzoai/go-fuse/v2/fuse"
)
type zipRoot struct {
fs.Inode
zr *zip.ReadCloser
}
var _ = (fs.NodeOnAdder)((*zipRoot)(nil))
func (zr *zipRoot) OnAdd(ctx context.Context) {
for _, f := range zr.zr.File {
if f.FileInfo().IsDir() {
continue
}
dir, base := filepath.Split(filepath.Clean(f.Name))
p := &zr.Inode
for _, component := range strings.Split(dir, "/") {
if len(component) == 0 {
continue
}
ch := p.GetChild(component)
if ch == nil {
ch = p.NewPersistentInode(ctx, &fs.Inode{},
fs.StableAttr{Mode: fuse.S_IFDIR})
p.AddChild(component, ch, true)
}
p = ch
}
ch := p.NewPersistentInode(ctx, &zipFile{file: f}, fs.StableAttr{})
p.AddChild(base, ch, true)
}
}
// NewZipTree creates a new file-system for the zip file named name.
func NewZipTree(name string) (fs.InodeEmbedder, error) {
r, err := zip.OpenReader(name)
if err != nil {
return nil, err
}
return &zipRoot{zr: r}, nil
}
// zipFile is a file read from a zip archive.
type zipFile struct {
fs.Inode
file *zip.File
mu sync.Mutex
data []byte
}
var _ = (fs.NodeOpener)((*zipFile)(nil))
var _ = (fs.NodeGetattrer)((*zipFile)(nil))
// Getattr sets the minimum, which is the size. A more full-featured
// FS would also set timestamps and permissions.
func (zf *zipFile) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
out.Mode = uint32(zf.file.Mode()) & 07777
out.Nlink = 1
out.Mtime = uint64(zf.file.ModTime().Unix())
out.Atime = out.Mtime
out.Ctime = out.Mtime
out.Size = zf.file.UncompressedSize64
const bs = 512
out.Blksize = bs
out.Blocks = (out.Size + bs - 1) / bs
return 0
}
// Open lazily unpacks zip data
func (zf *zipFile) Open(ctx context.Context, flags uint32) (fs.FileHandle, uint32, syscall.Errno) {
zf.mu.Lock()
defer zf.mu.Unlock()
if zf.data == nil {
rc, err := zf.file.Open()
if err != nil {
return nil, 0, syscall.EIO
}
content, err := io.ReadAll(rc)
if err != nil {
return nil, 0, syscall.EIO
}
zf.data = content
}
// We don't return a filehandle since we don't really need
// one. The file content is immutable, so hint the kernel to
// cache the data.
return nil, fuse.FOPEN_KEEP_CACHE, 0
}
// Read simply returns the data that was already unpacked in the Open call
func (zf *zipFile) Read(ctx context.Context, f fs.FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) {
end := int(off) + len(dest)
if end > len(zf.data) {
end = len(zf.data)
}
return fuse.ReadResultData(zf.data[off:end]), 0
}
var _ = (fs.NodeOnAdder)((*zipRoot)(nil))
func NewArchiveFileSystem(name string) (root fs.InodeEmbedder, err error) {
switch {
case strings.HasSuffix(name, ".zip"):
root, err = NewZipTree(name)
case strings.HasSuffix(name, ".tar.gz"):
root, err = NewTarCompressedTree(name, "gz")
case strings.HasSuffix(name, ".tar.bz2"):
root, err = NewTarCompressedTree(name, "bz2")
case strings.HasSuffix(name, ".tar"):
f, err := os.Open(name)
if err != nil {
return nil, err
}
root = &tarRoot{rc: f}
default:
return nil, fmt.Errorf("unknown archive format %q", name)
}
if err != nil {
return nil, err
}
return root, nil
}