GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/publish/cmd_enter.cc
Date: 2024-04-28 02:33:07
Exec Total Coverage
Lines: 0 404 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 #include "cvmfs_config.h"
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 anc /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