GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/publish/cmd_enter.cc
Date: 2026-10-04 02:40:33
Exec Total Coverage
Lines: 0 411 0.0%
Branches: 0 1172 0.0%

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