| // Copyright 2013 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // A simulated Unix-like file system for use within NaCl. |
| // |
| // The simulation is not particularly tied to NaCl other than the reuse |
| // of NaCl's definition for the Stat_t structure. |
| // |
| // The file system need never be written to disk, so it is represented as |
| // in-memory Go data structures, never in a serialized form. |
| // |
| // TODO: Perhaps support symlinks, although they muck everything up. |
| |
| package syscall |
| |
| import ( |
| "io" |
| "sync" |
| "unsafe" |
| ) |
| |
| // Provided by package runtime. |
| func now() (sec int64, nsec int32) |
| |
| // An fsys is a file system. |
| // Since there is no I/O (everything is in memory), |
| // the global lock mu protects the whole file system state, |
| // and that's okay. |
| type fsys struct { |
| mu sync.Mutex |
| root *inode // root directory |
| cwd *inode // process current directory |
| inum uint64 // number of inodes created |
| dev []func() (devFile, error) // table for opening devices |
| } |
| |
| // A devFile is the implementation required of device files |
| // like /dev/null or /dev/random. |
| type devFile interface { |
| pread([]byte, int64) (int, error) |
| pwrite([]byte, int64) (int, error) |
| } |
| |
| // An inode is a (possibly special) file in the file system. |
| type inode struct { |
| Stat_t |
| data []byte |
| dir []dirent |
| } |
| |
| // A dirent describes a single directory entry. |
| type dirent struct { |
| name string |
| inode *inode |
| } |
| |
| // An fsysFile is the fileImpl implementation backed by the file system. |
| type fsysFile struct { |
| defaultFileImpl |
| fsys *fsys |
| inode *inode |
| openmode int |
| offset int64 |
| dev devFile |
| } |
| |
| // newFsys creates a new file system. |
| func newFsys() *fsys { |
| fs := &fsys{} |
| fs.mu.Lock() |
| defer fs.mu.Unlock() |
| ip := fs.newInode() |
| ip.Mode = 0555 | S_IFDIR |
| fs.dirlink(ip, ".", ip) |
| fs.dirlink(ip, "..", ip) |
| fs.cwd = ip |
| fs.root = ip |
| return fs |
| } |
| |
| var fs = newFsys() |
| var fsinit = func() {} |
| |
| func init() { |
| // do not trigger loading of zipped file system here |
| oldFsinit := fsinit |
| defer func() { fsinit = oldFsinit }() |
| fsinit = func() {} |
| Mkdir("/dev", 0555) |
| Mkdir("/tmp", 0777) |
| mkdev("/dev/null", 0666, openNull) |
| mkdev("/dev/random", 0444, openRandom) |
| mkdev("/dev/urandom", 0444, openRandom) |
| mkdev("/dev/zero", 0666, openZero) |
| chdirEnv() |
| } |
| |
| func chdirEnv() { |
| pwd, ok := Getenv("NACLPWD") |
| if ok { |
| chdir(pwd) |
| } |
| } |
| |
| // Except where indicated otherwise, unexported methods on fsys |
| // expect fs.mu to have been locked by the caller. |
| |
| // newInode creates a new inode. |
| func (fs *fsys) newInode() *inode { |
| fs.inum++ |
| ip := &inode{ |
| Stat_t: Stat_t{ |
| Ino: fs.inum, |
| Blksize: 512, |
| }, |
| } |
| return ip |
| } |
| |
| // atime sets ip.Atime to the current time. |
| func (fs *fsys) atime(ip *inode) { |
| sec, nsec := now() |
| ip.Atime, ip.AtimeNsec = sec, int64(nsec) |
| } |
| |
| // mtime sets ip.Mtime to the current time. |
| func (fs *fsys) mtime(ip *inode) { |
| sec, nsec := now() |
| ip.Mtime, ip.MtimeNsec = sec, int64(nsec) |
| } |
| |
| // dirlookup looks for an entry in the directory dp with the given name. |
| // It returns the directory entry and its index within the directory. |
| func (fs *fsys) dirlookup(dp *inode, name string) (de *dirent, index int, err error) { |
| fs.atime(dp) |
| for i := range dp.dir { |
| de := &dp.dir[i] |
| if de.name == name { |
| fs.atime(de.inode) |
| return de, i, nil |
| } |
| } |
| return nil, 0, ENOENT |
| } |
| |
| // dirlink adds to the directory dp an entry for name pointing at the inode ip. |
| // If dp already contains an entry for name, that entry is overwritten. |
| func (fs *fsys) dirlink(dp *inode, name string, ip *inode) { |
| fs.mtime(dp) |
| fs.atime(ip) |
| ip.Nlink++ |
| for i := range dp.dir { |
| if dp.dir[i].name == name { |
| dp.dir[i] = dirent{name, ip} |
| return |
| } |
| } |
| dp.dir = append(dp.dir, dirent{name, ip}) |
| dp.dirSize() |
| } |
| |
| func (dp *inode) dirSize() { |
| dp.Size = int64(len(dp.dir)) * (8 + 8 + 2 + 256) // Dirent |
| } |
| |
| // skipelem splits path into the first element and the remainder. |
| // the returned first element contains no slashes, and the returned |
| // remainder does not begin with a slash. |
| func skipelem(path string) (elem, rest string) { |
| for len(path) > 0 && path[0] == '/' { |
| path = path[1:] |
| } |
| if len(path) == 0 { |
| return "", "" |
| } |
| i := 0 |
| for i < len(path) && path[i] != '/' { |
| i++ |
| } |
| elem, path = path[:i], path[i:] |
| for len(path) > 0 && path[0] == '/' { |
| path = path[1:] |
| } |
| return elem, path |
| } |
| |
| // namei translates a file system path name into an inode. |
| // If parent is false, the returned ip corresponds to the given name, and elem is the empty string. |
| // If parent is true, the walk stops at the next-to-last element in the name, |
| // so that ip is the parent directory and elem is the final element in the path. |
| func (fs *fsys) namei(path string, parent bool) (ip *inode, elem string, err error) { |
| // Reject NUL in name. |
| for i := 0; i < len(path); i++ { |
| if path[i] == '\x00' { |
| return nil, "", EINVAL |
| } |
| } |
| |
| // Reject empty name. |
| if path == "" { |
| return nil, "", EINVAL |
| } |
| |
| if path[0] == '/' { |
| ip = fs.root |
| } else { |
| ip = fs.cwd |
| } |
| |
| for len(path) > 0 && path[len(path)-1] == '/' { |
| path = path[:len(path)-1] |
| } |
| |
| for { |
| elem, rest := skipelem(path) |
| if elem == "" { |
| if parent && ip.Mode&S_IFMT == S_IFDIR { |
| return ip, ".", nil |
| } |
| break |
| } |
| if ip.Mode&S_IFMT != S_IFDIR { |
| return nil, "", ENOTDIR |
| } |
| if len(elem) >= 256 { |
| return nil, "", ENAMETOOLONG |
| } |
| if parent && rest == "" { |
| // Stop one level early. |
| return ip, elem, nil |
| } |
| de, _, err := fs.dirlookup(ip, elem) |
| if err != nil { |
| return nil, "", err |
| } |
| ip = de.inode |
| path = rest |
| } |
| if parent { |
| return nil, "", ENOTDIR |
| } |
| return ip, "", nil |
| } |
| |
| // open opens or creates a file with the given name, open mode, |
| // and permission mode bits. |
| func (fs *fsys) open(name string, openmode int, mode uint32) (fileImpl, error) { |
| dp, elem, err := fs.namei(name, true) |
| if err != nil { |
| return nil, err |
| } |
| var ( |
| ip *inode |
| dev devFile |
| ) |
| de, _, err := fs.dirlookup(dp, elem) |
| if err != nil { |
| if openmode&O_CREATE == 0 { |
| return nil, err |
| } |
| ip = fs.newInode() |
| ip.Mode = mode |
| fs.dirlink(dp, elem, ip) |
| if ip.Mode&S_IFMT == S_IFDIR { |
| fs.dirlink(ip, ".", ip) |
| fs.dirlink(ip, "..", dp) |
| } |
| } else { |
| ip = de.inode |
| if openmode&(O_CREATE|O_EXCL) == O_CREATE|O_EXCL { |
| return nil, EEXIST |
| } |
| if openmode&O_TRUNC != 0 { |
| if ip.Mode&S_IFMT == S_IFDIR { |
| return nil, EISDIR |
| } |
| ip.data = nil |
| } |
| if ip.Mode&S_IFMT == S_IFCHR { |
| if ip.Rdev < 0 || ip.Rdev >= int64(len(fs.dev)) || fs.dev[ip.Rdev] == nil { |
| return nil, ENODEV |
| } |
| dev, err = fs.dev[ip.Rdev]() |
| if err != nil { |
| return nil, err |
| } |
| } |
| } |
| |
| switch openmode & O_ACCMODE { |
| case O_WRONLY, O_RDWR: |
| if ip.Mode&S_IFMT == S_IFDIR { |
| return nil, EISDIR |
| } |
| } |
| |
| switch ip.Mode & S_IFMT { |
| case S_IFDIR: |
| if openmode&O_ACCMODE != O_RDONLY { |
| return nil, EISDIR |
| } |
| |
| case S_IFREG: |
| // ok |
| |
| case S_IFCHR: |
| // handled above |
| |
| default: |
| // TODO: some kind of special file |
| return nil, EPERM |
| } |
| |
| f := &fsysFile{ |
| fsys: fs, |
| inode: ip, |
| openmode: openmode, |
| dev: dev, |
| } |
| if openmode&O_APPEND != 0 { |
| f.offset = ip.Size |
| } |
| return f, nil |
| } |
| |
| // fsysFile methods to implement fileImpl. |
| |
| func (f *fsysFile) stat(st *Stat_t) error { |
| f.fsys.mu.Lock() |
| defer f.fsys.mu.Unlock() |
| *st = f.inode.Stat_t |
| return nil |
| } |
| |
| func (f *fsysFile) read(b []byte) (int, error) { |
| f.fsys.mu.Lock() |
| defer f.fsys.mu.Unlock() |
| n, err := f.preadLocked(b, f.offset) |
| f.offset += int64(n) |
| return n, err |
| } |
| |
| func ReadDirent(fd int, buf []byte) (int, error) { |
| f, err := fdToFsysFile(fd) |
| if err != nil { |
| return 0, err |
| } |
| f.fsys.mu.Lock() |
| defer f.fsys.mu.Unlock() |
| if f.inode.Mode&S_IFMT != S_IFDIR { |
| return 0, EINVAL |
| } |
| n, err := f.preadLocked(buf, f.offset) |
| f.offset += int64(n) |
| return n, err |
| } |
| |
| func (f *fsysFile) write(b []byte) (int, error) { |
| f.fsys.mu.Lock() |
| defer f.fsys.mu.Unlock() |
| n, err := f.pwriteLocked(b, f.offset) |
| f.offset += int64(n) |
| return n, err |
| } |
| |
| func (f *fsysFile) seek(offset int64, whence int) (int64, error) { |
| f.fsys.mu.Lock() |
| defer f.fsys.mu.Unlock() |
| switch whence { |
| case io.SeekCurrent: |
| offset += f.offset |
| case io.SeekEnd: |
| offset += f.inode.Size |
| } |
| if offset < 0 { |
| return 0, EINVAL |
| } |
| if offset > f.inode.Size { |
| return 0, EINVAL |
| } |
| f.offset = offset |
| return offset, nil |
| } |
| |
| func (f *fsysFile) pread(b []byte, offset int64) (int, error) { |
| f.fsys.mu.Lock() |
| defer f.fsys.mu.Unlock() |
| return f.preadLocked(b, offset) |
| } |
| |
| func (f *fsysFile) pwrite(b []byte, offset int64) (int, error) { |
| f.fsys.mu.Lock() |
| defer f.fsys.mu.Unlock() |
| return f.pwriteLocked(b, offset) |
| } |
| |
| func (f *fsysFile) preadLocked(b []byte, offset int64) (int, error) { |
| if f.openmode&O_ACCMODE == O_WRONLY { |
| return 0, EINVAL |
| } |
| if offset < 0 { |
| return 0, EINVAL |
| } |
| if f.dev != nil { |
| f.fsys.atime(f.inode) |
| f.fsys.mu.Unlock() |
| defer f.fsys.mu.Lock() |
| return f.dev.pread(b, offset) |
| } |
| if offset > f.inode.Size { |
| return 0, nil |
| } |
| if int64(len(b)) > f.inode.Size-offset { |
| b = b[:f.inode.Size-offset] |
| } |
| |
| if f.inode.Mode&S_IFMT == S_IFDIR { |
| if offset%direntSize != 0 || len(b) != 0 && len(b) < direntSize { |
| return 0, EINVAL |
| } |
| fs.atime(f.inode) |
| n := 0 |
| for len(b) >= direntSize { |
| src := f.inode.dir[int(offset/direntSize)] |
| dst := (*Dirent)(unsafe.Pointer(&b[0])) |
| dst.Ino = int64(src.inode.Ino) |
| dst.Off = offset |
| dst.Reclen = direntSize |
| for i := range dst.Name { |
| dst.Name[i] = 0 |
| } |
| copy(dst.Name[:], src.name) |
| n += direntSize |
| offset += direntSize |
| b = b[direntSize:] |
| } |
| return n, nil |
| } |
| |
| fs.atime(f.inode) |
| n := copy(b, f.inode.data[offset:]) |
| return n, nil |
| } |
| |
| func (f *fsysFile) pwriteLocked(b []byte, offset int64) (int, error) { |
| if f.openmode&O_ACCMODE == O_RDONLY { |
| return 0, EINVAL |
| } |
| if offset < 0 { |
| return 0, EINVAL |
| } |
| if f.dev != nil { |
| f.fsys.atime(f.inode) |
| f.fsys.mu.Unlock() |
| defer f.fsys.mu.Lock() |
| return f.dev.pwrite(b, offset) |
| } |
| if offset > f.inode.Size { |
| return 0, EINVAL |
| } |
| f.fsys.mtime(f.inode) |
| n := copy(f.inode.data[offset:], b) |
| if n < len(b) { |
| f.inode.data = append(f.inode.data, b[n:]...) |
| f.inode.Size = int64(len(f.inode.data)) |
| } |
| return len(b), nil |
| } |
| |
| // Standard Unix system calls. |
| |
| func Open(path string, openmode int, perm uint32) (fd int, err error) { |
| fsinit() |
| fs.mu.Lock() |
| defer fs.mu.Unlock() |
| f, err := fs.open(path, openmode, perm&0777|S_IFREG) |
| if err != nil { |
| return -1, err |
| } |
| return newFD(f), nil |
| } |
| |
| func Mkdir(path string, perm uint32) error { |
| fs.mu.Lock() |
| defer fs.mu.Unlock() |
| _, err := fs.open(path, O_CREATE|O_EXCL, perm&0777|S_IFDIR) |
| return err |
| } |
| |
| func Getcwd(buf []byte) (n int, err error) { |
| // Force package os to default to the old algorithm using .. and directory reads. |
| return 0, ENOSYS |
| } |
| |
| func Stat(path string, st *Stat_t) error { |
| fsinit() |
| fs.mu.Lock() |
| defer fs.mu.Unlock() |
| ip, _, err := fs.namei(path, false) |
| if err != nil { |
| return err |
| } |
| *st = ip.Stat_t |
| return nil |
| } |
| |
| func Lstat(path string, st *Stat_t) error { |
| return Stat(path, st) |
| } |
| |
| func unlink(path string, isdir bool) error { |
| fsinit() |
| fs.mu.Lock() |
| defer fs.mu.Unlock() |
| dp, elem, err := fs.namei(path, true) |
| if err != nil { |
| return err |
| } |
| if elem == "." || elem == ".." { |
| return EINVAL |
| } |
| de, _, err := fs.dirlookup(dp, elem) |
| if err != nil { |
| return err |
| } |
| if isdir { |
| if de.inode.Mode&S_IFMT != S_IFDIR { |
| return ENOTDIR |
| } |
| if len(de.inode.dir) != 2 { |
| return ENOTEMPTY |
| } |
| } else { |
| if de.inode.Mode&S_IFMT == S_IFDIR { |
| return EISDIR |
| } |
| } |
| de.inode.Nlink-- |
| *de = dp.dir[len(dp.dir)-1] |
| dp.dir = dp.dir[:len(dp.dir)-1] |
| dp.dirSize() |
| return nil |
| } |
| |
| func Unlink(path string) error { |
| return unlink(path, false) |
| } |
| |
| func Rmdir(path string) error { |
| return unlink(path, true) |
| } |
| |
| func Chmod(path string, mode uint32) error { |
| fsinit() |
| fs.mu.Lock() |
| defer fs.mu.Unlock() |
| ip, _, err := fs.namei(path, false) |
| if err != nil { |
| return err |
| } |
| ip.Mode = ip.Mode&^0777 | mode&0777 |
| return nil |
| } |
| |
| func Fchmod(fd int, mode uint32) error { |
| f, err := fdToFsysFile(fd) |
| if err != nil { |
| return err |
| } |
| f.fsys.mu.Lock() |
| defer f.fsys.mu.Unlock() |
| f.inode.Mode = f.inode.Mode&^0777 | mode&0777 |
| return nil |
| } |
| |
| func Chown(path string, uid, gid int) error { |
| fsinit() |
| fs.mu.Lock() |
| defer fs.mu.Unlock() |
| ip, _, err := fs.namei(path, false) |
| if err != nil { |
| return err |
| } |
| if uid != -1 { |
| ip.Uid = uint32(uid) |
| } |
| if gid != -1 { |
| ip.Gid = uint32(gid) |
| } |
| return nil |
| } |
| |
| func Fchown(fd int, uid, gid int) error { |
| fs.mu.Lock() |
| defer fs.mu.Unlock() |
| f, err := fdToFsysFile(fd) |
| if err != nil { |
| return err |
| } |
| f.fsys.mu.Lock() |
| defer f.fsys.mu.Unlock() |
| f.inode.Uid = uint32(uid) |
| f.inode.Gid = uint32(gid) |
| return nil |
| } |
| |
| func Lchown(path string, uid, gid int) error { |
| return Chown(path, uid, gid) |
| } |
| |
| func UtimesNano(path string, ts []Timespec) error { |
| if len(ts) != 2 { |
| return EINVAL |
| } |
| fsinit() |
| fs.mu.Lock() |
| defer fs.mu.Unlock() |
| ip, _, err := fs.namei(path, false) |
| if err != nil { |
| return err |
| } |
| ip.Atime = ts[0].Sec |
| ip.AtimeNsec = int64(ts[0].Nsec) |
| ip.Mtime = ts[1].Sec |
| ip.MtimeNsec = int64(ts[1].Nsec) |
| return nil |
| } |
| |
| func Link(path, link string) error { |
| fsinit() |
| fs.mu.Lock() |
| defer fs.mu.Unlock() |
| ip, _, err := fs.namei(path, false) |
| if err != nil { |
| return err |
| } |
| dp, elem, err := fs.namei(link, true) |
| if err != nil { |
| return err |
| } |
| if ip.Mode&S_IFMT == S_IFDIR { |
| return EPERM |
| } |
| _, _, err = fs.dirlookup(dp, elem) |
| if err == nil { |
| return EEXIST |
| } |
| fs.dirlink(dp, elem, ip) |
| return nil |
| } |
| |
| func Rename(from, to string) error { |
| fsinit() |
| fs.mu.Lock() |
| defer fs.mu.Unlock() |
| fdp, felem, err := fs.namei(from, true) |
| if err != nil { |
| return err |
| } |
| fde, _, err := fs.dirlookup(fdp, felem) |
| if err != nil { |
| return err |
| } |
| tdp, telem, err := fs.namei(to, true) |
| if err != nil { |
| return err |
| } |
| fs.dirlink(tdp, telem, fde.inode) |
| fde.inode.Nlink-- |
| *fde = fdp.dir[len(fdp.dir)-1] |
| fdp.dir = fdp.dir[:len(fdp.dir)-1] |
| fdp.dirSize() |
| return nil |
| } |
| |
| func (fs *fsys) truncate(ip *inode, length int64) error { |
| if length > 1e9 || ip.Mode&S_IFMT != S_IFREG { |
| return EINVAL |
| } |
| if length < int64(len(ip.data)) { |
| ip.data = ip.data[:length] |
| } else { |
| data := make([]byte, length) |
| copy(data, ip.data) |
| ip.data = data |
| } |
| ip.Size = int64(len(ip.data)) |
| return nil |
| } |
| |
| func Truncate(path string, length int64) error { |
| fsinit() |
| fs.mu.Lock() |
| defer fs.mu.Unlock() |
| ip, _, err := fs.namei(path, false) |
| if err != nil { |
| return err |
| } |
| return fs.truncate(ip, length) |
| } |
| |
| func Ftruncate(fd int, length int64) error { |
| f, err := fdToFsysFile(fd) |
| if err != nil { |
| return err |
| } |
| f.fsys.mu.Lock() |
| defer f.fsys.mu.Unlock() |
| return f.fsys.truncate(f.inode, length) |
| } |
| |
| func Chdir(path string) error { |
| fsinit() |
| return chdir(path) |
| } |
| |
| func chdir(path string) error { |
| fs.mu.Lock() |
| defer fs.mu.Unlock() |
| ip, _, err := fs.namei(path, false) |
| if err != nil { |
| return err |
| } |
| fs.cwd = ip |
| return nil |
| } |
| |
| func Fchdir(fd int) error { |
| f, err := fdToFsysFile(fd) |
| if err != nil { |
| return err |
| } |
| f.fsys.mu.Lock() |
| defer f.fsys.mu.Unlock() |
| if f.inode.Mode&S_IFMT != S_IFDIR { |
| return ENOTDIR |
| } |
| fs.cwd = f.inode |
| return nil |
| } |
| |
| func Readlink(path string, buf []byte) (n int, err error) { |
| return 0, ENOSYS |
| } |
| |
| func Symlink(path, link string) error { |
| return ENOSYS |
| } |
| |
| func Fsync(fd int) error { |
| return nil |
| } |
| |
| // Special devices. |
| |
| func mkdev(path string, mode uint32, open func() (devFile, error)) error { |
| f, err := fs.open(path, O_CREATE|O_RDONLY|O_EXCL, S_IFCHR|mode) |
| if err != nil { |
| return err |
| } |
| ip := f.(*fsysFile).inode |
| ip.Rdev = int64(len(fs.dev)) |
| fs.dev = append(fs.dev, open) |
| return nil |
| } |
| |
| type nullFile struct{} |
| |
| func openNull() (devFile, error) { return &nullFile{}, nil } |
| func (f *nullFile) close() error { return nil } |
| func (f *nullFile) pread(b []byte, offset int64) (int, error) { return 0, nil } |
| func (f *nullFile) pwrite(b []byte, offset int64) (int, error) { return len(b), nil } |
| |
| type zeroFile struct{} |
| |
| func openZero() (devFile, error) { return &zeroFile{}, nil } |
| func (f *zeroFile) close() error { return nil } |
| func (f *zeroFile) pwrite(b []byte, offset int64) (int, error) { return len(b), nil } |
| |
| func (f *zeroFile) pread(b []byte, offset int64) (int, error) { |
| for i := range b { |
| b[i] = 0 |
| } |
| return len(b), nil |
| } |
| |
| type randomFile struct{} |
| |
| func openRandom() (devFile, error) { |
| return randomFile{}, nil |
| } |
| |
| func (f randomFile) close() error { |
| return nil |
| } |
| |
| func (f randomFile) pread(b []byte, offset int64) (int, error) { |
| if err := naclGetRandomBytes(b); err != nil { |
| return 0, err |
| } |
| return len(b), nil |
| } |
| |
| func (f randomFile) pwrite(b []byte, offset int64) (int, error) { |
| return 0, EPERM |
| } |
| |
| func fdToFsysFile(fd int) (*fsysFile, error) { |
| f, err := fdToFile(fd) |
| if err != nil { |
| return nil, err |
| } |
| impl := f.impl |
| fsysf, ok := impl.(*fsysFile) |
| if !ok { |
| return nil, EINVAL |
| } |
| return fsysf, nil |
| } |
| |
| // create creates a file in the file system with the given name, mode, time, and data. |
| // It is meant to be called when initializing the file system image. |
| func create(name string, mode uint32, sec int64, data []byte) error { |
| fs.mu.Lock() |
| defer fs.mu.Unlock() |
| f, err := fs.open(name, O_CREATE|O_EXCL, mode) |
| if err != nil { |
| if mode&S_IFMT == S_IFDIR { |
| ip, _, err := fs.namei(name, false) |
| if err == nil && (ip.Mode&S_IFMT) == S_IFDIR { |
| return nil // directory already exists |
| } |
| } |
| return err |
| } |
| ip := f.(*fsysFile).inode |
| ip.Atime = sec |
| ip.Mtime = sec |
| ip.Ctime = sec |
| if len(data) > 0 { |
| ip.Size = int64(len(data)) |
| ip.data = data |
| } |
| return nil |
| } |