| package pkgfs |
| |
| import ( |
| "fmt" |
| "io" |
| "path/filepath" |
| "strings" |
| "time" |
| |
| "thinfs/fs" |
| |
| "fuchsia.googlesource.com/far" |
| ) |
| |
| func newMetaFar(name, version, blob string, fs *Filesystem) *metaFar { |
| return &metaFar{ |
| name: name, |
| version: version, |
| blob: blob, |
| fs: fs, |
| } |
| } |
| |
| // metaFar is a shared reference to an open meta.far or one or more of it's contents. |
| type metaFar struct { |
| name, version string // of the package |
| blob string |
| |
| fs *Filesystem |
| } |
| |
| func (mf *metaFar) open() (*far.Reader, error) { |
| f, err := mf.fs.blobfs.Open(mf.blob) |
| if err != nil { |
| return nil, err |
| } |
| |
| fr, err := far.NewReader(f) |
| if err != nil { |
| f.Close() |
| } |
| return fr, err |
| } |
| |
| func (mf *metaFar) list() ([]string, error) { |
| fr, err := mf.open() |
| if err != nil { |
| return nil, err |
| } |
| defer fr.Close() |
| return fr.List(), nil |
| } |
| |
| // metaFile is the package dir "meta" opened as a file, which on read returns |
| // the merkleroot. |
| type metaFile struct { |
| unsupportedFile |
| |
| *metaFar |
| |
| off int64 |
| flags fs.OpenFlags |
| } |
| |
| func newMetaFile(name, version, blob string, fs *Filesystem, flags fs.OpenFlags) *metaFile { |
| return &metaFile{ |
| unsupportedFile(fmt.Sprintf("pkgfs:meta:%s/%s@%s", name, version, blob)), |
| newMetaFar(name, version, blob, fs), |
| 0, |
| flags, |
| } |
| } |
| |
| func (f *metaFile) Close() error { |
| return nil |
| } |
| |
| func (f *metaFile) GetOpenFlags() fs.OpenFlags { |
| return f.flags |
| } |
| |
| func (f *metaFile) Stat() (int64, time.Time, time.Time, error) { |
| return int64(len(f.blob)), time.Time{}, time.Time{}, nil |
| } |
| |
| func (f *metaFile) Read(p []byte, off int64, whence int) (int, error) { |
| if whence != fs.WhenceFromCurrent { |
| return 0, fs.ErrNotSupported |
| } |
| if f.off+off >= int64(len(f.blob)) { |
| return 0, fs.ErrEOF |
| } |
| |
| n := copy(p, f.blob[f.off+off:]) |
| f.off += off + int64(n) |
| return n, nil |
| } |
| |
| type metaFarDir struct { |
| unsupportedDirectory |
| |
| *metaFar |
| |
| path string |
| } |
| |
| func newMetaFarDir(name, version, blob string, fs *Filesystem) *metaFarDir { |
| return &metaFarDir{ |
| unsupportedDirectory(fmt.Sprintf("pkgfs:meta.far:%s/%s@%s", name, version, blob)), |
| newMetaFar(name, version, blob, fs), |
| "meta", |
| } |
| } |
| |
| func newMetaFarDirAt(name, version, blob string, fs *Filesystem, path string) *metaFarDir { |
| mf := newMetaFarDir(name, version, blob, fs) |
| mf.path = filepath.Join("meta", path) |
| return mf |
| } |
| |
| func (d *metaFarDir) Close() error { |
| debugLog("pkgfs:metaFarDir:close %q/%q@%s", d.name, d.version, d.blob) |
| return nil |
| } |
| |
| func (d *metaFarDir) Dup() (fs.Directory, error) { |
| return d, nil |
| } |
| |
| func (d *metaFarDir) Reopen(flags fs.OpenFlags) (fs.Directory, error) { |
| return d, nil |
| } |
| |
| func (d *metaFarDir) Open(name string, flags fs.OpenFlags) (fs.File, fs.Directory, *fs.Remote, error) { |
| name = clean(name) |
| debugLog("pkgfs:metaFarDir:open %q", name) |
| |
| if name == "" { |
| if flags.File() || (!flags.Directory() && !flags.Path()) { |
| return newMetaFile(d.name, d.version, d.blob, d.fs, flags), nil, nil, nil |
| } |
| return nil, d, nil, nil |
| } |
| |
| name = filepath.Join(d.path, name) |
| |
| if flags.Create() || flags.Truncate() || flags.Write() || flags.Append() { |
| debugLog("pkgfs:metaFarDir:open %q unsupported flags", name) |
| return nil, nil, nil, fs.ErrNotSupported |
| } |
| |
| contents, err := d.metaFar.list() |
| if err != nil { |
| return nil, nil, nil, err |
| } |
| |
| for _, lname := range contents { |
| if name == lname { |
| mff, err := newMetaFarFile(d.name, d.version, d.blob, d.fs, name) |
| return mff, nil, nil, err |
| } |
| } |
| |
| dname := name + "/" |
| for _, lname := range contents { |
| if strings.HasPrefix(lname, dname) { |
| return nil, newMetaFarDirAt(d.name, d.version, d.blob, d.fs, name), nil, nil |
| } |
| } |
| |
| debugLog("pkgfs:metaFarDir:open %q not found", name) |
| return nil, nil, nil, fs.ErrNotFound |
| } |
| |
| func (d *metaFarDir) Read() ([]fs.Dirent, error) { |
| debugLog("pkgfs:metaFarDir:read %q %q", d.blob, d.path) |
| |
| contents, err := d.metaFar.list() |
| if err != nil { |
| return nil, goErrToFSErr(err) |
| } |
| |
| // TODO(raggi): improve efficiency |
| dirs := map[string]struct{}{} |
| dents := []fs.Dirent{} |
| dents = append(dents, dirDirEnt(".")) |
| |
| for _, name := range contents { |
| if !strings.HasPrefix(name, d.path+"/") { |
| continue |
| } |
| name = strings.TrimPrefix(name, d.path+"/") |
| |
| parts := strings.SplitN(name, "/", 2) |
| if len(parts) == 2 { |
| if _, ok := dirs[parts[0]]; !ok { |
| dirs[parts[0]] = struct{}{} |
| dents = append(dents, dirDirEnt(parts[0])) |
| } |
| |
| } else { |
| dents = append(dents, fileDirEnt(parts[0])) |
| } |
| } |
| return dents, nil |
| } |
| |
| func (d *metaFarDir) Stat() (int64, time.Time, time.Time, error) { |
| debugLog("pkgfs:metaFarDir:stat %q/%q@%s", d.name, d.version, d.blob) |
| // TODO(raggi): forward stat values from the index |
| contents, err := d.metaFar.list() |
| if err != nil { |
| return 0, time.Time{}, time.Time{}, goErrToFSErr(err) |
| } |
| return int64(len(contents)), d.fs.mountTime, d.fs.mountTime, nil |
| } |
| |
| type metaFarFile struct { |
| unsupportedFile |
| |
| *metaFar |
| fr *far.Reader |
| er io.ReaderAt |
| |
| off int64 |
| path string |
| } |
| |
| func newMetaFarFile(name, version, blob string, fs *Filesystem, path string) (*metaFarFile, error) { |
| debugLog("pkgfs:metaFarFile:new %q/%s", blob, path) |
| |
| mf := newMetaFar(name, version, blob, fs) |
| fr, err := mf.open() |
| if err != nil { |
| return nil, goErrToFSErr(err) |
| } |
| er, err := fr.Open(path) |
| if err != nil { |
| fr.Close() |
| return nil, goErrToFSErr(err) |
| } |
| |
| return &metaFarFile{ |
| unsupportedFile(fmt.Sprintf("pkgfs:metaFarFile:%s/%s/%s", name, version, path)), |
| mf, |
| fr, |
| er, |
| 0, |
| path, |
| }, nil |
| } |
| |
| func (f *metaFarFile) Close() error { |
| debugLog("pkgfs:metaFarFile:close %q/%s", f.blob, f.path) |
| f.fr.Close() |
| return nil |
| } |
| |
| func (f *metaFarFile) Dup() (fs.File, error) { |
| debugLog("pkgfs:metaFarFile:dup %q/%s", f.blob, f.path) |
| |
| fr, err := f.metaFar.open() |
| if err != nil { |
| return nil, goErrToFSErr(err) |
| } |
| er, err := fr.Open(f.path) |
| if err != nil { |
| fr.Close() |
| return nil, goErrToFSErr(err) |
| } |
| |
| return &metaFarFile{ |
| f.unsupportedFile, |
| f.metaFar, |
| fr, |
| er, |
| 0, |
| f.path, |
| }, nil |
| } |
| |
| func (f *metaFarFile) Reopen(flags fs.OpenFlags) (fs.File, error) { |
| debugLog("pkgfs:metaFarFile:reopen %q/%s", f.blob, f.path) |
| f.off = 0 |
| return f, nil |
| } |
| |
| func (f *metaFarFile) Read(p []byte, off int64, whence int) (int, error) { |
| debugLog("pkgfs:metaFarFile:read %q/%s - %d %d", f.blob, f.path, off, whence) |
| |
| // TODO(raggi): this could allocate less/be far more efficient |
| |
| switch whence { |
| case fs.WhenceFromCurrent: |
| f.off += off |
| n, err := f.er.ReadAt(p, f.off) |
| f.off += int64(n) |
| return n, goErrToFSErr(err) |
| case fs.WhenceFromStart: |
| return f.er.ReadAt(p, off) |
| } |
| return 0, fs.ErrNotSupported |
| } |
| |
| func (f *metaFarFile) Seek(offset int64, whence int) (int64, error) { |
| debugLog("pkgfs:metaFarFile:seek %q/%s", f.blob, f.path) |
| var err error |
| var n int64 |
| switch whence { |
| case fs.WhenceFromCurrent: |
| f.off = f.off + offset |
| case fs.WhenceFromStart: |
| f.off = offset |
| case fs.WhenceFromEnd: |
| err = fs.ErrNotSupported |
| default: |
| return 0, fs.ErrInvalidArgs |
| } |
| if err != nil { |
| return f.off, goErrToFSErr(err) |
| } |
| return n, nil |
| } |
| |
| func (f *metaFarFile) Stat() (int64, time.Time, time.Time, error) { |
| debugLog("pkgfs:metaFarFile:stat") |
| return int64(f.fr.GetSize(f.path)), time.Time{}, time.Time{}, nil |
| } |