lib9p

Go 9P library.
Log | Files | Refs | LICENSE

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:
Mdiskfs/file.go | 2+-
Mdiskfs/qid_plan9.go | 2+-
Mdiskfs/qid_unix.go | 2+-
Adiskfs/qid_windows.go | 112+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adiskfs/stat_windows.go | 214+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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