Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
go/src/syscall/fs_nacl.go
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
846 lines (761 sloc)
17.2 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 | |
} |