blob: acede468944375da9d644eb01018a3baa9412cc3 [file] [log] [blame]
Luis Hector Chavez81efb332017-09-18 14:01:29 -07001// Copyright 2017 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "libcontainer/libcontainer_util.h"
6
7#include <errno.h>
8#include <fcntl.h>
9#if USE_device_mapper
10#include <libdevmapper.h>
11#endif
12#include <linux/loop.h>
13#include <sys/mount.h>
14#include <sys/stat.h>
15
16#include <memory>
17#include <vector>
18
19#include <base/bind.h>
20#include <base/bind_helpers.h>
21#include <base/callback_helpers.h>
22#include <base/files/scoped_file.h>
23#include <base/strings/string_number_conversions.h>
24#include <base/strings/string_split.h>
25#include <base/strings/string_util.h>
26#include <base/strings/stringprintf.h>
27
28namespace libcontainer {
29
30namespace {
31
32constexpr base::FilePath::CharType kLoopdevCtlPath[] =
33 FILE_PATH_LITERAL("/dev/loop-control");
34#if USE_device_mapper
35constexpr base::FilePath::CharType kDevMapperPath[] =
36 FILE_PATH_LITERAL("/dev/mapper/");
37#endif
38
39} // namespace
40
41int GetUsernsOutsideId(const std::string& map, int id) {
42 if (map.empty())
43 return id;
44
45 std::string map_copy = map;
46 base::StringPiece map_piece(map_copy);
47
48 for (const auto& mapping : base::SplitStringPiece(
49 map_piece, ",", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL)) {
50 std::vector<base::StringPiece> tokens = base::SplitStringPiece(
51 mapping, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
52
53 if (tokens.size() != 3)
54 return -EINVAL;
55
56 uint32_t inside, outside, length;
57 if (!base::StringToUint(tokens[0], &inside) ||
58 !base::StringToUint(tokens[1], &outside) ||
59 !base::StringToUint(tokens[2], &length)) {
60 return -EINVAL;
61 }
62
63 if (id >= inside && id <= (inside + length)) {
64 return (id - inside) + outside;
65 }
66 }
67
68 return -EINVAL;
69}
70
71int MakeDir(const base::FilePath& path, int uid, int gid, int mode) {
72 if (mkdir(path.value().c_str(), mode))
73 return -errno;
74 if (chmod(path.value().c_str(), mode))
75 return -errno;
76 if (chown(path.value().c_str(), uid, gid))
77 return -errno;
78 return 0;
79}
80
81int TouchFile(const base::FilePath& path, int uid, int gid, int mode) {
82 base::ScopedFD fd(open(path.value().c_str(), O_RDWR | O_CREAT, mode));
83 if (!fd.is_valid())
84 return -errno;
85 if (fchown(fd.get(), uid, gid))
86 return -errno;
87 return 0;
88}
89
90int LoopdevSetup(const base::FilePath& source,
91 base::FilePath* loopdev_path_out) {
92 base::ScopedFD source_fd(open(source.value().c_str(), O_RDONLY | O_CLOEXEC));
93 if (!source_fd.is_valid())
94 return -errno;
95
96 base::ScopedFD control_fd(
97 open(kLoopdevCtlPath, O_RDWR | O_NOFOLLOW | O_CLOEXEC));
98 if (!control_fd.is_valid())
99 return -errno;
100
101 while (true) {
102 int num = ioctl(control_fd.get(), LOOP_CTL_GET_FREE);
103 if (num < 0)
104 return -errno;
105
106 base::FilePath loopdev_path(base::StringPrintf("/dev/loop%i", num));
107 base::ScopedFD loop_fd(
108 open(loopdev_path.value().c_str(), O_RDONLY | O_NOFOLLOW | O_CLOEXEC));
109 if (!loop_fd.is_valid())
110 return -errno;
111
112 if (ioctl(loop_fd.get(), LOOP_SET_FD, source_fd.get()) == 0) {
113 *loopdev_path_out = loopdev_path;
114 break;
115 }
116
117 if (errno != EBUSY)
118 return -errno;
119 }
120
121 return 0;
122}
123
124int LoopdevDetach(const base::FilePath& loopdev) {
125 base::ScopedFD fd(
126 open(loopdev.value().c_str(), O_RDONLY | O_NOFOLLOW | O_CLOEXEC));
127 if (!fd.is_valid())
128 return -errno;
129 if (ioctl(fd.get(), LOOP_CLR_FD) < 0)
130 return -errno;
131
132 return 0;
133}
134
135int DeviceMapperSetup(const base::FilePath& source,
136 const std::string& verity_cmdline,
137 base::FilePath* dm_path_out,
138 std::string* dm_name_out) {
139#if USE_device_mapper
140 // Normalize the name into something unique-esque.
141 std::string dm_name =
142 base::StringPrintf("cros-containers-%s", source.value().c_str());
143 base::ReplaceChars(dm_name, "/", "_", &dm_name);
144
145 // Get the /dev path for the higher levels to mount.
146 base::FilePath dm_path = base::FilePath(kDevMapperPath).Append(dm_name);
147
148 // Insert the source path in the verity command line.
149 std::string verity = verity_cmdline;
150 base::ReplaceSubstringsAfterOffset(&verity, 0, "@DEV@", source.value());
151
152 // Extract the first three parameters for dm-verity settings.
153 char ttype[20];
154 unsigned long long start, size;
155 int n;
156 if (sscanf(verity.c_str(), "%llu %llu %10s %n", &start, &size, ttype, &n) !=
157 3) {
158 return -errno;
159 }
160
161 /* Finally create the device mapper. */
162 std::unique_ptr<struct dm_task, decltype(&dm_task_destroy)> dmt(
163 dm_task_create(DM_DEVICE_CREATE), &dm_task_destroy);
164 if (dmt == nullptr)
165 return -errno;
166
167 if (!dm_task_set_name(dmt.get(), dm_name.c_str()))
168 return -errno;
169
170 if (!dm_task_set_ro(dmt.get()))
171 return -errno;
172
173 if (!dm_task_add_target(dmt.get(), start, size, ttype, verity.c_str() + n))
174 return -errno;
175
176 uint32_t cookie = 0;
177 if (!dm_task_set_cookie(dmt.get(), &cookie, 0))
178 return -errno;
179
180 if (!dm_task_run(dmt.get()))
181 return -errno;
182
183 /* Make sure the node exists before we continue. */
184 dm_udev_wait(cookie);
185
186 *dm_path_out = dm_path;
187 *dm_name_out = dm_name;
188#endif
189 return 0;
190}
191
192// Tear down the device mapper target.
193int DeviceMapperDetach(const std::string& dm_name) {
194#if USE_device_mapper
195 struct dm_task* dmt = dm_task_create(DM_DEVICE_REMOVE);
196 if (dmt == nullptr)
197 return -errno;
198
199 base::ScopedClosureRunner teardown(
200 base::Bind(base::IgnoreResult(&dm_task_destroy), base::Unretained(dmt)));
201
202 if (!dm_task_set_name(dmt, dm_name.c_str()))
203 return -errno;
204
205 if (!dm_task_run(dmt))
206 return -errno;
207#endif
208 return 0;
209}
210
211int MountExternal(const std::string& src,
212 const std::string& dest,
213 const std::string& type,
214 unsigned long flags,
215 const std::string& data) {
216 bool remount_ro = false;
217
218 // R/O bind mounts have to be remounted since 'bind' and 'ro' can't both be
219 // specified in the original bind mount. Remount R/O after the initial mount.
220 if ((flags & MS_BIND) && (flags & MS_RDONLY)) {
221 remount_ro = true;
222 flags &= ~MS_RDONLY;
223 }
224
225 if (mount(src.c_str(),
226 dest.c_str(),
227 type.c_str(),
228 flags,
229 data.empty() ? nullptr : data.c_str()) == -1) {
230 return -errno;
231 }
232
233 if (remount_ro) {
234 flags |= MS_RDONLY;
235 if (mount(src.c_str(),
236 dest.c_str(),
237 nullptr,
238 flags | MS_REMOUNT,
239 data.empty() ? nullptr : data.c_str()) == -1) {
240 return -errno;
241 }
242 }
243
244 return 0;
245}
246
247} // namespace libcontainer