commit 99310f35f7a64b95f962bafe1b8fccabd4b13260
parent b575a59b0b37a1d1087461bb631235dd69009fa2
Author: Matsuda Kenji <info@mtkn.jp>
Date: Tue, 19 Dec 2023 10:02:41 +0900
add windows support for diskfs
Diffstat:
5 files changed, 329 insertions(+), 3 deletions(-)
diff --git a/diskfs/file.go b/diskfs/file.go
@@ -81,7 +81,7 @@ func (f *File) ReadDir(n int) ([]fs.DirEntry, error) {
if err != nil {
return nil, fmt.Errorf("info: %v", err)
}
- id := idFromInfo(fi)
+ id := idFromInfo(f.path, fi)
de[i] = &lib9p.DirEntry{Stat: *fiStat(f.fs.qidPool, id, fi)}
}
return de, nil
diff --git a/diskfs/qid_plan9.go b/diskfs/qid_plan9.go
@@ -7,7 +7,7 @@ import (
type fileID struct{}
-func idFromInfo(fi fs.FileInfo) fileID { return fileID{} }
+func idFromInfo(path string, fi fs.FileInfo) fileID { return fileID{} }
// QidPool is the list of Qids in the file system.
type QidPool struct {}
diff --git a/diskfs/qid_unix.go b/diskfs/qid_unix.go
@@ -48,7 +48,7 @@ func (f *File) id() (fileID, error) {
}
// idFromInfo derives the fileID from the fs.FileInfo.
-func idFromInfo(info fs.FileInfo) fileID {
+func idFromInfo(path string, info fs.FileInfo) fileID {
stat := info.Sys().(*syscall.Stat_t)
return fileID{device: uint64(stat.Dev), inode: stat.Ino}
}
diff --git a/diskfs/qid_windows.go b/diskfs/qid_windows.go
@@ -0,0 +1,112 @@
+//go:build windows
+package diskfs
+
+import (
+ "fmt"
+ "io/fs"
+ "os"
+ "path/filepath"
+ "time"
+
+ "git.mtkn.jp/lib9p"
+)
+
+// fileID is the identifier of a windows file.
+// It is used to assosiate the file with Qid.
+type fileID string
+
+// qidReq is used to update the Qid.Vers by checking the file's
+// modified time.
+type qidRec struct {
+ qid *lib9p.Qid
+ mtime time.Time
+}
+
+// QidPool is the list of Qids in the file system.
+type QidPool struct {
+ m map[fileID]*qidRec
+ nextQid uint64
+}
+
+// id derives the fileID from the file.
+func (f *File) id() (fileID, error) {
+ return fileID(f.path), nil
+}
+
+// idFromInfo derives the fileID from the fs.FileInfo.
+func idFromInfo(path string, info fs.FileInfo) fileID {
+ return fileID(path)
+}
+
+// newQidPool allocates a QidPool.
+func newQidPool() *QidPool {
+ return &QidPool{
+ m: make(map[fileID]*qidRec),
+ }
+}
+
+// lookup looks up the file in the QidPool.
+// If found, it returns the corresponding Qid and true.
+// If not found, it returns nil and false.
+func (pool *QidPool) lookup(f *File) (lib9p.Qid, bool) {
+ id, err := f.id()
+ if err != nil {
+ return lib9p.Qid{}, false
+ }
+ ospath := filepath.Join(f.fs.rootPath, f.path)
+ fsfi, err := os.Stat(ospath)
+ if err != nil {
+ return lib9p.Qid{}, false
+ }
+ return pool.lookupID(id, fsfi.ModTime())
+}
+
+// lookupID looksup the file in the QidPool by fileID.
+// It also checks if the file is modified after the last access by
+// comparing the qidReq.mtime and mtime, and update Qid.Vers if needed.
+func (pool *QidPool) lookupID(id fileID, mtime time.Time) (lib9p.Qid, bool) {
+ qrec, ok := pool.m[id]
+ if !ok {
+ return lib9p.Qid{}, false
+ }
+ if qrec.mtime.Before(mtime) {
+ qrec.qid.Vers++
+ qrec.mtime = mtime
+ }
+ return *qrec.qid, ok
+}
+
+// add adds the file's Qid to the QidPool and returns the newly added Qid.
+func (pool *QidPool) add(f *File) (lib9p.Qid, error) {
+ id, err := f.id()
+ if err != nil {
+ return lib9p.Qid{}, fmt.Errorf("get id: %v", err)
+ }
+ ospath := filepath.Join(f.fs.rootPath, f.path)
+ fi, err := os.Stat(ospath)
+ if err != nil {
+ return lib9p.Qid{}, fmt.Errorf("stat %v: %v", f, err)
+ }
+ return pool.addID(id, fi)
+}
+
+// addID does the same as add, but by fileID.
+func (pool *QidPool) addID(id fileID, info fs.FileInfo) (lib9p.Qid, error) {
+ qtype := lib9p.FSModeToQidType(info.Mode())
+ qid := &lib9p.Qid{
+ Path: pool.nextQid,
+ Type: qtype,
+ }
+ pool.m[id] = &qidRec{qid: qid, mtime: info.ModTime()}
+ pool.nextQid++
+ return *qid, nil
+}
+
+// delete deletes Qid associated with f from the QidPool.
+func (pool *QidPool) delete(f *File) {
+ id, err := f.id()
+ if err != nil {
+ return
+ }
+ delete(pool.m, id)
+}
diff --git a/diskfs/stat_windows.go b/diskfs/stat_windows.go
@@ -0,0 +1,213 @@
+//go:build windows
+package diskfs
+
+import (
+ "fmt"
+ "io/fs"
+ "os"
+ "os/user"
+ "path"
+ "path/filepath"
+ "strconv"
+ "syscall"
+ "time"
+
+ "git.mtkn.jp/lib9p"
+)
+
+func fileTimeToUnixTime(t syscall.Filetime) uint32 {
+ return uint32(uint64(t.HighDateTime << 32 | t.LowDateTime) / 1e7 - 11644473600)
+}
+
+// fiStat generates lib9p.Stat from the FileInfo.
+// It requires QidPool and fileID to fill the Qid field of Stat.
+func fiStat(pool *QidPool, id fileID, info fs.FileInfo) *lib9p.Stat {
+ qid, ok := pool.lookupID(id, info.ModTime())
+ if !ok {
+ var err error
+ qid, err = pool.addID(id, info)
+ if err != nil {
+ panic(fmt.Errorf("allocID: %v", err))
+ }
+ }
+
+ stat := info.Sys().(*syscall.Win32FileAttributeData)
+ /*
+ // TODO: get actual file uid
+ usr, err := user.LookupId(strconv.Itoa(syscall.Getuid()))
+ if err != nil {
+ panic(fmt.Errorf("user: %v", err))
+ }
+ // TODO: get actual file gid
+ group, err := user.LookupGroupId(strconv.Itoa(syscall.Getgid()))
+ if err != nil {
+ panic(fmt.Errorf("group: %v", err))
+ }
+ */
+ return &lib9p.Stat{
+ Type: 0,
+ Dev: 0,
+ Qid: qid,
+ Mode: info.Mode(),
+ Atime: fileTimeToUnixTime(stat.LastAccessTime),
+ Mtime: fileTimeToUnixTime(stat.LastWriteTime),
+ Length: info.Size(),
+ Name: info.Name(),
+ Uid: "kenji", //usr.Username,
+ Gid: "kenji", //group.Name,
+ Muid: "",
+ }
+}
+
+var gidCache map[fileID]string
+
+func init() {
+ gidCache = make(map[fileID]string)
+}
+
+// Stat is real implementation of File.Stat.
+func (f *File) stat() (*lib9p.FileInfo, error) {
+ ospath := filepath.Join(f.fs.rootPath, f.path)
+ fsfi, err := os.Stat(ospath)
+ if err != nil {
+ return nil, fmt.Errorf("stat: %v", err)
+ }
+ var stat lib9p.Stat
+ stat.Type = 0
+ stat.Dev = 0
+ qid, ok := f.fs.qidPool.lookup(f)
+ if !ok {
+ qid, err = f.fs.qidPool.add(f)
+ if err != nil {
+ panic(fmt.Errorf("alloc qid: %v", err))
+ }
+ }
+ stat.Qid = qid
+ winfs := fsfi.Sys().(*syscall.Win32FileAttributeData)
+ stat.Length = fsfi.Size()
+ stat.Mode = fsfi.Mode()
+ stat.Name = fsfi.Name()
+ stat.Atime = fileTimeToUnixTime(winfs.LastAccessTime)
+ stat.Mtime = fileTimeToUnixTime(winfs.LastWriteTime)
+/*
+ usr, err := user.LookupId(strconv.Itoa(syscall.Getuid()))
+ if err != nil {
+ return nil, fmt.Errorf("LookupId: %v", err)
+ }
+*/
+ stat.Uid = "kenji" // usr.Username
+/*
+ group, err := user.LookupGroupId(strconv.Itoa(syscall.Getgid()))
+ if err != nil {
+ return nil, fmt.Errorf("LookupGroupId: %v", err)
+ }
+*/
+ stat.Gid = "kenji" //group.Name
+ stat.Muid = ""
+ return &lib9p.FileInfo{stat}, nil
+}
+
+// wstat is the real implementation of File.WStat.
+// TODO: when error occurs, file stat should be restored.
+func (f *File) wstat(s *lib9p.Stat) error {
+ var file *os.File
+ fi, err := f.Stat()
+ if err != nil {
+ return fmt.Errorf("stat: %v", err)
+ }
+ oldStat := fi.Sys().(*lib9p.Stat)
+ if s.Length != oldStat.Length {
+ if file == nil {
+ file, err = os.OpenFile(path.Join(f.fs.rootPath, f.path), os.O_RDWR, 0)
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+ }
+ if err := file.Truncate(s.Length); err != nil {
+ return fmt.Errorf("truncate: %v", err)
+ }
+ ret, err := file.Seek(0, 0)
+ if err != nil {
+ return fmt.Errorf("seek 0: %d, %w", ret, err)
+ }
+ }
+ if s.Mode != oldStat.Mode {
+ if file == nil {
+ file, err = os.OpenFile(path.Join(f.fs.rootPath, f.path), os.O_RDWR, 0)
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+ }
+ if err := file.Chmod(s.Mode); err != nil {
+ return fmt.Errorf("chmod: %v", err)
+ }
+ }
+ if s.Mtime != oldStat.Mtime {
+ err := os.Chtimes(f.path, time.Time{}, time.Unix(int64(s.Mtime), 0))
+ if err != nil {
+ return fmt.Errorf("chtimes: %v", err)
+ }
+ }
+ if s.Gid != oldStat.Gid {
+ group, err := user.LookupGroup(s.Gid)
+ if err != nil {
+ return fmt.Errorf("lookupgroup: %v", err)
+ }
+ usr, err := user.Lookup(oldStat.Uid)
+ if err != nil {
+ return fmt.Errorf("lookup user: %v", err)
+ }
+ u, err := strconv.Atoi(usr.Uid)
+ if err != nil {
+ return fmt.Errorf("convert uid: %v", err)
+ }
+ g, err := strconv.Atoi(group.Gid)
+ if err != nil {
+ return fmt.Errorf("convert gid: %v", err)
+ }
+ if file == nil {
+ file, err = os.OpenFile(path.Join(f.fs.rootPath, f.path), os.O_RDWR, 0)
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+ }
+ if err := file.Chown(u, g); err != nil {
+ return fmt.Errorf("chown: %v", err)
+ }
+ }
+ if s.Name != oldStat.Name {
+ // TODO: check neither Names contains "/"
+ oldpath := filepath.Join(f.fs.rootPath, path.Dir(f.path), oldStat.Name)
+ newpath := filepath.Join(f.fs.rootPath, path.Dir(f.path), s.Name)
+ if err := os.Rename(oldpath, newpath); err != nil {
+ return fmt.Errorf("rename: %v", err)
+ }
+ }
+ return nil
+}
+
+func chown(ospath string, uid, gid string) error {
+ usr, err := user.Lookup(uid)
+ if err != nil {
+ return fmt.Errorf("lookup user: %v", err)
+ }
+ u, err := strconv.Atoi(usr.Uid)
+ if err != nil {
+ return fmt.Errorf("convert uid: %v", err)
+ }
+ group, err := user.LookupGroup(gid)
+ if err != nil {
+ return fmt.Errorf("lookupgroup: %v", err)
+ }
+ g, err := strconv.Atoi(group.Gid)
+ if err != nil {
+ return fmt.Errorf("convert gid: %v", err)
+ }
+ if err := os.Chown(ospath, u, g); err != nil {
+ return fmt.Errorf("set owner and group: %v", err)
+ }
+ return nil
+}
+\ No newline at end of file