stat_unix.go (4589B)
1 //go:build unix 2 3 package diskfs 4 5 import ( 6 "fmt" 7 "io/fs" 8 "os" 9 "os/user" 10 "path" 11 "path/filepath" 12 "strconv" 13 "syscall" 14 "time" 15 16 "git.mtkn.jp/lib9p" 17 ) 18 19 // fiStat generates lib9p.Stat from the FileInfo. 20 // It requires QidPool and fileID to fill the Qid field of Stat. 21 func fiStat(pool *QidPool, id fileID, info fs.FileInfo) (*lib9p.Stat, error) { 22 qid, ok := pool.lookupID(id, info.ModTime()) 23 if !ok { 24 var err error 25 qid, err = pool.addID(id, info) 26 if err != nil { 27 return nil, fmt.Errorf("allocID: %v", err) 28 } 29 } 30 31 stat := info.Sys().(*syscall.Stat_t) 32 usr, err := user.LookupId(strconv.Itoa(int(stat.Uid))) 33 if err != nil { 34 return nil, fmt.Errorf("user: %v", err) 35 } 36 if _, ok := gidCache[id]; !ok { 37 group, err := user.LookupGroupId(strconv.Itoa(int(stat.Gid))) 38 if err != nil { 39 return nil, fmt.Errorf("LookupGroupId(%d): %v", stat.Gid, err) 40 } 41 gidCache[id] = group.Name 42 } 43 return &lib9p.Stat{ 44 Type: 0, 45 Dev: 0, 46 Qid: qid, 47 Mode: info.Mode(), 48 Atime: uint32(stat.Atim.Sec), 49 Mtime: uint32(stat.Mtim.Sec), 50 Length: stat.Size, 51 Name: info.Name(), 52 Uid: usr.Username, 53 Gid: gidCache[id], 54 Muid: "", 55 }, nil 56 } 57 58 var gidCache map[fileID]string 59 60 func init() { 61 gidCache = make(map[fileID]string) 62 } 63 64 // Stat is real implementation of File.Stat. 65 func (f *File) stat() (*lib9p.FileInfo, error) { 66 ospath := filepath.Join(f.fs.rootPath, f.path) 67 fsfi, err := os.Stat(ospath) 68 if err != nil { 69 return nil, fmt.Errorf("stat: %v", err) 70 } 71 id, err := f.id() 72 if err != nil { 73 return nil, fmt.Errorf("id: %v", err) 74 } 75 stat, err := fiStat(f.fs.qidPool, id, fsfi) 76 if err != nil { 77 return nil, fmt.Errorf("fiStat: %v", err) 78 } 79 return &lib9p.FileInfo{Stat: *stat}, nil 80 } 81 82 // wstat is the real implementation of File.WStat. 83 // TODO: when error occurs, file stat should be restored. 84 func (f *File) wstat(s *lib9p.Stat) error { 85 var file *os.File 86 fi, err := f.Stat() 87 if err != nil { 88 return fmt.Errorf("stat: %v", err) 89 } 90 oldStat := fi.Sys().(*lib9p.Stat) 91 if s.Length != oldStat.Length { 92 if file == nil { // TODO: what is this? 93 file, err = os.OpenFile(path.Join(f.fs.rootPath, f.path), os.O_RDWR, 0) 94 if err != nil { 95 return err 96 } 97 defer file.Close() 98 } 99 if err := file.Truncate(s.Length); err != nil { 100 return fmt.Errorf("truncate: %v", err) 101 } 102 ret, err := file.Seek(0, 0) 103 if err != nil { 104 return fmt.Errorf("seek 0: %d, %w", ret, err) 105 } 106 } 107 if s.Mode != oldStat.Mode { 108 if file == nil { 109 file, err = os.OpenFile(path.Join(f.fs.rootPath, f.path), os.O_RDWR, 0) 110 if err != nil { 111 return err 112 } 113 defer file.Close() 114 } 115 if err := file.Chmod(s.Mode); err != nil { 116 return fmt.Errorf("chmod: %v", err) 117 } 118 } 119 // TODO: changing atime is not permitted by the protocol, but some unix 120 // utilities try to change it (e.g. mv, touch, git). 121 if s.Atime != oldStat.Atime { 122 err := os.Chtimes(path.Join(f.fs.rootPath, f.path), time.Unix(int64(s.Mtime), 0), time.Time{}) 123 if err != nil { 124 return fmt.Errorf("change atime: %v", err) 125 } 126 } 127 if s.Mtime != oldStat.Mtime { 128 err := os.Chtimes(path.Join(f.fs.rootPath, f.path), time.Time{}, time.Unix(int64(s.Mtime), 0)) 129 if err != nil { 130 return fmt.Errorf("change mtime: %v", err) 131 } 132 } 133 if s.Gid != oldStat.Gid { 134 group, err := user.LookupGroup(s.Gid) 135 if err != nil { 136 return fmt.Errorf("lookupgroup: %v", err) 137 } 138 usr, err := user.Lookup(oldStat.Uid) 139 if err != nil { 140 return fmt.Errorf("lookup user: %v", err) 141 } 142 u, err := strconv.Atoi(usr.Uid) 143 if err != nil { 144 return fmt.Errorf("convert uid: %v", err) 145 } 146 g, err := strconv.Atoi(group.Gid) 147 if err != nil { 148 return fmt.Errorf("convert gid: %v", err) 149 } 150 if err := os.Chown(filepath.Join(f.fs.rootPath, f.path), u, g); err != nil { 151 return fmt.Errorf("chown: %v", err) 152 } 153 st, err := os.Stat(filepath.Join(f.fs.rootPath, f.path)) 154 if err != nil { 155 return fmt.Errorf("stat: %v", err) 156 } 157 stat_t := st.Sys().(*syscall.Stat_t) 158 fileid := fileID{device: uint64(stat_t.Dev), inode: stat_t.Ino} 159 gidCache[fileid] = s.Gid 160 } 161 if s.Name != oldStat.Name { 162 // TODO: check neither Name contains "/" 163 oldpath := path.Join(f.fs.rootPath, path.Dir(f.path), oldStat.Name) 164 newpath := path.Join(f.fs.rootPath, path.Dir(f.path), s.Name) 165 if fi, err := os.Stat(newpath); err == nil { 166 id := idFromInfo(newpath, fi) 167 f.fs.qidPool.deleteID(id) 168 if err := os.Remove(newpath); err != nil { 169 return fmt.Errorf("remove old file %s: %v", s.Name, err) 170 } 171 } 172 if err := os.Rename(oldpath, newpath); err != nil { 173 return fmt.Errorf("rename: %v", err) 174 } 175 } 176 return nil 177 }