lib9p

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

stat_unix.go (4007B)


      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 	if s.Mtime != oldStat.Mtime {
    120 		err := os.Chtimes(f.path, time.Time{}, time.Unix(int64(s.Mtime), 0))
    121 		if err != nil {
    122 			return fmt.Errorf("chtimes: %v", err)
    123 		}
    124 	}
    125 	if s.Gid != oldStat.Gid {
    126 		group, err := user.LookupGroup(s.Gid)
    127 		if err != nil {
    128 			return fmt.Errorf("lookupgroup: %v", err)
    129 		}
    130 		usr, err := user.Lookup(oldStat.Uid)
    131 		if err != nil {
    132 			return fmt.Errorf("lookup user: %v", err)
    133 		}
    134 		u, err := strconv.Atoi(usr.Uid)
    135 		if err != nil {
    136 			return fmt.Errorf("convert uid: %v", err)
    137 		}
    138 		g, err := strconv.Atoi(group.Gid)
    139 		if err != nil {
    140 			return fmt.Errorf("convert gid: %v", err)
    141 		}
    142 		if err := os.Chown(filepath.Join(f.fs.rootPath, f.path), u, g); err != nil {
    143 			return fmt.Errorf("chown: %v", err)
    144 		}
    145 		st, err := os.Stat(filepath.Join(f.fs.rootPath, f.path))
    146 		if err != nil {
    147 			return fmt.Errorf("stat: %v", err)
    148 		}
    149 		stat_t := st.Sys().(*syscall.Stat_t)
    150 		fileid := fileID{device: uint64(stat_t.Dev), inode: stat_t.Ino}
    151 		gidCache[fileid] = s.Gid
    152 	}
    153 	if s.Name != oldStat.Name {
    154 		// TODO: check neither Name contains "/"
    155 		oldpath := path.Join(f.fs.rootPath, path.Dir(f.path), oldStat.Name)
    156 		newpath := path.Join(f.fs.rootPath, path.Dir(f.path), s.Name)
    157 		if err := os.Rename(oldpath, newpath); err != nil {
    158 			return fmt.Errorf("rename: %v", err)
    159 		}
    160 	}
    161 	return nil
    162 }