forked from canonical/lxd
/
container_file.go
139 lines (119 loc) · 3.06 KB
/
container_file.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package main
import (
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"syscall"
"github.com/gorilla/mux"
"github.com/lxc/lxd/shared"
)
func containerFileHandler(d *Daemon, r *http.Request) Response {
name := mux.Vars(r)["name"]
c, err := containerLXDLoad(d, name)
if err != nil {
return SmartError(err)
}
targetPath := r.FormValue("path")
if targetPath == "" {
return BadRequest(fmt.Errorf("missing path argument"))
}
if !c.IsRunning() {
return BadRequest(fmt.Errorf("container is not running"))
}
initPid := c.InitPID()
switch r.Method {
case "GET":
return containerFileGet(initPid, r, targetPath)
case "POST":
idmapset, err := c.LastIdmapSet()
if err != nil {
return InternalError(err)
}
return containerFilePut(initPid, r, targetPath, idmapset)
default:
return NotFound
}
}
func containerFileGet(pid int, r *http.Request, path string) Response {
/*
* Copy out of the ns to a temporary file, and then use that to serve
* the request from. This prevents us from having to worry about stuff
* like people breaking out of the container root by symlinks or
* ../../../s etc. in the path, since we can just rely on the kernel
* for correctness.
*/
temp, err := ioutil.TempFile("", "lxd_forkgetfile_")
if err != nil {
return InternalError(err)
}
defer temp.Close()
cmd := exec.Command(
os.Args[0],
"forkgetfile",
temp.Name(),
fmt.Sprintf("%d", pid),
path,
)
if out, err := cmd.CombinedOutput(); err != nil {
return InternalError(fmt.Errorf(strings.TrimRight(string(out), "\n")))
}
fi, err := temp.Stat()
if err != nil {
return SmartError(err)
}
/*
* Unfortunately, there's no portable way to do this:
* https://groups.google.com/forum/#!topic/golang-nuts/tGYjYyrwsGM
* https://groups.google.com/forum/#!topic/golang-nuts/ywS7xQYJkHY
*/
sb := fi.Sys().(*syscall.Stat_t)
headers := map[string]string{
"X-LXD-uid": strconv.FormatUint(uint64(sb.Uid), 10),
"X-LXD-gid": strconv.FormatUint(uint64(sb.Gid), 10),
"X-LXD-mode": fmt.Sprintf("%04o", fi.Mode()&os.ModePerm),
}
files := make([]fileResponseEntry, 1)
files[0].identifier = filepath.Base(path)
files[0].path = temp.Name()
files[0].filename = filepath.Base(path)
return FileResponse(r, files, headers, true)
}
func containerFilePut(pid int, r *http.Request, p string, idmapset *shared.IdmapSet) Response {
uid, gid, mode := shared.ParseLXDFileHeaders(r.Header)
if idmapset != nil {
uid, gid = idmapset.ShiftIntoNs(uid, gid)
}
temp, err := ioutil.TempFile("", "lxd_forkputfile_")
if err != nil {
return InternalError(err)
}
defer func() {
temp.Close()
os.Remove(temp.Name())
}()
_, err = io.Copy(temp, r.Body)
if err != nil {
return InternalError(err)
}
cmd := exec.Command(
os.Args[0],
"forkputfile",
temp.Name(),
fmt.Sprintf("%d", pid),
p,
fmt.Sprintf("%d", uid),
fmt.Sprintf("%d", gid),
fmt.Sprintf("%d", mode&os.ModePerm),
)
out, err := cmd.CombinedOutput()
if err != nil {
return InternalError(fmt.Errorf(strings.TrimRight(string(out), "\n")))
}
return EmptySyncResponse
}