Line |
Branch |
Exec |
Source |
1 |
|
|
/** |
2 |
|
|
* This file is part of the CernVM File System. |
3 |
|
|
*/ |
4 |
|
|
|
5 |
|
|
|
6 |
|
|
#include "cmd_enter.h" |
7 |
|
|
|
8 |
|
|
#include <dirent.h> |
9 |
|
|
#include <errno.h> |
10 |
|
|
#include <fcntl.h> |
11 |
|
|
#include <linux/limits.h> |
12 |
|
|
#include <poll.h> |
13 |
|
|
#include <sched.h> |
14 |
|
|
#include <signal.h> |
15 |
|
|
#include <sys/mount.h> |
16 |
|
|
#include <unistd.h> |
17 |
|
|
|
18 |
|
|
|
19 |
|
|
#include <sys/wait.h> |
20 |
|
|
|
21 |
|
|
#include <algorithm> |
22 |
|
|
#include <cassert> |
23 |
|
|
#include <cstdio> |
24 |
|
|
#include <map> |
25 |
|
|
#include <set> |
26 |
|
|
#include <string> |
27 |
|
|
#include <vector> |
28 |
|
|
|
29 |
|
|
#include "backoff.h" |
30 |
|
|
#include "options.h" |
31 |
|
|
#include "publish/except.h" |
32 |
|
|
#include "publish/repository.h" |
33 |
|
|
#include "publish/settings.h" |
34 |
|
|
#include "sanitizer.h" |
35 |
|
|
#include "util/logging.h" |
36 |
|
|
#include "util/namespace.h" |
37 |
|
|
#include "util/platform.h" |
38 |
|
|
#include "util/posix.h" |
39 |
|
|
#include "util/string.h" |
40 |
|
|
|
41 |
|
|
using namespace std; // NOLINT |
42 |
|
|
|
43 |
|
|
namespace { |
44 |
|
|
|
45 |
|
|
/** |
46 |
|
|
* The parent pid and init pid in the outer PID namespace |
47 |
|
|
*/ |
48 |
|
|
struct AnchorPid { |
49 |
|
|
pid_t parent_pid; |
50 |
|
|
pid_t init_pid; |
51 |
|
|
}; |
52 |
|
|
|
53 |
|
✗ |
static AnchorPid EnterRootContainer() { |
54 |
|
✗ |
uid_t euid = geteuid(); |
55 |
|
✗ |
uid_t egid = getegid(); |
56 |
|
✗ |
NamespaceFailures failure = CreateUserNamespace(0, 0); |
57 |
|
✗ |
if (failure != kFailNsOk) { |
58 |
|
✗ |
throw publish::EPublish("cannot create root user namespace (" + |
59 |
|
✗ |
StringifyInt(failure) + " / " + StringifyInt(errno) + ") [euid=" + |
60 |
|
✗ |
StringifyInt(euid) + ", egid=" + StringifyInt(egid) + "]"); |
61 |
|
|
} |
62 |
|
|
|
63 |
|
✗ |
bool rvb = CreateMountNamespace(); |
64 |
|
✗ |
if (!rvb) throw publish::EPublish("cannot create mount namespace"); |
65 |
|
|
int fd; |
66 |
|
✗ |
rvb = CreatePidNamespace(&fd); |
67 |
|
✗ |
if (!rvb) throw publish::EPublish("cannot create pid namespace"); |
68 |
|
|
AnchorPid anchor_pid; |
69 |
|
✗ |
int rvi = SafeRead(fd, &anchor_pid.parent_pid, sizeof(pid_t)); |
70 |
|
✗ |
if (rvi != sizeof(pid_t)) |
71 |
|
✗ |
throw publish::EPublish("cannot initialize pid namespace"); |
72 |
|
✗ |
rvi = SafeRead(fd, &anchor_pid.init_pid, sizeof(pid_t)); |
73 |
|
✗ |
if (rvi != sizeof(pid_t)) |
74 |
|
✗ |
throw publish::EPublish("cannot initialize pid namespace"); |
75 |
|
✗ |
close(fd); |
76 |
|
✗ |
return anchor_pid; |
77 |
|
|
} |
78 |
|
|
|
79 |
|
✗ |
static void EnsureDirectory(const std::string &path) { |
80 |
|
✗ |
bool rv = MkdirDeep(path, 0700, true /* veryfy_writable */); |
81 |
|
✗ |
if (!rv) |
82 |
|
✗ |
throw publish::EPublish("cannot create directory " + path); |
83 |
|
|
} |
84 |
|
|
|
85 |
|
✗ |
static void RemoveSingle(const std::string &path) { |
86 |
|
|
platform_stat64 info; |
87 |
|
✗ |
int retval = platform_lstat(path.c_str(), &info); |
88 |
|
✗ |
if (retval != 0) { |
89 |
|
✗ |
if (errno == ENOENT) |
90 |
|
✗ |
return; |
91 |
|
✗ |
throw publish::EPublish("cannot remove " + path); |
92 |
|
|
} |
93 |
|
|
|
94 |
|
✗ |
int rv = 0; |
95 |
|
✗ |
if (S_ISDIR(info.st_mode)) |
96 |
|
✗ |
rv = rmdir(path.c_str()); |
97 |
|
|
else |
98 |
|
✗ |
rv = unlink(path.c_str()); |
99 |
|
|
|
100 |
|
✗ |
if (rv == 0 || errno == ENOENT) |
101 |
|
✗ |
return; |
102 |
|
|
|
103 |
|
✗ |
throw publish::EPublish( |
104 |
|
✗ |
"cannot remove " + path + " (" + StringifyInt(errno) + ")"); |
105 |
|
|
} |
106 |
|
|
|
107 |
|
|
|
108 |
|
✗ |
static void RemoveUnderlay( |
109 |
|
|
const std::string &path, const std::vector<std::string> &new_paths) |
110 |
|
|
{ |
111 |
|
✗ |
std::vector<std::string> all_mounts = platform_mountlist(); |
112 |
|
✗ |
std::vector<std::string> umount_targets; |
113 |
|
✗ |
for (unsigned i = 0; i < all_mounts.size(); ++i) { |
114 |
|
✗ |
if (HasPrefix(all_mounts[i], path + "/", false /* ignore_case */)) |
115 |
|
✗ |
umount_targets.push_back(all_mounts[i]); |
116 |
|
|
} |
117 |
|
|
|
118 |
|
✗ |
std::sort(umount_targets.begin(), umount_targets.end()); |
119 |
|
✗ |
std::vector<std::string>::reverse_iterator iter = umount_targets.rbegin(); |
120 |
|
✗ |
std::vector<std::string>::reverse_iterator iend = umount_targets.rend(); |
121 |
|
✗ |
for (; iter != iend; ++iter) { |
122 |
|
✗ |
bool rvb = platform_umount_lazy(iter->c_str()); |
123 |
|
|
// Some sub mounts, e.g. /sys/kernel/tracing, cannot be unmounted |
124 |
|
✗ |
if (!rvb && errno != EINVAL && errno != EACCES) { |
125 |
|
✗ |
throw publish::EPublish( |
126 |
|
✗ |
"cannot unmount " + *iter + " (" + StringifyInt(errno) + ")"); |
127 |
|
|
} |
128 |
|
|
} |
129 |
|
|
|
130 |
|
✗ |
std::vector<std::string> sorted_new_paths(new_paths); |
131 |
|
✗ |
std::sort(sorted_new_paths.begin(), sorted_new_paths.end()); |
132 |
|
✗ |
iter = sorted_new_paths.rbegin(); |
133 |
|
✗ |
iend = sorted_new_paths.rend(); |
134 |
|
✗ |
for (; iter != iend; ++iter) { |
135 |
|
✗ |
std::string p = *iter; |
136 |
|
✗ |
while (p.length() > path.length()) { |
137 |
|
✗ |
assert(HasPrefix(p, path, false /*ignore_case*/)); |
138 |
|
|
// The parent path might not be empty until all children are visited |
139 |
|
|
try { |
140 |
|
✗ |
RemoveSingle(p); |
141 |
|
✗ |
} catch (const publish::EPublish &e) { |
142 |
|
✗ |
if (errno != ENOTEMPTY) |
143 |
|
✗ |
throw; |
144 |
|
|
} |
145 |
|
✗ |
p = GetParentPath(p); |
146 |
|
|
} |
147 |
|
|
} |
148 |
|
✗ |
RemoveSingle(path); |
149 |
|
|
} |
150 |
|
|
|
151 |
|
|
} // anonymous namespace |
152 |
|
|
|
153 |
|
|
|
154 |
|
|
namespace publish { |
155 |
|
|
|
156 |
|
✗ |
void CmdEnter::CreateUnderlay( |
157 |
|
|
const std::string &source_dir, |
158 |
|
|
const std::string &dest_dir, |
159 |
|
|
const std::vector<std::string> &empty_dirs, |
160 |
|
|
std::vector<std::string> *new_paths) |
161 |
|
|
{ |
162 |
|
|
LogCvmfs(kLogCvmfs, kLogDebug, "underlay: entry %s --> %s", |
163 |
|
|
source_dir.c_str(), dest_dir.c_str()); |
164 |
|
|
|
165 |
|
|
// For an empty directory /cvmfs/atlas.cern.ch, we are going to store "/cvmfs" |
166 |
|
✗ |
std::vector<std::string> empty_toplevel_dirs; |
167 |
|
✗ |
for (unsigned i = 0; i < empty_dirs.size(); ++i) { |
168 |
|
✗ |
std::string toplevel_dir = empty_dirs[i]; |
169 |
|
✗ |
while (!GetParentPath(toplevel_dir).empty()) |
170 |
|
✗ |
toplevel_dir = GetParentPath(toplevel_dir); |
171 |
|
✗ |
empty_toplevel_dirs.push_back(toplevel_dir); |
172 |
|
|
|
173 |
|
|
// We create $DEST/cvmfs (top-level dir) |
174 |
|
✗ |
std::string dest_empty_dir = dest_dir + toplevel_dir; |
175 |
|
|
LogCvmfs(kLogCvmfs, kLogDebug, "underlay: mkdir %s", |
176 |
|
|
dest_empty_dir.c_str()); |
177 |
|
✗ |
EnsureDirectory(dest_empty_dir); |
178 |
|
✗ |
new_paths->push_back(dest_empty_dir); |
179 |
|
|
} |
180 |
|
|
|
181 |
|
✗ |
std::vector<std::string> names; |
182 |
|
✗ |
std::vector<mode_t> modes; |
183 |
|
|
// In a recursive call, the source directory might not exist, which is fine |
184 |
|
✗ |
std::string d = source_dir.empty() ? "/" : source_dir; |
185 |
|
✗ |
if (DirectoryExists(d)) { |
186 |
|
✗ |
bool rv = ListDirectory(d, &names, &modes); |
187 |
|
✗ |
if (!rv) |
188 |
|
✗ |
throw EPublish("cannot list directory " + d); |
189 |
|
|
} |
190 |
|
|
// We need to order creating the bindmounts such that the destination itself |
191 |
|
|
// is handled first; otherwise, the recursive bind mount to the session dir |
192 |
|
|
// creates all other sub bind mounts again. |
193 |
|
✗ |
for (unsigned i = 0; i < names.size(); ++i) { |
194 |
|
✗ |
std::string source = source_dir + "/" + names[i] + "/"; |
195 |
|
✗ |
std::string dest = dest_dir + "/" + names[i] + "/"; |
196 |
|
✗ |
if (HasPrefix(dest, source, false /* ignore_case */)) { |
197 |
|
✗ |
iter_swap(names.begin(), names.begin() + i); |
198 |
|
✗ |
iter_swap(modes.begin(), modes.begin() + i); |
199 |
|
✗ |
break; |
200 |
|
|
} |
201 |
|
|
} |
202 |
|
|
|
203 |
|
|
// List the contents of the source directory |
204 |
|
|
// 1. Symlinks are created as they are |
205 |
|
|
// 2. Directories become empty directories and are bind-mounted |
206 |
|
|
// 3. File become empty regular files and are bind-mounted |
207 |
|
✗ |
for (unsigned i = 0; i < names.size(); ++i) { |
208 |
|
✗ |
if (std::find(empty_toplevel_dirs.begin(), empty_toplevel_dirs.end(), |
209 |
|
✗ |
std::string("/") + names[i]) != empty_toplevel_dirs.end()) |
210 |
|
|
{ |
211 |
|
✗ |
continue; |
212 |
|
|
} |
213 |
|
|
|
214 |
|
✗ |
std::string source = source_dir + "/" + names[i]; |
215 |
|
✗ |
std::string dest = dest_dir + "/" + names[i]; |
216 |
|
✗ |
new_paths->push_back(dest); |
217 |
|
✗ |
if (S_ISLNK(modes[i])) { |
218 |
|
|
char buf[PATH_MAX + 1]; |
219 |
|
✗ |
ssize_t nchars = readlink(source.c_str(), buf, PATH_MAX); |
220 |
|
✗ |
if (nchars < 0) |
221 |
|
✗ |
throw EPublish("cannot read symlink " + source); |
222 |
|
✗ |
buf[nchars] = '\0'; |
223 |
|
✗ |
SymlinkForced(std::string(buf), dest); |
224 |
|
|
} else { |
225 |
|
✗ |
if (S_ISDIR(modes[i])) { |
226 |
|
✗ |
EnsureDirectory(dest); |
227 |
|
|
} else { |
228 |
|
✗ |
CreateFile(dest, 0600, false /* ignore_failure */); |
229 |
|
|
} |
230 |
|
|
LogCvmfs(kLogCvmfs, kLogDebug, "underlay: %s --> %s", |
231 |
|
|
source.c_str(), dest.c_str()); |
232 |
|
✗ |
bool rv = BindMount(source, dest); |
233 |
|
✗ |
if (!rv) { |
234 |
|
✗ |
throw EPublish("cannot bind mount " + source + " --> " + dest + |
235 |
|
✗ |
" (" + StringifyInt(errno) + ")"); |
236 |
|
|
} |
237 |
|
|
} |
238 |
|
|
} |
239 |
|
|
|
240 |
|
|
// Recurse into the directory trees containing empty directories |
241 |
|
|
// CreateUnderlay($SOURCE/cvmfs, $DEST/cvmfs, /atlas.cern.ch) |
242 |
|
✗ |
for (unsigned i = 0; i < empty_toplevel_dirs.size(); ++i) { |
243 |
|
✗ |
std::string toplevel_dir = empty_toplevel_dirs[i]; |
244 |
|
✗ |
std::vector<std::string> empty_sub_dir; |
245 |
|
✗ |
empty_sub_dir.push_back(empty_dirs[i].substr(toplevel_dir.length())); |
246 |
|
✗ |
if (!empty_sub_dir[0].empty()) { |
247 |
|
✗ |
CreateUnderlay(source_dir + toplevel_dir, |
248 |
|
✗ |
dest_dir + toplevel_dir, |
249 |
|
|
empty_sub_dir, |
250 |
|
|
new_paths); |
251 |
|
|
} |
252 |
|
|
} |
253 |
|
|
} |
254 |
|
|
|
255 |
|
✗ |
void CmdEnter::WriteCvmfsConfig(const std::string &extra_config) { |
256 |
|
|
BashOptionsManager options_manager( |
257 |
|
✗ |
new DefaultOptionsTemplateManager(fqrn_)); |
258 |
|
✗ |
options_manager.ParseDefault(fqrn_); |
259 |
|
✗ |
if (!extra_config.empty()) |
260 |
|
✗ |
options_manager.ParsePath(extra_config, false /* external */); |
261 |
|
|
|
262 |
|
✗ |
options_manager.SetValue("CVMFS_MOUNT_DIR", |
263 |
|
✗ |
settings_spool_area_.readonly_mnt()); |
264 |
|
✗ |
options_manager.SetValue("CVMFS_AUTO_UPDATE", "no"); |
265 |
|
✗ |
options_manager.SetValue("CVMFS_NFS_SOURCE", "no"); |
266 |
|
✗ |
options_manager.SetValue("CVMFS_HIDE_MAGIC_XATTRS", "yes"); |
267 |
|
✗ |
options_manager.SetValue("CVMFS_SERVER_CACHE_MODE", "yes"); |
268 |
|
✗ |
options_manager.SetValue("CVMFS_CLAIM_OWNERSHIP", "yes"); |
269 |
|
✗ |
options_manager.SetValue("CVMFS_USYSLOG", settings_spool_area_.client_log()); |
270 |
|
✗ |
options_manager.SetValue("CVMFS_RELOAD_SOCKETS", |
271 |
|
✗ |
settings_spool_area_.cache_dir()); |
272 |
|
✗ |
options_manager.SetValue("CVMFS_WORKSPACE", settings_spool_area_.cache_dir()); |
273 |
|
✗ |
options_manager.SetValue("CVMFS_CACHE_PRIMARY", "private"); |
274 |
|
✗ |
options_manager.SetValue("CVMFS_CACHE_private_TYPE", "posix"); |
275 |
|
✗ |
options_manager.SetValue("CVMFS_CACHE_private_BASE", |
276 |
|
✗ |
settings_spool_area_.cache_dir()); |
277 |
|
✗ |
options_manager.SetValue("CVMFS_CACHE_private_SHARED", "on"); |
278 |
|
✗ |
options_manager.SetValue("CVMFS_CACHE_private_QUOTA_LIMIT", "4000"); |
279 |
|
|
|
280 |
|
✗ |
bool rv = SafeWriteToFile(options_manager.Dump(), |
281 |
|
✗ |
settings_spool_area_.client_config(), |
282 |
|
|
kPrivateFileMode); |
283 |
|
✗ |
if (!rv) { |
284 |
|
✗ |
throw EPublish("cannot write client config to " + |
285 |
|
✗ |
settings_spool_area_.client_config()); |
286 |
|
|
} |
287 |
|
|
} |
288 |
|
|
|
289 |
|
|
|
290 |
|
✗ |
void CmdEnter::MountCvmfs() { |
291 |
|
✗ |
if (FindExecutable(cvmfs2_binary_).empty()) { |
292 |
|
✗ |
throw EPublish("cannot find executable " + cvmfs2_binary_, |
293 |
|
✗ |
EPublish::kFailMissingDependency); |
294 |
|
|
} |
295 |
|
|
|
296 |
|
✗ |
int fd_stdout = open(stdout_path_.c_str(), O_CREAT | O_APPEND | O_WRONLY, |
297 |
|
✗ |
kPrivateFileMode); |
298 |
|
✗ |
int fd_stderr = open(stderr_path_.c_str(), O_CREAT | O_APPEND | O_WRONLY, |
299 |
|
✗ |
kPrivateFileMode); |
300 |
|
|
|
301 |
|
✗ |
std::string cvmfs_config = settings_spool_area_.client_config(); |
302 |
|
|
|
303 |
|
✗ |
std::vector<std::string> cmdline; |
304 |
|
✗ |
cmdline.push_back(cvmfs2_binary_); |
305 |
|
✗ |
cmdline.push_back("-o"); |
306 |
|
✗ |
cmdline.push_back("allow_other,config=" + cvmfs_config); |
307 |
|
✗ |
cmdline.push_back(fqrn_); |
308 |
|
✗ |
cmdline.push_back(settings_spool_area_.readonly_mnt()); |
309 |
|
✗ |
std::set<int> preserved_fds; |
310 |
|
✗ |
preserved_fds.insert(0); |
311 |
|
✗ |
preserved_fds.insert(1); |
312 |
|
✗ |
preserved_fds.insert(2); |
313 |
|
✗ |
std::map<int, int> map_fds; |
314 |
|
✗ |
map_fds[fd_stdout] = 1; |
315 |
|
✗ |
map_fds[fd_stderr] = 2; |
316 |
|
|
pid_t pid_child; |
317 |
|
✗ |
bool rvb = ManagedExec(cmdline, preserved_fds, map_fds, |
318 |
|
|
false /* drop_credentials */, false /* clear_env */, |
319 |
|
|
false /* double_fork */, |
320 |
|
|
&pid_child); |
321 |
|
✗ |
if (!rvb) { |
322 |
|
✗ |
close(fd_stdout); |
323 |
|
✗ |
close(fd_stderr); |
324 |
|
✗ |
throw EPublish("cannot run " + cvmfs2_binary_); |
325 |
|
|
} |
326 |
|
✗ |
int exit_code = WaitForChild(pid_child); |
327 |
|
✗ |
close(fd_stdout); |
328 |
|
✗ |
close(fd_stderr); |
329 |
|
✗ |
if (exit_code != 0) { |
330 |
|
✗ |
throw EPublish("cannot mount cvmfs read-only branch (" + |
331 |
|
✗ |
StringifyInt(exit_code) + ")\n" + |
332 |
|
✗ |
" command: `" + JoinStrings(cmdline, " ").c_str() + "`"); |
333 |
|
|
} |
334 |
|
|
|
335 |
|
✗ |
rvb = BindMount(settings_spool_area_.readonly_mnt(), |
336 |
|
✗ |
rootfs_dir_ + settings_spool_area_.readonly_mnt()); |
337 |
|
✗ |
if (!rvb) |
338 |
|
✗ |
throw EPublish("cannot map CernVM-FS mount point"); |
339 |
|
|
} |
340 |
|
|
|
341 |
|
|
|
342 |
|
✗ |
void CmdEnter::MountOverlayfs() { |
343 |
|
✗ |
if (FindExecutable(overlayfs_binary_).empty()) { |
344 |
|
✗ |
throw EPublish("cannot find executable " + overlayfs_binary_, |
345 |
|
✗ |
EPublish::kFailMissingDependency); |
346 |
|
|
} |
347 |
|
|
|
348 |
|
✗ |
int fd_stdout = open(stdout_path_.c_str(), O_CREAT | O_APPEND | O_WRONLY, |
349 |
|
✗ |
kPrivateFileMode); |
350 |
|
✗ |
int fd_stderr = open(stderr_path_.c_str(), O_CREAT | O_APPEND | O_WRONLY, |
351 |
|
✗ |
kPrivateFileMode); |
352 |
|
|
|
353 |
|
✗ |
std::vector<std::string> cmdline; |
354 |
|
✗ |
cmdline.push_back(overlayfs_binary_); |
355 |
|
✗ |
cmdline.push_back("-o"); |
356 |
|
✗ |
cmdline.push_back(string("lowerdir=") + settings_spool_area_.readonly_mnt() + |
357 |
|
✗ |
",upperdir=" + settings_spool_area_.scratch_dir() + |
358 |
|
✗ |
",workdir=" + settings_spool_area_.ovl_work_dir()); |
359 |
|
✗ |
cmdline.push_back(rootfs_dir_ + settings_spool_area_.union_mnt()); |
360 |
|
✗ |
std::set<int> preserved_fds; |
361 |
|
✗ |
preserved_fds.insert(0); |
362 |
|
✗ |
preserved_fds.insert(1); |
363 |
|
✗ |
preserved_fds.insert(2); |
364 |
|
✗ |
std::map<int, int> map_fds; |
365 |
|
✗ |
map_fds[fd_stdout] = 1; |
366 |
|
✗ |
map_fds[fd_stderr] = 2; |
367 |
|
|
pid_t pid_child; |
368 |
|
✗ |
bool rvb = ManagedExec(cmdline, preserved_fds, map_fds, |
369 |
|
|
true /* drop_credentials */, false /* clear_env */, |
370 |
|
|
false /* double_fork */, |
371 |
|
|
&pid_child); |
372 |
|
✗ |
if (!rvb) { |
373 |
|
✗ |
close(fd_stdout); |
374 |
|
✗ |
close(fd_stderr); |
375 |
|
✗ |
throw EPublish("cannot run " + overlayfs_binary_); |
376 |
|
|
} |
377 |
|
✗ |
int exit_code = WaitForChild(pid_child); |
378 |
|
✗ |
close(fd_stdout); |
379 |
|
✗ |
close(fd_stderr); |
380 |
|
✗ |
if (exit_code != 0) { |
381 |
|
✗ |
throw EPublish("cannot mount overlay file system (" + |
382 |
|
✗ |
StringifyInt(exit_code) + ")\n" + |
383 |
|
✗ |
" command: `" + JoinStrings(cmdline, " ").c_str() + "`"); |
384 |
|
|
} |
385 |
|
|
} |
386 |
|
|
|
387 |
|
|
|
388 |
|
✗ |
std::string CmdEnter::GetCvmfsXattr(const std::string &name) { |
389 |
|
✗ |
std::string xattr; |
390 |
|
✗ |
bool rvb = platform_getxattr(settings_spool_area_.readonly_mnt(), |
391 |
|
✗ |
std::string("user.") + name, &xattr); |
392 |
|
✗ |
if (!rvb) { |
393 |
|
✗ |
throw EPublish("cannot get extrended attribute " + name + " from " + |
394 |
|
✗ |
settings_spool_area_.readonly_mnt()); |
395 |
|
|
} |
396 |
|
✗ |
return xattr; |
397 |
|
|
} |
398 |
|
|
|
399 |
|
|
|
400 |
|
✗ |
void CmdEnter::CleanupSession( |
401 |
|
|
bool keep_logs, const std::vector<std::string> &new_paths) |
402 |
|
|
{ |
403 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout | kLogNoLinebreak, |
404 |
|
|
"Cleaning out session directory... "); |
405 |
|
|
|
406 |
|
✗ |
std::string pid_xattr; |
407 |
|
✗ |
bool rvb = platform_getxattr(settings_spool_area_.readonly_mnt(), |
408 |
|
|
"user.pid", &pid_xattr); |
409 |
|
✗ |
if (!rvb) |
410 |
|
✗ |
throw EPublish("cannot find CernVM-FS process"); |
411 |
|
✗ |
pid_t pid_cvmfs = String2Uint64(GetCvmfsXattr("pid")); |
412 |
|
|
|
413 |
|
✗ |
const std::string union_mnt = rootfs_dir_ + settings_spool_area_.union_mnt(); |
414 |
|
✗ |
rvb = platform_umount_lazy(union_mnt.c_str()); |
415 |
|
✗ |
if (!rvb) |
416 |
|
✗ |
throw EPublish("cannot unmount overlayfs on " + union_mnt); |
417 |
|
✗ |
rvb = platform_umount_lazy( |
418 |
|
✗ |
(rootfs_dir_ + settings_spool_area_.readonly_mnt()).c_str()); |
419 |
|
✗ |
if (!rvb) { |
420 |
|
✗ |
throw EPublish("cannot unmount mapped CernVM-FS on " + |
421 |
|
✗ |
rootfs_dir_ + settings_spool_area_.readonly_mnt()); |
422 |
|
|
} |
423 |
|
✗ |
rvb = platform_umount_lazy(settings_spool_area_.readonly_mnt().c_str()); |
424 |
|
✗ |
if (!rvb) { |
425 |
|
✗ |
throw EPublish("cannot unmount CernVM-FS on " + |
426 |
|
✗ |
settings_spool_area_.readonly_mnt()); |
427 |
|
|
} |
428 |
|
|
|
429 |
|
✗ |
BackoffThrottle backoff; |
430 |
|
✗ |
while (ProcessExists(pid_cvmfs)) { |
431 |
|
✗ |
backoff.Throttle(); |
432 |
|
|
} |
433 |
|
|
|
434 |
|
✗ |
RemoveSingle(settings_spool_area_.readonly_mnt()); |
435 |
|
✗ |
RemoveSingle(settings_spool_area_.client_config()); |
436 |
|
✗ |
RemoveSingle(env_conf_); |
437 |
|
✗ |
rvb = RemoveTree(settings_spool_area_.ovl_work_dir()); |
438 |
|
✗ |
if (!rvb) |
439 |
|
✗ |
throw EPublish("cannot remove " + settings_spool_area_.ovl_work_dir()); |
440 |
|
✗ |
rvb = RemoveTree(settings_spool_area_.scratch_base()); |
441 |
|
✗ |
if (!rvb) |
442 |
|
✗ |
throw EPublish("cannot remove " + settings_spool_area_.scratch_base()); |
443 |
|
✗ |
rvb = RemoveTree(settings_spool_area_.cache_dir()); |
444 |
|
✗ |
if (!rvb) |
445 |
|
✗ |
throw EPublish("cannot remove " + settings_spool_area_.cache_dir()); |
446 |
|
✗ |
RemoveUnderlay(rootfs_dir_, new_paths); |
447 |
|
✗ |
rvb = RemoveTree(settings_spool_area_.tmp_dir()); |
448 |
|
✗ |
if (!rvb) |
449 |
|
✗ |
throw EPublish("cannot remove " + settings_spool_area_.tmp_dir()); |
450 |
|
|
|
451 |
|
✗ |
if (keep_logs) { |
452 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, "[logs available in %s]", |
453 |
|
|
settings_spool_area_.log_dir().c_str()); |
454 |
|
✗ |
return; |
455 |
|
|
} |
456 |
|
|
|
457 |
|
✗ |
rvb = RemoveTree(settings_spool_area_.log_dir()); |
458 |
|
✗ |
RemoveSingle(session_dir_ + "/session_pid"); |
459 |
|
✗ |
RemoveSingle(session_dir_ + "/" + fqrn_ + "/server.conf"); |
460 |
|
✗ |
RemoveSingle(session_dir_ + "/" + fqrn_); |
461 |
|
✗ |
RemoveSingle(session_dir_ + "/session_token"); |
462 |
|
✗ |
RemoveSingle(session_dir_ + "/in_transaction.lock"); |
463 |
|
✗ |
RemoveSingle(session_dir_ + "/shellaction.marker"); |
464 |
|
✗ |
RemoveSingle(session_dir_); |
465 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, "[done]"); |
466 |
|
|
} |
467 |
|
|
|
468 |
|
|
|
469 |
|
✗ |
int CmdEnter::Main(const Options &options) { |
470 |
|
✗ |
fqrn_ = options.plain_args()[0].value_str; |
471 |
|
✗ |
sanitizer::RepositorySanitizer sanitizer; |
472 |
|
✗ |
if (!sanitizer.IsValid(fqrn_)) { |
473 |
|
✗ |
throw EPublish("malformed repository name: " + fqrn_); |
474 |
|
|
} |
475 |
|
|
|
476 |
|
|
bool rvb; |
477 |
|
|
|
478 |
|
|
// We cannot have any capabilities or else we are not allowed to write |
479 |
|
|
// to /proc/self/setgroups and /proc/self/[u|g]id_map when creating a user |
480 |
|
|
// namespace |
481 |
|
✗ |
Env::DropCapabilities(); |
482 |
|
|
|
483 |
|
✗ |
if (options.Has("cvmfs2")) { |
484 |
|
✗ |
cvmfs2_binary_ = options.GetString("cvmfs2"); |
485 |
|
|
// Lucky guess: library in the same directory than the binary, |
486 |
|
|
// but don't overwrite an explicit setting |
487 |
|
✗ |
setenv("CVMFS_LIBRARY_PATH", GetParentPath(cvmfs2_binary_).c_str(), 0); |
488 |
|
|
} |
489 |
|
|
|
490 |
|
|
// Prepare the session directory |
491 |
|
✗ |
std::string workspace = GetHomeDirectory() + "/.cvmfs/" + fqrn_; |
492 |
|
✗ |
EnsureDirectory(workspace); |
493 |
|
✗ |
session_dir_ = CreateTempDir(workspace + "/session"); |
494 |
|
✗ |
if (session_dir_.empty()) |
495 |
|
✗ |
throw EPublish("cannot create session directory in " + workspace); |
496 |
|
✗ |
settings_spool_area_.SetUnionMount(std::string("/cvmfs/") + fqrn_); |
497 |
|
✗ |
settings_spool_area_.SetSpoolArea(session_dir_); |
498 |
|
✗ |
settings_spool_area_.EnsureDirectories(); |
499 |
|
✗ |
rootfs_dir_ = session_dir_ + "/rootfs"; |
500 |
|
✗ |
EnsureDirectory(rootfs_dir_); |
501 |
|
✗ |
stdout_path_ = settings_spool_area_.log_dir() + "/stdout.log"; |
502 |
|
✗ |
stderr_path_ = settings_spool_area_.log_dir() + "/stderr.log"; |
503 |
|
|
|
504 |
|
|
// Save process context information before switching namespaces |
505 |
|
✗ |
string cwd = GetCurrentWorkingDirectory(); |
506 |
|
✗ |
uid_t uid = geteuid(); |
507 |
|
✗ |
gid_t gid = getegid(); |
508 |
|
|
|
509 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, |
510 |
|
|
"*** NOTE: This is currently an experimental CernVM-FS feature\n"); |
511 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, |
512 |
|
|
"Entering ephemeral writable shell for %s... ", |
513 |
|
|
settings_spool_area_.union_mnt().c_str()); |
514 |
|
|
|
515 |
|
|
// Create root user namespace and rootfs underlay |
516 |
|
✗ |
AnchorPid anchor_pid = EnterRootContainer(); |
517 |
|
✗ |
std::vector<std::string> empty_dirs; |
518 |
|
✗ |
empty_dirs.push_back(settings_spool_area_.union_mnt()); |
519 |
|
✗ |
empty_dirs.push_back("/proc"); |
520 |
|
✗ |
std::vector<std::string> new_paths; |
521 |
|
✗ |
CreateUnderlay("", rootfs_dir_, empty_dirs, &new_paths); |
522 |
|
|
// The .cvmfsenter marker file helps when attaching to the mount namespace |
523 |
|
|
// to distinguish it from the system root |
524 |
|
✗ |
rvb = SymlinkForced(session_dir_, rootfs_dir_ + "/.cvmfsenter"); |
525 |
|
✗ |
if (!rvb) |
526 |
|
✗ |
throw EPublish("cannot create marker file " + rootfs_dir_ + "/.cvmfsenter"); |
527 |
|
✗ |
new_paths.push_back(rootfs_dir_ + "/.cvmfsenter"); |
528 |
|
✗ |
rvb = ProcMount(rootfs_dir_ + "/proc"); |
529 |
|
✗ |
if (!rvb) |
530 |
|
✗ |
throw EPublish("cannot mount " + rootfs_dir_ + "/proc"); |
531 |
|
✗ |
setenv("CVMFS_ENTER_SESSION_DIR", session_dir_.c_str(), 1 /* overwrite */); |
532 |
|
|
|
533 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout | kLogNoLinebreak, |
534 |
|
|
"Mounting CernVM-FS read-only layer... "); |
535 |
|
|
// We mount in a sub process in order to avoid tainting the main process |
536 |
|
|
// with the CVMFS_* environment variables |
537 |
|
✗ |
pid_t pid = fork(); |
538 |
|
✗ |
if (pid < 0) |
539 |
|
✗ |
throw EPublish("cannot fork"); |
540 |
|
✗ |
if (pid == 0) { |
541 |
|
✗ |
WriteCvmfsConfig(options.GetStringDefault("cvmfs-config", "")); |
542 |
|
✗ |
MountCvmfs(); |
543 |
|
✗ |
_exit(0); |
544 |
|
|
} |
545 |
|
✗ |
int exit_code = WaitForChild(pid); |
546 |
|
✗ |
if (exit_code != 0) |
547 |
|
✗ |
_exit(exit_code); |
548 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, "done"); |
549 |
|
|
|
550 |
|
✗ |
env_conf_ = session_dir_ + "/env.conf"; |
551 |
|
✗ |
rvb = SafeWriteToFile(std::string("CVMFS_FQRN=") + fqrn_ + "\n", |
552 |
|
✗ |
env_conf_, kPrivateFileMode); |
553 |
|
✗ |
if (!rvb) |
554 |
|
✗ |
throw EPublish("cannot create session environment file"); |
555 |
|
|
|
556 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout | kLogNoLinebreak, |
557 |
|
|
"Mounting union file system... "); |
558 |
|
✗ |
MountOverlayfs(); |
559 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, "done"); |
560 |
|
|
|
561 |
|
|
// Fork the inner process, the outer one is used later for cleanup |
562 |
|
✗ |
pid = fork(); |
563 |
|
✗ |
if (pid < 0) |
564 |
|
✗ |
throw EPublish("cannot create subshell"); |
565 |
|
|
|
566 |
|
✗ |
if (pid == 0) { |
567 |
|
✗ |
if (!options.Has("root")) { |
568 |
|
✗ |
NamespaceFailures failure = CreateUserNamespace(uid, gid); |
569 |
|
✗ |
if (failure != kFailNsOk) { |
570 |
|
✗ |
throw publish::EPublish("cannot create user namespace for " + |
571 |
|
✗ |
StringifyInt(uid) + ":" + StringifyInt(gid) + " (" + |
572 |
|
✗ |
StringifyInt(failure) + " / " + StringifyInt(errno) + ") [euid=" + |
573 |
|
✗ |
StringifyInt(geteuid()) + ", egid=" + StringifyInt(getegid()) + "]"); |
574 |
|
|
} |
575 |
|
|
} |
576 |
|
|
|
577 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout | kLogNoLinebreak, |
578 |
|
|
"Switching to %s... ", rootfs_dir_.c_str()); |
579 |
|
✗ |
int rvi = chroot(rootfs_dir_.c_str()); |
580 |
|
✗ |
if (rvi != 0) |
581 |
|
✗ |
throw EPublish("cannot chroot to " + rootfs_dir_); |
582 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, "done"); |
583 |
|
|
|
584 |
|
✗ |
SettingsBuilder builder; |
585 |
|
✗ |
UniquePtr<Publisher> publisher; |
586 |
|
|
|
587 |
|
✗ |
if (options.Has("transaction")) { |
588 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, |
589 |
|
|
"Starting a transaction inside the enter shell"); |
590 |
|
|
|
591 |
|
✗ |
if (options.Has("repo-config")) { |
592 |
|
✗ |
repo_config_ = options.GetString("repo-config"); |
593 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, |
594 |
|
|
"Parsing the external configuration for the repository at %s", |
595 |
|
|
repo_config_.c_str()); |
596 |
|
|
|
597 |
|
✗ |
std::string config; |
598 |
|
✗ |
std::string config_file = repo_config_ + "/" + fqrn_ + "/server.conf"; |
599 |
|
✗ |
std::string folderpath = session_dir_ + "/" + fqrn_; |
600 |
|
✗ |
MkdirDeep(folderpath.c_str(), 0700, true /* veryfy_writable */); |
601 |
|
|
|
602 |
|
✗ |
std::string session_config_file = folderpath + "/server.conf"; |
603 |
|
✗ |
int fd_config = open(config_file.c_str(), O_RDONLY); |
604 |
|
✗ |
SafeReadToString(fd_config, &config); |
605 |
|
✗ |
SafeWriteToFile(config, session_config_file, 0600); |
606 |
|
|
|
607 |
|
✗ |
builder.SetConfigPath(session_dir_); |
608 |
|
|
} |
609 |
|
|
|
610 |
|
|
SettingsPublisher *settings_publisher = |
611 |
|
✗ |
builder.CreateSettingsPublisher(fqrn_, false); |
612 |
|
✗ |
publisher = new Publisher(*settings_publisher); |
613 |
|
✗ |
publisher->Transaction(); |
614 |
|
|
} |
615 |
|
|
|
616 |
|
|
// May fail if the working directory was invalid to begin with |
617 |
|
✗ |
rvi = chdir(cwd.c_str()); |
618 |
|
✗ |
if (rvi != 0) { |
619 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, "Warning: cannot chdir to %s", |
620 |
|
|
cwd.c_str()); |
621 |
|
|
} |
622 |
|
|
|
623 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, "\n" |
624 |
|
|
"You can attach to this shell from another another terminal with\n"); |
625 |
|
✗ |
if (options.Has("root")) { |
626 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, |
627 |
|
|
" nsenter --preserve-credentials -U -p -m -t %u\n" |
628 |
|
|
" chroot %s\n", |
629 |
|
|
anchor_pid.init_pid, rootfs_dir_.c_str()); |
630 |
|
|
} else { |
631 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, |
632 |
|
|
" nsenter --preserve-credentials -U -m -t %u\n" |
633 |
|
|
" chroot %s\n" |
634 |
|
|
" nsenter --preserve-credentials -S %u -G %u -U -p -t 1\n", |
635 |
|
|
anchor_pid.parent_pid, rootfs_dir_.c_str(), uid, gid); |
636 |
|
|
} |
637 |
|
|
|
638 |
|
✗ |
std::vector<std::string> cmdline; |
639 |
|
|
// options.plain_args()[0] is the repository name |
640 |
|
✗ |
for (unsigned i = 1; i < options.plain_args().size(); ++i) { |
641 |
|
✗ |
cmdline.push_back(options.plain_args()[i].value_str); |
642 |
|
|
} |
643 |
|
✗ |
if (cmdline.empty()) |
644 |
|
✗ |
cmdline.push_back(GetShell()); |
645 |
|
✗ |
std::set<int> preserved_fds; |
646 |
|
✗ |
preserved_fds.insert(0); |
647 |
|
✗ |
preserved_fds.insert(1); |
648 |
|
✗ |
preserved_fds.insert(2); |
649 |
|
✗ |
pid_t pid_child = 0; |
650 |
|
✗ |
rvb = ManagedExec(cmdline, preserved_fds, std::map<int, int>(), |
651 |
|
|
false /* drop_credentials */, false /* clear_env */, |
652 |
|
|
false /* double_fork */, |
653 |
|
|
&pid_child); |
654 |
|
✗ |
std::string s = StringifyInt(pid_child); |
655 |
|
✗ |
SafeWriteToFile(s, session_dir_ + "/session_pid", 0600); |
656 |
|
|
|
657 |
|
✗ |
std::vector<int> sigs; |
658 |
|
✗ |
sigs.push_back(SIGUSR1); |
659 |
|
✗ |
exit_code = WaitForChild(pid_child, sigs); |
660 |
|
|
|
661 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, "Closing CernVM-FS shell..."); |
662 |
|
✗ |
if (options.Has("transaction") && |
663 |
|
✗ |
!FileExists(session_dir_ + "/shellaction.marker")) { |
664 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, "Closing current transaction..."); |
665 |
|
✗ |
publisher->session()->SetKeepAlive(false); |
666 |
|
|
} |
667 |
|
|
|
668 |
|
✗ |
return exit_code; |
669 |
|
|
} |
670 |
|
|
|
671 |
|
✗ |
exit_code = WaitForChild(pid); |
672 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, "Leaving CernVM-FS shell..."); |
673 |
|
|
|
674 |
|
✗ |
if (!options.Has("keep-session")) |
675 |
|
✗ |
CleanupSession(options.Has("keep-logs"), new_paths); |
676 |
|
|
|
677 |
|
✗ |
return exit_code; |
678 |
|
|
} |
679 |
|
|
|
680 |
|
|
} // namespace publish |
681 |
|
|
|