GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/swissknife_info.cc
Date: 2025-06-22 02:36:02
Exec Total Coverage
Lines: 0 138 0.0%
Branches: 0 140 0.0%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 *
4 * This command reads the content of a .cvmfspublished file and exposes it
5 * to the user.
6 */
7
8 #define __STDC_FORMAT_MACROS
9
10 #include "swissknife_info.h"
11
12 #include <string>
13
14 #include "crypto/hash.h"
15 #include "manifest.h"
16 #include "network/download.h"
17 #include "util/logging.h"
18 #include "util/posix.h"
19 #include "util/string.h"
20
21 using namespace std; // NOLINT
22
23 namespace swissknife {
24
25 /**
26 * Checks if the given path looks like a remote path
27 */
28 static bool IsRemote(const string &repository) {
29 return HasPrefix(repository, "http://", false)
30 || HasPrefix(repository, "https://", false);
31 }
32
33 /**
34 * Checks for existence of a file either locally or via HTTP head
35 */
36 bool CommandInfo::Exists(const string &repository, const string &file) const {
37 if (IsRemote(repository)) {
38 const string url = repository + "/" + file;
39 download::JobInfo head(&url, false);
40 return download_manager()->Fetch(&head) == download::kFailOk;
41 } else {
42 return FileExists(file);
43 }
44 }
45
46 ParameterList CommandInfo::GetParams() const {
47 swissknife::ParameterList r;
48 r.push_back(Parameter::Mandatory('r', "repository directory / url"));
49 r.push_back(Parameter::Optional('u', "repository mount point"));
50 r.push_back(Parameter::Optional('l', "log level (0-4, default: 2)"));
51 r.push_back(Parameter::Optional('@', "proxy url"));
52 r.push_back(Parameter::Switch('c', "show root catalog hash"));
53 r.push_back(Parameter::Switch('C', "show mounted root catalog hash"));
54 r.push_back(Parameter::Switch('n', "show fully qualified repository name"));
55 r.push_back(Parameter::Switch('t', "show time stamp"));
56 r.push_back(Parameter::Switch('m',
57 "check if repository is marked as "
58 "replication master copy"));
59 r.push_back(Parameter::Switch('v', "repository revision number"));
60 r.push_back(Parameter::Switch('g',
61 "check if repository is garbage "
62 "collectable"));
63 r.push_back(Parameter::Switch('o',
64 "check if the repository maintains a "
65 "reference log file"));
66 r.push_back(Parameter::Switch('h', "print results in human readable form"));
67 r.push_back(Parameter::Switch('L', "follow HTTP redirects"));
68 r.push_back(Parameter::Switch('X',
69 "show whether external data is supported "
70 "in the root catalog."));
71 r.push_back(Parameter::Switch('M', "print repository meta info."));
72 r.push_back(Parameter::Switch('R', "print raw manifest."));
73 r.push_back(Parameter::Switch('e', "check if the repository is empty"));
74 return r;
75 }
76
77 int swissknife::CommandInfo::Main(const swissknife::ArgumentList &args) {
78 if (args.find('l') != args.end()) {
79 const unsigned log_level = kLogLevel0
80 << String2Uint64(*args.find('l')->second);
81 if (log_level > kLogNone) {
82 LogCvmfs(kLogCvmfs, kLogStderr, "invalid log level");
83 return 1;
84 }
85 SetLogVerbosity(static_cast<LogLevels>(log_level));
86 }
87 const string mount_point = (args.find('u') != args.end())
88 ? *args.find('u')->second
89 : "";
90 const string repository = MakeCanonicalPath(*args.find('r')->second);
91
92 // sanity check
93 if (args.count('C') > 0 && mount_point.empty()) {
94 LogCvmfs(kLogCvmfs, kLogStderr, "need a CernVM-FS mountpoint (-u) for -C");
95 return 1;
96 }
97
98 if (IsRemote(repository)) {
99 const bool follow_redirects = args.count('L') > 0;
100 const string proxy = (args.find('@') != args.end())
101 ? *args.find('@')->second
102 : "";
103 if (!this->InitDownloadManager(follow_redirects, proxy)) {
104 return 1;
105 }
106 }
107
108 // Check if we should be human readable
109 const bool human_readable = (args.count('h') > 0);
110
111 if (args.count('e') > 0) {
112 const string manifest_path = IsRemote(repository)
113 ? ".cvmfspublished"
114 : repository + "/.cvmfspublished";
115 const bool is_empty = !Exists(repository, manifest_path);
116 LogCvmfs(kLogCvmfs, kLogStdout, "%s%s",
117 (human_readable) ? "Empty Repository: " : "",
118 StringifyBool(is_empty).c_str());
119 if (is_empty)
120 return 0;
121 }
122
123 // Load manifest file
124 // Repository can be HTTP address or on local file system
125 // TODO(jblomer): do this using Manifest::Fetch
126 // currently this is not possible, since Manifest::Fetch asks for the
127 // repository name... Which we want to figure out with the tool at hand.
128 // Possible Fix: Allow for a Manifest::Fetch with an empty name.
129 UniquePtr<manifest::Manifest> manifest;
130 if (IsRemote(repository)) {
131 const string url = repository + "/.cvmfspublished";
132 cvmfs::MemSink manifest_memsink;
133 download::JobInfo download_manifest(&url, false, false, NULL,
134 &manifest_memsink);
135 const download::Failures retval =
136 download_manager()->Fetch(&download_manifest);
137 if (retval != download::kFailOk) {
138 LogCvmfs(kLogCvmfs, kLogStderr, "failed to download manifest (%d - %s)",
139 retval, download::Code2Ascii(retval));
140 return 1;
141 }
142
143 manifest = manifest::Manifest::LoadMem(manifest_memsink.data(),
144 manifest_memsink.pos());
145 } else {
146 if (chdir(repository.c_str()) != 0) {
147 LogCvmfs(kLogCvmfs, kLogStderr, "failed to switch to directory %s",
148 repository.c_str());
149 return 1;
150 }
151 manifest = manifest::Manifest::LoadFile(".cvmfspublished");
152 }
153
154 if (!manifest.IsValid()) {
155 LogCvmfs(kLogCvmfs, kLogStderr, "failed to load repository manifest");
156 return 1;
157 }
158
159 // Validate Manifest
160 const string certificate_path = "data/" + manifest->certificate().MakePath();
161 if (!Exists(repository, certificate_path)) {
162 LogCvmfs(kLogCvmfs, kLogStderr, "failed to find certificate (%s)",
163 certificate_path.c_str());
164 return 1;
165 }
166
167 // Get information from the mount point
168 if (args.count('C') > 0) {
169 assert(!mount_point.empty());
170 const std::string root_hash_xattr = "user.root_hash";
171 std::string root_hash;
172 const bool success = platform_getxattr(mount_point, root_hash_xattr,
173 &root_hash);
174 if (!success) {
175 LogCvmfs(kLogCvmfs, kLogStderr,
176 "failed to retrieve extended attribute "
177 " '%s' from '%s' (errno: %d)",
178 root_hash_xattr.c_str(), mount_point.c_str(), errno);
179 return 1;
180 }
181 LogCvmfs(kLogCvmfs, kLogStdout, "%s%s",
182 (human_readable) ? "Mounted Root Hash: " : "",
183 root_hash.c_str());
184 }
185
186 // Get information about external data
187 if (args.count('X') > 0) {
188 assert(!mount_point.empty());
189 const std::string external_data_xattr = "user.external_data";
190 std::string external_data;
191 const bool success = platform_getxattr(mount_point, external_data_xattr,
192 &external_data);
193 if (!success) {
194 LogCvmfs(kLogCvmfs, kLogStderr,
195 "failed to retrieve extended attribute "
196 " '%s' from '%s' (errno: %d)",
197 external_data_xattr.c_str(), mount_point.c_str(), errno);
198 return 1;
199 }
200 LogCvmfs(kLogCvmfs, kLogStdout, "%s%s",
201 (human_readable) ? "External data enabled: " : "",
202 external_data.c_str());
203 }
204
205 // Get information from the Manifest
206 if (args.count('c') > 0) {
207 LogCvmfs(kLogCvmfs, kLogStdout, "%s%s",
208 (human_readable) ? "Root Catalog Hash: " : "",
209 manifest->catalog_hash().ToString().c_str());
210 }
211
212 if (args.count('n') > 0) {
213 LogCvmfs(kLogCvmfs, kLogStdout, "%s%s",
214 (human_readable) ? "Fully Qualified Repository Name: " : "",
215 manifest->repository_name().c_str());
216 }
217
218 if (args.count('t') > 0) {
219 LogCvmfs(kLogCvmfs, kLogStdout, "%s%lu",
220 (human_readable) ? "Time Stamp: " : "",
221 manifest->publish_timestamp());
222 }
223
224 if (args.count('m') > 0) {
225 LogCvmfs(kLogCvmfs, kLogStdout, "%s%s",
226 (human_readable) ? "Replication Master Copy: " : "",
227 (Exists(repository, ".cvmfs_master_replica")) ? "true" : "false");
228 }
229
230 if (args.count('v') > 0) {
231 LogCvmfs(kLogCvmfs, kLogStdout, "%s%s",
232 (human_readable) ? "Revision: " : "",
233 (StringifyInt(manifest->revision())).c_str());
234 }
235
236 if (args.count('g') > 0) {
237 LogCvmfs(kLogCvmfs, kLogStdout, "%s%s",
238 (human_readable) ? "Garbage Collectable: " : "",
239 (StringifyBool(manifest->garbage_collectable())).c_str());
240 }
241
242 if (args.count('o') > 0) {
243 LogCvmfs(kLogCvmfs, kLogStdout, "%s%s",
244 (human_readable) ? "Maintains Reference Log: " : "",
245 (Exists(repository, ".cvmfsreflog")) ? "true" : "false");
246 }
247
248 if (args.count('M') > 0) {
249 const shash::Any meta_info(manifest->meta_info());
250 if (meta_info.IsNull()) {
251 if (human_readable)
252 LogCvmfs(kLogCvmfs, kLogStderr, "no meta info available");
253 return 0;
254 }
255 const string url = repository + "/data/" + meta_info.MakePath();
256 cvmfs::MemSink metainfo_memsink;
257 download::JobInfo download_metainfo(&url, true, false, &meta_info,
258 &metainfo_memsink);
259 const download::Failures retval =
260 download_manager()->Fetch(&download_metainfo);
261 if (retval != download::kFailOk) {
262 if (human_readable)
263 LogCvmfs(kLogCvmfs, kLogStderr,
264 "failed to download meta info (%d - %s)", retval,
265 download::Code2Ascii(retval));
266 return 1;
267 }
268 const string info(reinterpret_cast<char *>(metainfo_memsink.data()),
269 metainfo_memsink.pos());
270 LogCvmfs(kLogCvmfs, kLogStdout | kLogNoLinebreak, "%s", info.c_str());
271 }
272
273 if (args.count('R') > 0) {
274 LogCvmfs(kLogCvmfs, kLogStdout | kLogNoLinebreak, "%s",
275 manifest->ExportString().c_str());
276 }
277
278 return 0;
279 }
280
281 //------------------------------------------------------------------------------
282
283 int CommandVersion::Main(const ArgumentList &args) {
284 LogCvmfs(kLogCvmfs, kLogStdout, "%s", CVMFS_VERSION);
285 return 0;
286 }
287
288 } // namespace swissknife
289