GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/publish/cmd_enter.cc
Date: 2026-04-26 02:35:59
Exec Total Coverage
Lines: 0 410 0.0%
Branches: 0 1166 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_ + "/in_transaction.lock");
456 RemoveSingle(session_dir_ + "/shellaction.marker");
457 RemoveSingle(session_dir_);
458 LogCvmfs(kLogCvmfs, kLogStdout, "[done]");
459 }
460
461
462 int CmdEnter::Main(const Options &options) {
463 fqrn_ = options.plain_args()[0].value_str;
464 const sanitizer::RepositorySanitizer sanitizer;
465 if (!sanitizer.IsValid(fqrn_)) {
466 throw EPublish("malformed repository name: " + fqrn_);
467 }
468
469 bool rvb;
470
471 // We cannot have any capabilities or else we are not allowed to write
472 // to /proc/self/setgroups and /proc/self/[u|g]id_map when creating a user
473 // namespace
474 Env::DropCapabilities();
475
476 if (options.Has("cvmfs2")) {
477 cvmfs2_binary_ = options.GetString("cvmfs2");
478 // Lucky guess: library in the same directory than the binary,
479 // but don't overwrite an explicit setting
480 setenv("CVMFS_LIBRARY_PATH", GetParentPath(cvmfs2_binary_).c_str(), 0);
481 }
482
483 // Prepare the session directory
484 const std::string workspace = GetHomeDirectory() + "/.cvmfs/" + fqrn_;
485 EnsureDirectory(workspace);
486 session_dir_ = CreateTempDir(workspace + "/session");
487 if (session_dir_.empty())
488 throw EPublish("cannot create session directory in " + workspace);
489 settings_spool_area_.SetUnionMount(std::string("/cvmfs/") + fqrn_);
490 settings_spool_area_.SetSpoolArea(session_dir_);
491 settings_spool_area_.EnsureDirectories();
492 rootfs_dir_ = session_dir_ + "/rootfs";
493 EnsureDirectory(rootfs_dir_);
494 stdout_path_ = settings_spool_area_.log_dir() + "/stdout.log";
495 stderr_path_ = settings_spool_area_.log_dir() + "/stderr.log";
496
497 // Save process context information before switching namespaces
498 const string cwd = GetCurrentWorkingDirectory();
499 const uid_t uid = geteuid();
500 const gid_t gid = getegid();
501
502 LogCvmfs(kLogCvmfs, kLogStdout,
503 "*** NOTE: This is currently an experimental CernVM-FS feature\n");
504 LogCvmfs(kLogCvmfs, kLogStdout,
505 "Entering ephemeral writable shell for %s... ",
506 settings_spool_area_.union_mnt().c_str());
507
508 // Create root user namespace and rootfs underlay
509 const AnchorPid anchor_pid = EnterRootContainer();
510 std::vector<std::string> empty_dirs;
511 empty_dirs.push_back(settings_spool_area_.union_mnt());
512 empty_dirs.push_back("/proc");
513 std::vector<std::string> new_paths;
514 CreateUnderlay("", rootfs_dir_, empty_dirs, &new_paths);
515 // The .cvmfsenter marker file helps when attaching to the mount namespace
516 // to distinguish it from the system root
517 rvb = SymlinkForced(session_dir_, rootfs_dir_ + "/.cvmfsenter");
518 if (!rvb)
519 throw EPublish("cannot create marker file " + rootfs_dir_ + "/.cvmfsenter");
520 new_paths.push_back(rootfs_dir_ + "/.cvmfsenter");
521 rvb = ProcMount(rootfs_dir_ + "/proc");
522 if (!rvb)
523 throw EPublish("cannot mount " + rootfs_dir_ + "/proc");
524 setenv("CVMFS_ENTER_SESSION_DIR", session_dir_.c_str(), 1 /* overwrite */);
525
526 LogCvmfs(kLogCvmfs, kLogStdout | kLogNoLinebreak,
527 "Mounting CernVM-FS read-only layer... ");
528 // We mount in a sub process in order to avoid tainting the main process
529 // with the CVMFS_* environment variables
530 pid_t pid = fork();
531 if (pid < 0)
532 throw EPublish("cannot fork");
533 if (pid == 0) {
534 WriteCvmfsConfig(options.GetStringDefault("cvmfs-config", ""));
535 MountCvmfs();
536 _exit(0);
537 }
538 int exit_code = WaitForChild(pid);
539 if (exit_code != 0)
540 _exit(exit_code);
541 LogCvmfs(kLogCvmfs, kLogStdout, "done");
542
543 env_conf_ = session_dir_ + "/env.conf";
544 rvb = SafeWriteToFile(std::string("CVMFS_FQRN=") + fqrn_ + "\n", env_conf_,
545 kPrivateFileMode);
546 if (!rvb)
547 throw EPublish("cannot create session environment file");
548
549 LogCvmfs(kLogCvmfs, kLogStdout | kLogNoLinebreak,
550 "Mounting union file system... ");
551 MountOverlayfs();
552 LogCvmfs(kLogCvmfs, kLogStdout, "done");
553
554 // Fork the inner process, the outer one is used later for cleanup
555 pid = fork();
556 if (pid < 0)
557 throw EPublish("cannot create subshell");
558
559 if (pid == 0) {
560 if (!options.Has("root")) {
561 const NamespaceFailures failure = CreateUserNamespace(uid, gid);
562 if (failure != kFailNsOk) {
563 throw publish::EPublish(
564 "cannot create user namespace for " + StringifyInt(uid) + ":"
565 + StringifyInt(gid) + " (" + StringifyInt(failure) + " / "
566 + StringifyInt(errno) + ") [euid=" + StringifyInt(geteuid())
567 + ", egid=" + StringifyInt(getegid()) + "]");
568 }
569 }
570
571 LogCvmfs(kLogCvmfs, kLogStdout | kLogNoLinebreak, "Switching to %s... ",
572 rootfs_dir_.c_str());
573 int rvi = chroot(rootfs_dir_.c_str());
574 if (rvi != 0)
575 throw EPublish("cannot chroot to " + rootfs_dir_);
576 LogCvmfs(kLogCvmfs, kLogStdout, "done");
577
578 SettingsBuilder builder;
579 UniquePtr<Publisher> publisher;
580
581 if (options.Has("transaction")) {
582 LogCvmfs(kLogCvmfs, kLogStdout,
583 "Starting a transaction inside the enter shell");
584
585 if (options.Has("repo-config")) {
586 repo_config_ = options.GetString("repo-config");
587 LogCvmfs(kLogCvmfs, kLogStdout,
588 "Parsing the external configuration for the repository at %s",
589 repo_config_.c_str());
590
591 std::string config;
592 const std::string config_file = repo_config_ + "/" + fqrn_
593 + "/server.conf";
594 const std::string folderpath = session_dir_ + "/" + fqrn_;
595 MkdirDeep(folderpath.c_str(), 0700, true /* veryfy_writable */);
596
597 const std::string session_config_file = folderpath + "/server.conf";
598 const int fd_config = open(config_file.c_str(), O_RDONLY);
599 SafeReadToString(fd_config, &config);
600 SafeWriteToFile(config, session_config_file, 0600);
601
602 builder.SetConfigPath(session_dir_);
603 }
604
605 SettingsPublisher *settings_publisher = builder.CreateSettingsPublisher(
606 fqrn_, false);
607 publisher = new Publisher(*settings_publisher);
608 publisher->Transaction();
609 }
610
611 // May fail if the working directory was invalid to begin with
612 rvi = chdir(cwd.c_str());
613 if (rvi != 0) {
614 LogCvmfs(kLogCvmfs, kLogStdout, "Warning: cannot chdir to %s",
615 cwd.c_str());
616 }
617
618 LogCvmfs(
619 kLogCvmfs, kLogStdout,
620 "\n"
621 "You can attach to this shell from another another terminal with\n");
622 if (options.Has("root")) {
623 LogCvmfs(kLogCvmfs, kLogStdout,
624 " nsenter --preserve-credentials -U -p -m -t %u\n"
625 " chroot %s\n",
626 anchor_pid.init_pid, rootfs_dir_.c_str());
627 } else {
628 LogCvmfs(kLogCvmfs, kLogStdout,
629 " nsenter --preserve-credentials -U -m -t %u\n"
630 " chroot %s\n"
631 " nsenter --preserve-credentials -S %u -G %u -U -p -t 1\n",
632 anchor_pid.parent_pid, rootfs_dir_.c_str(), uid, gid);
633 }
634
635 std::vector<std::string> cmdline;
636 // options.plain_args()[0] is the repository name
637 for (unsigned i = 1; i < options.plain_args().size(); ++i) {
638 cmdline.push_back(options.plain_args()[i].value_str);
639 }
640 if (cmdline.empty())
641 cmdline.push_back(GetShell());
642 std::set<int> preserved_fds;
643 preserved_fds.insert(0);
644 preserved_fds.insert(1);
645 preserved_fds.insert(2);
646 pid_t pid_child = 0;
647 rvb = ManagedExec(cmdline, preserved_fds, std::map<int, int>(),
648 false /* drop_credentials */, false /* clear_env */,
649 false /* double_fork */, &pid_child);
650 const std::string s = StringifyInt(pid_child);
651 SafeWriteToFile(s, session_dir_ + "/session_pid", 0600);
652
653 std::vector<int> sigs;
654 sigs.push_back(SIGUSR1);
655 exit_code = WaitForChild(pid_child, sigs);
656
657 LogCvmfs(kLogCvmfs, kLogStdout, "Closing CernVM-FS shell...");
658 if (options.Has("transaction")
659 && !FileExists(session_dir_ + "/shellaction.marker")) {
660 LogCvmfs(kLogCvmfs, kLogStdout, "Closing current transaction...");
661 publisher->session()->SetKeepAlive(false);
662 }
663
664 return exit_code;
665 }
666
667 exit_code = WaitForChild(pid);
668 LogCvmfs(kLogCvmfs, kLogStdout, "Leaving CernVM-FS shell...");
669
670 if (!options.Has("keep-session"))
671 CleanupSession(options.Has("keep-logs"), new_paths);
672
673 return exit_code;
674 }
675
676 } // namespace publish
677