lib9p

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

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 }