Skip to content
This repository has been archived by the owner on May 12, 2021. It is now read-only.

Commit

Permalink
protocols/grpc: implement function to copy files
Browse files Browse the repository at this point in the history
CopyFileRequest can be used to copy files from the host to the workload
rootfs (guest), this request is intended to copy "static" and small
files like resolv.conf and hosts, but any other file can be copied.

fixes #432

Signed-off-by: Julio Montes <[email protected]>
  • Loading branch information
Julio Montes committed Dec 18, 2018
1 parent 29f3fe9 commit 169d755
Show file tree
Hide file tree
Showing 5 changed files with 753 additions and 157 deletions.
76 changes: 76 additions & 0 deletions grpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ var (
sysfsMemOnlinePath = "/sys/devices/system/memory"
sysfsMemoryBlockSizePath = "/sys/devices/system/memory/block_size_bytes"
sysfsConnectedCPUsPath = filepath.Join(sysfsCPUOnlinePath, "online")
containersRootfsPath = "/run"
)

type onlineResource struct {
Expand Down Expand Up @@ -1450,3 +1451,78 @@ func (a *agentGRPC) SetGuestDateTime(ctx context.Context, req *pb.SetGuestDateTi
}
return &gpb.Empty{}, nil
}

// CopyFile copies files form host to container's rootfs (guest). Files can be copied by parts, for example
// a file which size is 2MB, can be copied calling CopyFile 2 times, in the first call req.Offset is 0,
// req.FileSize is 2MB and req.Data contains the first half of the file, in the seconds call req.Offset is 1MB,
// req.FileSize is 2MB and req.Data contains the second half of the file. For security reason all write operations
// are made in a temporary file, once temporary file reaches the expected size (req.FileSize), it's moved to
// destination file (req.Path).
func (a *agentGRPC) CopyFile(ctx context.Context, req *pb.CopyFileRequest) (*gpb.Empty, error) {
// get absolute path, to avoid paths like '/run/../sbin/init'
path, err := filepath.Abs(req.Path)
if err != nil {
return emptyResp, err
}

// container's rootfs is mounted at /run, in order to avoid overwrite guest's rootfs files, only
// is possible to copy files to /run
if !strings.HasPrefix(path, containersRootfsPath) {
return emptyResp, fmt.Errorf("Only is possible to copy files into the %s directory", containersRootfsPath)
}

if err := os.MkdirAll(filepath.Dir(path), os.FileMode(req.DirMode)); err != nil {
return emptyResp, err
}

// create a temporary file and write the content.
tmpPath := path + ".tmp"
tmpFile, err := os.OpenFile(tmpPath, os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
return emptyResp, err
}

if _, err := tmpFile.WriteAt(req.Data, req.Offset); err != nil {
tmpFile.Close()
return emptyResp, err
}
tmpFile.Close()

// get temporary file information
st, err := os.Stat(tmpPath)
if err != nil {
return emptyResp, err
}

agentLog.WithFields(logrus.Fields{
"tmp-file-size": st.Size(),
"expected-size": req.FileSize,
}).Debugf("Checking temporary file size")

// if file size is not equal to the expected size means that copy file operation has not finished.
// CopyFile should be called again with new content and a different offset.
if st.Size() != req.FileSize {
return emptyResp, nil
}

if err := os.Chmod(tmpPath, os.FileMode(req.FileMode)); err != nil {
return emptyResp, err
}

if err := os.Chown(tmpPath, int(req.Uid), int(req.Gid)); err != nil {
return emptyResp, err
}

// At this point temoporary file has the expected size, atomically move it overwriting
// the destination.
agentLog.WithFields(logrus.Fields{
"tmp-path": tmpPath,
"des-path": path,
}).Debugf("Moving temporary file")

if err := os.Rename(tmpPath, path); err != nil {
return emptyResp, err
}

return emptyResp, nil
}
50 changes: 50 additions & 0 deletions grpc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -786,3 +786,53 @@ func TestPosixRlimitsToRlimits(t *testing.T) {

assert.Equal(rlimits, expectedRlimits)
}

func TestCopyFile(t *testing.T) {
assert := assert.New(t)

oldContainersRootfsPath := containersRootfsPath
containersRootfsPath = "/tmp"
defer func() {
containersRootfsPath = oldContainersRootfsPath
}()

a := &agentGRPC{}
req := &pb.CopyFileRequest{
DirMode: 0755,
FileMode: 0755,
Uid: int32(os.Getuid()),
Gid: int32(os.Getgid()),
}

_, err := a.CopyFile(context.Background(), req)
assert.Error(err)

dir, err := ioutil.TempDir("", "copy")
assert.NoError(err)
defer os.RemoveAll(dir)

req.Path = filepath.Join(dir, "file")

part1 := []byte("hello")
part2 := []byte("world")
req.FileSize = int64(len(part1) + len(part2))

// send first part
req.Offset = 0
req.Data = part1
_, err = a.CopyFile(context.Background(), req)
assert.NoError(err)

// send second part
req.Offset = int64(len(part1))
req.Data = part2
_, err = a.CopyFile(context.Background(), req)
assert.NoError(err)

// check file exist
assert.FileExists(req.Path)
content, err := ioutil.ReadFile(req.Path)
assert.NoError(err)
// check file's content
assert.Equal(content, append(part1, part2...))
}
Loading

0 comments on commit 169d755

Please sign in to comment.