CernVM-FS  2.13.0
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
swissknife_ingestsql.cc
Go to the documentation of this file.
1 #include "swissknife_ingestsql.h"
2 
3 #include <fcntl.h>
4 #include <stdio.h>
5 #include <unistd.h>
6 #include <sys/time.h>
7 #include <sys/resource.h>
8 #include <sys/types.h>
9 #include <pwd.h>
10 #include <grp.h>
11 
12 #include <csignal>
13 #include <cstdlib>
14 #include <fstream>
15 #include <sstream>
16 #include <unordered_map>
17 #include <unordered_set>
18 #include <stack>
19 
20 #include "acl.h"
21 #include "catalog_mgr_rw.h"
22 #include "curl/curl.h"
23 #include "gateway_util.h"
24 #include "swissknife_lease_curl.h"
25 #include "swissknife_lease_json.h"
26 #include "swissknife_sync.h"
27 #include "upload.h"
28 #include "util/logging.h"
29 #include "catalog_downloader.h"
30 #include "shortstring.h"
31 
32 #define CHECK_SQLITE_ERROR(ret, expected) do { \
33  if (ret!=expected) { \
34  LogCvmfs(kLogCvmfs, kLogStderr, "SQLite error: %d", ret); \
35  assert(0); \
36  } \
37  } while (0)
38 
39 #define CUSTOM_ASSERT(check, msg, ...) do { \
40  if (!(check)) { \
41  LogCvmfs(kLogCvmfs, kLogStderr, msg, ##__VA_ARGS__); \
42  assert(0); \
43  } \
44  } while (0)
45 
46 #define SHOW_PROGRESS(item, freq, curr, total) do { \
47  if (curr % freq == 0 || curr == total) { \
48  LogCvmfs(kLogCvmfs, kLogStdout, "Processed %d/%d %s", curr, total, item); \
49  } \
50  } while (0)
51 
52 
53 
54 static const unsigned kExternalChunkSize = 24 * 1024 * 1024;
55 static const unsigned kInternalChunkSize = 6 * 1024 * 1024;
56 static const unsigned kDefaultLeaseBusyRetryInterval = 10;
57 static const unsigned kLeaseRefreshInterval = 90; // seconds
58 
59 
60 static bool g_lease_acquired = false;
61 static string g_gateway_url;
62 static string g_gateway_key_id;
63 static string g_gateway_secret;
64 static string g_session_token;
65 static string g_session_token_file;
66 static string g_s3_file;
67 static time_t g_last_lease_refresh=0;
68 static bool g_stop_refresh = false;
69 static int g_priority=0;
70 static bool g_add_missing_catalogs = false;
71 static string get_lease_from_paths(vector<string> paths);
72 static vector<string> get_all_dirs_from_sqlite(vector<string>& sqlite_db_vec,
73  bool include_additions,
74  bool include_deletions);
75 static string get_parent(const string& path);
76 static string get_basename(const string& path);
77 
78 static XattrList marshal_xattrs(const char *acl);
79 static string sanitise_name(const char *name_cstr, bool allow_leading_slash);
80 static void on_signal(int sig);
81 static string acquire_lease(const string& key_id, const string& secret, const string& lease_path,
82  const string& repo_service_url, bool force_cancel_lease, uint64_t *current_revision, string &current_root_hash,
83  unsigned int refresh_interval);
84 static void cancel_lease();
85 static void refresh_lease();
86 static vector<string> get_file_list(string& path);
87 static int check_hash(const char *hash) ;
89 static void create_empty_database( string& filename );
90 static void relax_db_locking(sqlite3 *db);
91 static bool check_prefix(const std::string &path , const std::string &prefix);
92 
93 static bool isDatabaseMarkedComplete(const char *dbfile);
94 static void setDatabaseMarkedComplete(const char *dbfile);
95 
96 extern "C" void* lease_refresh_thread(void *payload);
97 
98 static string sanitise_name(const char *name_cstr,
99  bool allow_leading_slash = false) {
100  int reason = 0;
101  const char *c = name_cstr;
102  while(*c == '/') {c++;} // strip any leading slashes
103  string name = string(c);
104  bool ok = true;
105 
106  if (!allow_leading_slash && HasPrefix(name, "/", true)) {
107  reason=1;
108  ok = false;
109  }
110  if (HasSuffix(name, "/", true)) {
111  if (!(allow_leading_slash &&
112  name.size() == 1)) { // account for the case where name=="/"
113  reason=2;
114  ok = false;
115  }
116  }
117  if (name.find("//") != string::npos) {
118  reason=3;
119  ok = false;
120  }
121  if (HasPrefix(name, "./", true) || HasPrefix(name, "../", true)) {
122  reason=4;
123  ok = false;
124  }
125  if (HasSuffix(name, "/.", true) || HasSuffix(name, "/..", true)) {
126  reason=5;
127  ok = false;
128  }
129  if (name.find("/./") != string::npos || name.find("/../") != string::npos) {
130  reason=6;
131  ok = false;
132  }
133  if (name == "") {
134  reason=7;
135  ok = false;
136  }
137  CUSTOM_ASSERT(ok, "Name [%s] is invalid (reason %d)", name.c_str(), reason);
138  return string(name);
139 }
140 
141 static string get_parent(const string& path) {
142  size_t found = path.find_last_of('/');
143  if (found == string::npos) {
144  return string("");
145  }
146  return path.substr(0, found);
147 }
148 
149 static string get_basename(const string& path) {
150  size_t found = path.find_last_of('/');
151  if (found == string::npos) {
152  return path;
153  }
154  return path.substr(found + 1);
155 }
156 
157 // this is copied from MakeRelativePath
158 static string MakeCatalogPath(const std::string &relative_path) {
159  return (relative_path == "") ? "" : "/" + relative_path;
160 }
161 
162 static string acquire_lease(const string& key_id, const string& secret, const string& lease_path,
163  const string& repo_service_url, bool force_cancel_lease, uint64_t *current_revision, string &current_root_hash,
164  unsigned int refresh_interval) {
165  CURLcode ret = curl_global_init(CURL_GLOBAL_ALL);
166  CUSTOM_ASSERT(ret == CURLE_OK, "failed to init curl");
167  bool acquired = false;
168 
169  string gateway_metadata_str;
170  char *gateway_metadata = getenv("CVMFS_GATEWAY_METADATA");
171  if (gateway_metadata != NULL) gateway_metadata_str = gateway_metadata;
172 
173  while (!acquired) {
174  CurlBuffer buffer;
175  if (MakeAcquireRequest(key_id, secret, lease_path, repo_service_url,
176  &buffer, gateway_metadata_str)) {
177  string session_token;
178 
179  LeaseReply rep = ParseAcquireReplyWithRevision(buffer, &session_token, current_revision, current_root_hash);
180  switch (rep) {
181  case kLeaseReplySuccess:
182  acquired = true;
183  g_lease_acquired = true;
184  g_last_lease_refresh = time(NULL);
185  return session_token;
186  break;
187  case kLeaseReplyBusy:
188  if( force_cancel_lease ) {
189  LogCvmfs(kLogCvmfs, kLogStderr, "Lease busy, forcing cancellation (TODO");
190  }
191  LogCvmfs(kLogCvmfs, kLogStderr, "Lease busy, retrying in %d sec",
192  refresh_interval);
193  sleep(refresh_interval);
194  break;
195  default:
196  LogCvmfs(kLogCvmfs, kLogStderr, "Error acquiring lease: %s. Retrying in %d sec",
197  buffer.data.c_str(), refresh_interval);
198  sleep(refresh_interval);
199  }
200  } else {
201  LogCvmfs(kLogCvmfs, kLogStderr, "Error making lease acquisition request. Retrying in %d sec", refresh_interval);
202  sleep(refresh_interval);
203  }
204  }
205  assert(false);
206  return "";
207 }
208 
209 static uint64_t make_commit_on_gateway( const std::string &old_root_hash, const std::string &new_root_hash, int priority) {
210  CurlBuffer buffer;
211  char priorityStr[100];
212  sprintf(priorityStr, "%d", priority);
213  buffer.data="";
214 
215  std::string payload = "{\n\"old_root_hash\": \"" + old_root_hash + "\",\n\"new_root_hash\": \""+new_root_hash+"\",\n\"priority\": "+priorityStr+"}";
216 
218  g_session_token, g_gateway_url, payload, &buffer, true);
219 }
220 
221 static void refresh_lease() {
222  CurlBuffer buffer;
223  buffer.data="";
224  if ( (time(NULL)- g_last_lease_refresh )< kLeaseRefreshInterval ){ return; }
225 
227  g_session_token, g_gateway_url, "", &buffer,false)) {
228  int ret = ParseDropReply(buffer);
229  if (kLeaseReplySuccess == ret) {
230  LogCvmfs(kLogCvmfs, kLogVerboseMsg, "Lease refreshed");
231  g_last_lease_refresh=time(NULL);
232  } else {
233  LogCvmfs(kLogCvmfs, kLogStderr, "Lease refresh failed: %d", ret);
234  }
235  } else {
236  LogCvmfs(kLogCvmfs, kLogStderr, "Lease refresh request failed");
237  if(buffer.data == "Method Not Allowed\n") {
238  g_last_lease_refresh=time(NULL);
239  LogCvmfs(kLogCvmfs, kLogStderr, "This gateway does not support lease refresh");
240  }
241 
242  }
243 }
244 
245 
246 static void cancel_lease() {
247  CurlBuffer buffer;
249  g_session_token, g_gateway_url, "", &buffer, false)) {
250  int ret = ParseDropReply(buffer);
251  if (kLeaseReplySuccess == ret) {
252  LogCvmfs(kLogCvmfs, kLogStdout, "Lease cancelled");
253  } else {
254  LogCvmfs(kLogCvmfs, kLogStderr, "Lease cancellation failed: %d", ret);
255  }
256  } else {
257  LogCvmfs(kLogCvmfs, kLogStderr, "Lease cancellation request failed");
258  }
259  g_stop_refresh=true;
260 }
261 
262 static void on_signal(int sig) {
263  signal(sig, SIG_DFL);
264  if (g_lease_acquired) {
265  LogCvmfs(kLogCvmfs, kLogStdout, "Cancelling lease");
266  cancel_lease();
267  unlink(g_session_token_file.c_str());
268  }
269  if (sig == SIGINT || sig == SIGTERM) exit(1);
270 }
271 
272 static vector<string> get_all_dirs_from_sqlite(vector<string>& sqlite_db_vec,
273  bool include_additions,
274  bool include_deletions) {
275  int ret;
276  vector<string> paths;
277 
278  for (vector<string>::iterator it = sqlite_db_vec.begin();
279  it != sqlite_db_vec.end(); it++) {
280  sqlite3 *db;
281  ret = sqlite3_open_v2((*it).c_str(), &db, SQLITE_OPEN_READONLY, NULL);
282  CHECK_SQLITE_ERROR(ret, SQLITE_OK);
283  relax_db_locking(db);
284 
285  vector<string> tables;
286  if (include_additions) {
287  tables.push_back("dirs");
288  tables.push_back("links");
289  tables.push_back("files");
290  }
291  if (include_deletions) {
292  tables.push_back("deletions");
293  }
294 
295  // get all the paths from the DB
296  for (vector<string>::iterator it = tables.begin(); it != tables.end();
297  it++) {
298  sqlite3_stmt *stmt;
299  string query = "SELECT name FROM " + *it;
300  ret = sqlite3_prepare_v2(db, query.c_str(), -1, &stmt, NULL);
301  CHECK_SQLITE_ERROR(ret, SQLITE_OK);
302  while (sqlite3_step(stmt) == SQLITE_ROW) {
303  char *name = (char *)sqlite3_column_text(stmt, 0);
304  string names = sanitise_name(name);
305  if (*it=="dirs") {
306  paths.push_back(names);
307  } else {
308  paths.push_back(get_parent(names));
309  }
310  }
311  ret = sqlite3_finalize(stmt);
312  CHECK_SQLITE_ERROR(ret, SQLITE_OK);
313  }
314  ret = sqlite3_close_v2(db);
315  CHECK_SQLITE_ERROR(ret, SQLITE_OK);
316  }
317  return paths;
318 }
319 
320 static int get_db_schema_revision(sqlite3 *db, const std::string &db_name = "") {
321  sqlite3_stmt *stmt;
322  std::ostringstream stmt_str;
323  stmt_str << "SELECT value FROM " << db_name << "properties WHERE key = 'schema_revision'";
324  int ret = sqlite3_prepare_v2(db, stmt_str.str().c_str(), -1, &stmt, NULL);
325  CHECK_SQLITE_ERROR(ret, SQLITE_OK);
326 
327  ret = sqlite3_step(stmt);
328  // if table exists, we require that it must have a schema_revision row
329  CHECK_SQLITE_ERROR(ret, SQLITE_ROW);
330  std::string schema_revision_str(reinterpret_cast<const char *>(sqlite3_column_text(stmt, 0)));
331  CHECK_SQLITE_ERROR(sqlite3_finalize(stmt), SQLITE_OK);
332  return std::stoi(schema_revision_str);
333 }
334 
335 static int get_row_count(sqlite3 *db, const std::string &table_name) {
336  sqlite3_stmt *stmt;
337  std::ostringstream stmt_str;
338  stmt_str << "SELECT COUNT(*) FROM " << table_name;
339  int ret = sqlite3_prepare_v2(db, stmt_str.str().c_str(), -1, &stmt, NULL);
340  CHECK_SQLITE_ERROR(ret, SQLITE_OK);
341 
342  ret = sqlite3_step(stmt);
343  CHECK_SQLITE_ERROR(ret, SQLITE_ROW);
344  std::string count_str(reinterpret_cast<const char *>(sqlite3_column_text(stmt, 0)));
345  CHECK_SQLITE_ERROR(sqlite3_finalize(stmt), SQLITE_OK);
346  return std::stoi(count_str);
347 }
348 
349 static int calculate_print_frequency(int total) {
350  int base = 1000;
351  while (base * 50 < total) base *= 10;
352  return base;
353 }
354 
355 // compute a common path among for paths for use as the lease path
356 static string get_lease_from_paths(vector<string> paths) {
357  CUSTOM_ASSERT(!paths.empty(), "no paths are provided");
358 
359  // we'd have to ensure path is relative
360  // (probably best to check this elsewhere as it is not just a requirement for this function)
361  auto lease = PathString(paths.at(0));
362  for (auto it = paths.begin() + 1; it != paths.end(); ++it) {
363  auto path = PathString(*it);
364  // shrink the lease path until it is a parent of "path"
365  while (!IsSubPath(lease, path)) {
366  auto i = lease.GetLength() - 1;
367  for (; i >= 0; --i) {
368  if (lease.GetChars()[i] == '/' || i == 0) {
369  lease.Truncate(i);
370  break;
371  }
372  }
373  }
374  if (lease.IsEmpty()) break; // early stop if lease is already at the root
375  }
376 
377  auto prefix = "/" + lease.ToString();
378 
379  LogCvmfs(kLogCvmfs, kLogStdout, "Longest prefix is %s", prefix.c_str());
380  return prefix;
381 }
382 
383 static XattrList marshal_xattrs(const char *acl_string) {
384  XattrList aclobj;
385 
386  if (acl_string == NULL || acl_string[0] == '\0') {
387  return aclobj;
388  }
389 
390  bool equiv_mode;
391  size_t binary_size;
392  char *binary_acl;
393  int ret = acl_from_text_to_xattr_value(string(acl_string), binary_acl, binary_size, equiv_mode);
394  if (ret) {
395  LogCvmfs(kLogCvmfs, kLogStderr, "failure of acl_from_text_to_xattr_value(%s)", acl_string);
396  assert(0); // TODO(vavolkl): incorporate error handling other than asserting
397  return aclobj;
398  }
399  if (!equiv_mode) {
400  CUSTOM_ASSERT(aclobj.Set("system.posix_acl_access", string(binary_acl, binary_size)), "failed to set system.posix_acl_access (ACL size %ld)", binary_size);
401  free(binary_acl);
402  }
403 
404  return aclobj;
405 }
406 
407 std::unordered_map<string, string> load_config(const string& config_file) {
408  std::unordered_map<string, string> config_map;
409  ifstream input(config_file);
410  if (!input) {
411  LogCvmfs(kLogCvmfs, kLogStderr, "could not open config file %s", config_file.c_str());
412  return config_map;
413  }
414  vector<string> lines;
415  for (string line; getline(input, line);) {
416  lines.push_back(line);
417  }
418 
419  for (auto it = lines.begin(); it != lines.end(); it++) {
420  string l = *it;
421  size_t p = l.find("=", 0);
422  if (p != string::npos) {
423  string key = l.substr(0, p);
424  string val = l.substr(p + 1);
425  // trim any double quotes
426  if (val.front() == '"') {
427  val = val.substr(1, val.length() - 2);
428  }
429  config_map[key] = val;
430  }
431  }
432 
433  return config_map;
434 }
435 
436 string retrieve_config(std::unordered_map<string, string> &config_map, const string& key) {
437  auto kv = config_map.find(key);
438  CUSTOM_ASSERT(kv != config_map.end(), "Parameter %s not found in config", key.c_str());
439  return kv->second;
440 }
441 
442 static vector<string> get_file_list(string& path) {
443  vector<string> paths;
444  const char *cpath = path.c_str();
445  struct stat st;
446  int ret = stat(cpath, &st);
447  CUSTOM_ASSERT(ret == 0, "failed to stat file %s", cpath);
448 
449  if (S_ISDIR(st.st_mode)) {
450  DIR *d;
451  struct dirent *dir;
452  d = opendir(cpath);
453  if (d) {
454  while ((dir = readdir(d)) != NULL) {
455  const char *t = strrchr(dir->d_name, '.');
456  if (t && !strcmp(t, ".db")) {
457  paths.push_back(path + "/" + dir->d_name);
458  }
459  }
460  closedir(d);
461  }
462  } else {
463  paths.push_back(path);
464  }
465  return paths;
466 }
467 
468 extern bool g_log_with_time;
469 
471 
472 
473  // the catalog code uses assert() liberally.
474  // install ABRT signal handler to catch an abort and cancel lease
475  signal(SIGABRT, &on_signal);
476  signal(SIGINT, &on_signal);
477  signal(SIGTERM, &on_signal);
478 
479  bool enable_corefiles = (args.find('c') != args.end());
480  if( !enable_corefiles ) {
481  struct rlimit rlim;
482  rlim.rlim_cur = rlim.rlim_max = 0;
483  setrlimit( RLIMIT_CORE, &rlim );
484  }
485 
486 
487  if (args.find('n') != args.end()) {
488  create_empty_database( *args.find('n')->second);
489  exit(0);
490  }
491 
492  //TODO(@vvolkl): add 'B' option to wait_for_update
493  //TODO(@vvolkl): add 'T' option for ttl
494 
495 
496  if (args.find('P') != args.end()) {
497  g_priority = atoi((*args.find('P')->second).c_str());
498  } else {
499  g_priority = -time(NULL);
500  }
501 
502 
503  unsigned int lease_busy_retry_interval = kDefaultLeaseBusyRetryInterval;
504  if (args.find('r') != args.end()) {
505  lease_busy_retry_interval = atoi((*args.find('r')->second).c_str());
506  }
507 
508  string dir_temp = "";
509  if (args.find('t') != args.end()) {
510  dir_temp = MakeCanonicalPath(*args.find('t')->second);
511  } else if (getenv("TMPDIR")) {
512  dir_temp = MakeCanonicalPath(getenv("TMPDIR"));
513  } else {
514  LogCvmfs(kLogCvmfs, kLogStderr, "-t or TMPDIR required");
515  return 1;
516  }
517 
518  string kConfigDir("/etc/cvmfs/gateway-client/");
519  if (args.find('C') != args.end()) {
520  kConfigDir = MakeCanonicalPath(*args.find('C')->second);
521  kConfigDir += "/";
522  LogCvmfs(kLogCvmfs, kLogStdout, "Overriding configuration dir prefix to %s", kConfigDir.c_str() );
523  }
524 
525  // mandatory arguments
526  string repo_name = *args.find('N')->second;
527  string sqlite_db_path = *args.find('D')->second;
528 
529  vector<string> sqlite_db_vec = get_file_list(sqlite_db_path);
530 
531  // optional arguments
532  bool allow_deletions = (args.find('d') != args.end());
533  bool force_cancel_lease = (args.find('x') != args.end());
534  bool allow_additions = !allow_deletions || (args.find('a') != args.end());
535  g_add_missing_catalogs = ( args.find('z') != args.end());
536  bool check_completed_graft_property = ( args.find('Z') != args.end());
537  if (args.find('v') != args.end()) {
539  }
540 
541  if(check_completed_graft_property) {
542  if(sqlite_db_vec.size()!=1) {
543  LogCvmfs(kLogCvmfs, kLogStderr, "-Z requires a single DB file");
544  exit(1);
545  }
546  if(isDatabaseMarkedComplete(sqlite_db_vec[0].c_str())) {
547  LogCvmfs(kLogCvmfs, kLogStderr, "DB file is already marked as completed_graft");
548  exit(0);
549  } else {
550  LogCvmfs(kLogCvmfs, kLogStderr, "DB file is not marked as completed_graft");
551  }
552  }
553 
554  string config_file = kConfigDir + repo_name + "/config";
555  string stratum0;
556  string proxy;
557 
558  string additional_prefix="";
559  bool has_additional_prefix=false;
560  if (args.find('p') != args.end()) {
561  additional_prefix=*args.find('p')->second;
562  additional_prefix = sanitise_name(additional_prefix.c_str(), true);
563  if( additional_prefix.back() != '/' ) {
564  additional_prefix += "/";
565  }
566  has_additional_prefix= true;
567  LogCvmfs(kLogCvmfs, kLogStdout, "Adding additional prefix %s to lease and all paths", additional_prefix.c_str() );
568  // now we are confident that any additional prefix has no leading / and does have a tailing /
569  }
570  auto config_map = load_config(config_file);
571 
572  if (args.find('g') != args.end()) {
573  g_gateway_url = *args.find('g')->second;
574  } else {
575  g_gateway_url = retrieve_config(config_map, "CVMFS_GATEWAY");
576  }
577  if (args.find('w') != args.end()) {
578  stratum0 = *args.find('w')->second;
579  } else {
580  stratum0 = retrieve_config(config_map, "CVMFS_STRATUM0");
581  }
582 
583  if (args.find('@') != args.end()) {
584  proxy = *args.find('@')->second;
585  } else {
586  proxy = retrieve_config(config_map, "CVMFS_HTTP_PROXY");
587  }
588 
589  string lease_path="";
590  //bool lease_autodetected = false;
591  if (args.find('l') != args.end()) {
592  lease_path = *args.find('l')->second;
593  } else {
594  // lease path wasn't specified, so try to autodetect it
595  vector<string> paths = get_all_dirs_from_sqlite(
596  sqlite_db_vec, allow_additions, allow_deletions);
597  if (paths.size() == 0) {
598  LogCvmfs(kLogCvmfs, kLogStdout, "Database is empty, nothing to do");
599  return 0; // treat it as a success
600  }
601  lease_path = get_lease_from_paths(paths);
602  //lease_autodetected = true;
603  }
604 
605  if (has_additional_prefix) {
606  if (lease_path == "/" ) { lease_path = "/" + additional_prefix; }
607  else {
608  if ( lease_path.substr(0,1)=="/" ) {
609  lease_path = "/" + additional_prefix + lease_path.substr(1, lease_path.size()-1);
610  } else {
611  lease_path = "/" + additional_prefix + lease_path; // prefix is certain to have a trailing /
612  }
613  }
614  }
615  if (lease_path.substr(0,1)!="/") { lease_path = "/" + lease_path; }
616  LogCvmfs(kLogCvmfs, kLogStdout, "Lease path is %s", lease_path.c_str());
617 
618 
619  string public_keys = kConfigDir + repo_name + "/pubkey";
620  string key_file = kConfigDir + repo_name + "/gatewaykey";
621  string s3_file = kConfigDir + repo_name + "/s3.conf";
622 
623  if( args.find('k') != args.end()) {
624  public_keys = *args.find('k')->second;
625  }
626  if( args.find('s') != args.end()) {
627  key_file = *args.find('s')->second;
628  }
629  if( args.find('3') != args.end()) {
630  s3_file = *args.find('3')->second;
631  }
632 
633  CUSTOM_ASSERT(access(public_keys.c_str(), R_OK) == 0, "%s is not readable", public_keys.c_str());
634  CUSTOM_ASSERT(access(key_file.c_str(), R_OK) == 0, "%s is not readable", key_file.c_str());
635 
636 // string spooler_definition_string = string("gw,,") + g_gateway_url;
637  // create a spooler that will upload to S3
638  string spooler_definition_string = string("S3,") + dir_temp + "," + repo_name + "@" + s3_file;
639 
640  // load gateway lease
642  LogCvmfs(kLogCvmfs, kLogStderr, "gateway::ReadKeys failed");
643  return 1;
644  }
645 
646  uint64_t current_revision=0;
647  std::string current_root_hash="";
648 
649  // acquire lease and save token to a file in the tmpdir
650  LogCvmfs(kLogCvmfs, kLogStdout, "Acquiring gateway lease on %s",
651  lease_path.c_str());
653  repo_name + lease_path, g_gateway_url, force_cancel_lease,
654  &current_revision, current_root_hash, lease_busy_retry_interval);
655 
656 
657  char *_tmpfile = strdup( (dir_temp + "/gateway_session_token_XXXXXX").c_str() );
658  int temp_fd = mkstemp(_tmpfile);
659  g_session_token_file = string(_tmpfile);
660  free(_tmpfile);
661 
662  FILE *fout=fdopen(temp_fd, "wb");
663  CUSTOM_ASSERT(fout!=NULL, "failed to open session token file %s for writing", g_session_token_file.c_str());
664  fputs(g_session_token.c_str(), fout);
665  fclose(fout);
666 
667  // now start the lease refresh thread
668  pthread_t lease_thread;
669  if ( 0 != pthread_create( &lease_thread, NULL, lease_refresh_thread, NULL ) ) {
670  LogCvmfs(kLogCvmfs, kLogStderr, "Unable to start lease refresh thread");
671  cancel_lease();
672  return 1;
673  }
674 
675  // now initialise the various bits we need
676 
677  upload::SpoolerDefinition spooler_definition(
678  spooler_definition_string, shash::kSha1, zlib::kZlibDefault, false, true,
681 
682  if (args.find('q') != args.end()) {
683  spooler_definition.number_of_concurrent_uploads =
684  String2Uint64(*args.find('q')->second);
685  }
686 
687  upload::SpoolerDefinition spooler_definition_catalogs(
688  spooler_definition.Dup2DefaultCompression());
689 
691  upload::Spooler::Construct(spooler_definition_catalogs, nullptr));
692 
693  if (!spooler_catalogs.IsValid()) {
694  LogCvmfs(kLogCvmfs, kLogStderr, "spooler_catalogs invalid");
695  cancel_lease();
696  return 1;
697  }
698  if (!InitDownloadManager(true, proxy, kCatalogDownloadMultiplier)) {
699  LogCvmfs(kLogCvmfs, kLogStderr, "download manager init failed");
700  cancel_lease();
701  return 1;
702  }
703  if (!InitSignatureManager(public_keys, "")) {
704  LogCvmfs(kLogCvmfs, kLogStderr, "signature manager init failed");
705  cancel_lease();
706  return 1;
707  }
708 
710 
711  manifest = FetchRemoteManifest(stratum0, repo_name, shash::Any());
712 
713  if (!manifest.IsValid()) {
714  LogCvmfs(kLogCvmfs, kLogStderr, "manifest invalid");
715  cancel_lease();
716  return 1;
717  }
718 
719  if(current_revision > 0 ) {
720  if( current_revision == manifest->revision() ) {
721  if (current_root_hash != manifest->catalog_hash().ToString() ) {
722  LogCvmfs(kLogCvmfs, kLogStderr, "Mismatch between cvmfspublished and gateway hash for revision %lu (%s!=%s)", current_revision, current_root_hash.c_str(), manifest->catalog_hash().ToString().c_str() );
723  cancel_lease();
724  return 1;
725  } else {
726  LogCvmfs(kLogCvmfs, kLogStdout, "Gateway and .cvmfspublished agree on repo version %lu", current_revision );
727  }
728  }
729  if( current_revision > manifest->revision() ) {
730  LogCvmfs(kLogCvmfs, kLogStdout, "Gateway has supplied a newer revision than the current .cvmfspublished %lu > %lu", current_revision, manifest->revision() );
731  manifest->set_revision(current_revision);
732  manifest->set_catalog_hash( shash::MkFromHexPtr(shash::HexPtr(current_root_hash), shash::kSuffixCatalog));
733  } else if (current_revision < manifest->revision() ) {
734  LogCvmfs(kLogCvmfs, kLogStdout, "Gateway has supplied an older revision than the current .cvmfspublished %lu < %lu", current_revision, manifest->revision() );
735  }
736  } else {
737  LogCvmfs(kLogCvmfs, kLogStdout, "Gateway has not supplied a revision. Using .cvmfspublished" );
738  }
739 
740 
741  // get hash of current root catalog, remove terminal "C", encode it
742  string old_root_hash = manifest->catalog_hash().ToString(true);
743  string hash = old_root_hash.substr(0, old_root_hash.length() - 1);
744  shash::Any base_hash =
746  LogCvmfs(kLogCvmfs, kLogStdout, "old_root_hash: %s", old_root_hash.c_str());
747 
748  bool is_balanced = false;
749 
750  catalog::WritableCatalogManager catalog_manager(
751  base_hash, stratum0, dir_temp, spooler_catalogs.weak_ref(),
754  is_balanced, SyncParameters::kDefaultMaxWeight, SyncParameters::kDefaultMinWeight, dir_temp /* dir_cache */);
755 
756  catalog_manager.Init();
757 
758 
759  // now graft the contents of the DB
760  vector<sqlite3*> open_dbs;
761  for (auto&& db_file : sqlite_db_vec) {
762  sqlite3 *db;
763  CHECK_SQLITE_ERROR(sqlite3_open_v2(db_file.c_str(), &db, SQLITE_OPEN_READONLY, NULL), SQLITE_OK);
764  relax_db_locking(db);
765  open_dbs.push_back(db);
766  }
767  process_sqlite(open_dbs, catalog_manager, allow_additions,
768  allow_deletions, lease_path.substr(1), additional_prefix);
769  for (auto&& db : open_dbs) {
770  CHECK_SQLITE_ERROR(sqlite3_close_v2(db), SQLITE_OK);
771  }
772 
773  // commit changes
774  LogCvmfs(kLogCvmfs, kLogStdout, "Committing changes...");
775  if (!catalog_manager.Commit(false, false, manifest.weak_ref())) {
776  LogCvmfs(kLogCvmfs, kLogStderr, "something went wrong during sync");
777  cancel_lease();
778  return 1;
779  }
780 
781  // finalize the spooler
782  LogCvmfs(kLogCvmfs, kLogStdout, "Waiting for all uploads to finish...");
783  spooler_catalogs->WaitForUpload();
784 
785  LogCvmfs(kLogCvmfs, kLogStdout, "Exporting repository manifest");
786 
787  // We call FinalizeSession(true) this time, to also trigger the commit
788  // operation on the gateway machine (if the upstream is of type "gw").
789 
790  // Get the path of the new root catalog
791  const string new_root_hash = manifest->catalog_hash().ToString(true);
792 
793 // if (!spooler_catalogs->FinalizeSession(true, old_root_hash, new_root_hash,
794 // RepositoryTag())) {
795 // LogCvmfs(kLogCvmfs, kLogStderr, "Failed to commit the transaction");
796 // // lease is only released on success
797 // cancel_lease();
798 // return 1;
799 // }
800 
801  LogCvmfs(kLogCvmfs, kLogStdout, "Committing with priority %d", g_priority);
802 
803  bool ok = make_commit_on_gateway( old_root_hash, new_root_hash, g_priority );
804  if(!ok) {
805  LogCvmfs(kLogCvmfs, kLogStderr, "something went wrong during commit on gateway");
806  cancel_lease();
807  exit(1);
808  }
809 
810 
811  unlink( g_session_token_file.c_str() );
812 
813  g_stop_refresh=true;
814 
815 
816  if(check_completed_graft_property) {
817  setDatabaseMarkedComplete(sqlite_db_vec[0].c_str());
818  }
819 
820 
821 
822  return 0;
823 }
824 
825 size_t writeFunction(void *ptr, size_t size, size_t nmemb, std::string* data) {
826  return size * nmemb;
827 }
828 
829 void replaceAllSubstrings(std::string& str, const std::string& from, const std::string& to) {
830  if (from.empty()) {
831  return; // Avoid infinite loop if 'from' is an empty string.
832  }
833  size_t startPos = 0;
834  while ((startPos = str.find(from, startPos)) != std::string::npos) {
835  str.replace(startPos, from.length(), to);
836  startPos += to.length(); // Advance startPos to avoid replacing the substring just inserted.
837  }
838 }
839 
840 
842  const std::vector<sqlite3*> &dbs,
843  catalog::WritableCatalogManager &catalog_manager,
844  bool allow_additions, bool allow_deletions, const std::string &lease_path, const std::string &additional_prefix)
845 {
846  std::map<std::string, Directory> all_dirs;
847  std::map<std::string, std::vector<File>> all_files;
848  std::map<std::string, std::vector<Symlink>> all_symlinks;
849 
850  for (auto&& db : dbs) {
851  load_dirs(db, lease_path, additional_prefix, all_dirs);
852  }
853 
854  // put in a nested scope so we can free up memory of `dir_names`
855  {
856  LogCvmfs(kLogCvmfs, kLogStdout, "Precaching existing directories (starting from %s)", lease_path.c_str());
857  std::unordered_set<std::string> dir_names;
858  std::transform(all_dirs.begin(), all_dirs.end(), std::inserter(dir_names, dir_names.end()),
859  [](const std::pair<std::string, Directory>& pair) {
860  return MakeCatalogPath(pair.first);
861  });
862  catalog_manager.LoadCatalogs(MakeCatalogPath(lease_path), dir_names);
863  }
864 
865  for (auto&& db : dbs) {
866  load_files(db, lease_path, additional_prefix, all_files);
867  load_symlinks(db, lease_path, additional_prefix, all_symlinks);
868  }
869 
870  // perform all deletions first
871  if (allow_deletions) {
872  LogCvmfs(kLogCvmfs, kLogStdout, "Processing deletions...");
873  for (auto&& db : dbs) {
874  CHECK_SQLITE_ERROR(do_deletions(db, catalog_manager, lease_path, additional_prefix), SQLITE_OK);
875  }
876  }
877 
878  if (allow_additions) {
879  LogCvmfs(kLogCvmfs, kLogStdout, "Processing additions...");
880  // first ensure all directories are present and create missing ones
881  do_additions(all_dirs, all_files, all_symlinks, lease_path, catalog_manager);
882  }
883 }
884 
885 void add_dir_to_tree(std::string path, std::unordered_map<std::string, std::set<std::string>> &tree, const std::string &lease_path) {
886  tree[path];
887  std::string parent_path = get_parent(path);
888  // recursively create any missing parents in the tree
889  // avoid creating a loop when we insert the root path
890  while (path != parent_path && path != lease_path && !tree[parent_path].count(path)) {
891  tree[parent_path].insert(path);
892  path = parent_path;
893  parent_path = get_parent(path);
894  }
895 }
896 
898  const DirMap &all_dirs, const FileMap &all_files, const SymlinkMap &all_symlinks, const std::string &lease_path, catalog::WritableCatalogManager &catalog_manager) {
899  // STEP 1:
900  // - collect all the dirs/symlinks/files we need to process from the DB
901  // - build a tree of paths for DFS traversal
902  // - note the tree will contain all parent dirs of symlinks/files even if those are not
903  // explicitly added to the dirs table
904  std::unordered_map<std::string, std::set<std::string>> tree;
905  for (auto&& p : all_dirs) {
906  add_dir_to_tree(p.first, tree, lease_path);
907  }
908  for (auto&& p : all_files) {
909  add_dir_to_tree(p.first, tree, lease_path);
910  }
911  for (auto&& p : all_symlinks) {
912  add_dir_to_tree(p.first, tree, lease_path);
913  }
914  int row_count = static_cast<int>(tree.size());
915  int print_every = calculate_print_frequency(row_count);
916  int curr_row = 0;
917  LogCvmfs(kLogCvmfs, kLogStdout, "Changeset: %ld dirs, %ld files, %ld symlinks", tree.size(), all_files.size(), all_symlinks.size());
918 
919  // STEP 2:
920  // - process all the changes with DFS traversal
921  // - make directories in pre-order
922  // - add files/symlinks and schedule upload in post-order
923  catalog_manager.SetupSingleCatalogUploadCallback();
924  std::stack<string> dfs_stack;
925  for (auto&& p : tree) {
926  // figure out the starting point by checking whose parent is missing from the tree
927  if (p.first == "" || !tree.count(get_parent(p.first))) {
928  CUSTOM_ASSERT(dfs_stack.empty(), "provided DB input forms more than one path trees");
929  dfs_stack.push(p.first);
930  }
931  }
932  std::set<string> visited;
933  while (!dfs_stack.empty()) {
934  string curr_dir = dfs_stack.top();
935  // add content for the dir in post-order traversal
936  if (visited.count(curr_dir)) {
937  curr_row++;
938  if (all_symlinks.count(curr_dir)) {
939  add_symlinks(catalog_manager, all_symlinks.at(curr_dir));
940  }
941  if (all_files.count(curr_dir)) {
942  add_files(catalog_manager, all_files.at(curr_dir));
943  }
944  // snapshot the dir (if it's a nested catalog mountpoint)
945  catalog::DirectoryEntry dir_entry;
946  bool exists = false;
947  exists = catalog_manager.LookupDirEntry(MakeCatalogPath(curr_dir),
948  catalog::kLookupDefault, &dir_entry);
949  assert(exists); // the dir must exist at this point
950  if (dir_entry.IsNestedCatalogMountpoint() || dir_entry.IsNestedCatalogRoot()) {
951  catalog_manager.AddCatalogToQueue(curr_dir);
952  catalog_manager.ScheduleReadyCatalogs();
953  }
954  dfs_stack.pop();
955  SHOW_PROGRESS("directories", print_every, curr_row, row_count);
956  } else {
957  visited.insert(curr_dir);
958  // push children to the stack
959  auto it = tree.find(curr_dir);
960  if (it != tree.end()) {
961  for (auto&& child : it->second) {
962  dfs_stack.push(child);
963  }
964  tree.erase(it);
965  }
966  if (!all_dirs.count(curr_dir)) continue;
967 
968  // create the dir first in pre-order traversal
969  const IngestSQL::Directory& dir = all_dirs.at(curr_dir);
970  catalog::DirectoryEntry dir_entry;
971 
972  bool exists = false;
973  exists = catalog_manager.LookupDirEntry(MakeCatalogPath(curr_dir),
974  catalog::kLookupDefault, &dir_entry);
975  CUSTOM_ASSERT(!(exists && !S_ISDIR(dir_entry.mode_)), "Refusing to replace existing file/symlink at %s with a directory", dir.name.c_str());
976 
977  dir_entry.name_ = NameString(get_basename(dir.name));
978  dir_entry.mtime_ = dir.mtime / 1000000000;
979  dir_entry.mode_ = dir.mode | S_IFDIR;
980  dir_entry.mode_ &= (S_IFDIR | 0777);
981  dir_entry.uid_ = dir.owner;
982  dir_entry.gid_ = dir.grp;
983  dir_entry.has_xattrs_ = !dir.xattr.IsEmpty();
984 
985  bool add_nested_catalog=false;
986 
987  if (exists) {
988  catalog_manager.TouchDirectory(dir_entry, dir.xattr, dir.name);
989  if((!dir_entry.IsNestedCatalogMountpoint() && !dir_entry.IsNestedCatalogRoot()) && ( g_add_missing_catalogs || dir.nested ) ) {
990  add_nested_catalog=true;
991  LogCvmfs(kLogCvmfs, kLogVerboseMsg, "Touching existing directory %s and adding nested catalog", dir.name.c_str());
992  } else {
993  LogCvmfs(kLogCvmfs, kLogVerboseMsg, "Touching existing directory %s", dir.name.c_str());
994  }
995  } else {
996  LogCvmfs(kLogCvmfs, kLogVerboseMsg, "Adding directory [%s]", dir.name.c_str());
997  catalog_manager.AddDirectory(dir_entry, dir.xattr, get_parent(dir.name));
998  if(dir.nested) {add_nested_catalog=true;}
999  }
1000  if (add_nested_catalog) {
1001  // now add a .cvmfscatalog file
1002  // so that manual changes won't remove the nested catalog
1003  LogCvmfs(kLogCvmfs, kLogVerboseMsg, "Placing .cvmfscatalog file in [%s]", dir.name.c_str());
1005  dir2.name_ = NameString(".cvmfscatalog");
1006  dir2.mtime_ = dir.mtime / 1000000000;
1007  dir2.mode_ = (S_IFREG | 0666);
1008  dir2.uid_ = 0;
1009  dir2.gid_ = 0;
1010  dir2.has_xattrs_ = 0;
1011  dir2.checksum_ = shash::MkFromHexPtr(
1012  shash::HexPtr("da39a3ee5e6b4b0d3255bfef95601890afd80709"),
1013  shash::kSuffixNone); // hash of ""
1014  XattrList xattr2;
1015  catalog_manager.AddFile(dir2, xattr2, dir.name);
1016 
1017  LogCvmfs(kLogCvmfs, kLogVerboseMsg, "Creating Nested Catalog [%s]", dir.name.c_str());
1018  catalog_manager.CreateNestedCatalog(dir.name);
1019  }
1020  }
1021  }
1022 
1023  // sanity check that we have processed all the input
1024  CUSTOM_ASSERT(tree.empty(), "not all directories are processed, malformed input DB?");
1025  catalog_manager.RemoveSingleCatalogUploadCallback();
1026  return 0;
1027 }
1028 
1030  catalog::WritableCatalogManager &catalog_manager, const std::vector<Symlink> &symlinks)
1031 {
1032  for (auto&& symlink : symlinks) {
1035  XattrList xattr;
1036  bool exists = false;
1037  exists = catalog_manager.LookupDirEntry(MakeCatalogPath(symlink.name),
1038  catalog::kLookupDefault, &dir);
1039 
1040  dir2.name_ = NameString(get_basename(symlink.name));
1041  dir2.mtime_ = symlink.mtime / 1000000000;
1042  dir2.uid_ = symlink.owner;
1043  dir2.gid_ = symlink.grp;
1044  dir2.has_xattrs_ = false;
1045  dir2.symlink_ = LinkString(symlink.target);
1046  dir2.mode_ = S_IFLNK | 0777;
1047 
1048  int noop=false;
1049 
1050  if (exists) {
1051  if(symlink.skip_if_file_or_dir) {
1052  if(S_ISDIR(dir.mode_) || S_ISREG(dir.mode_)) {
1053  LogCvmfs(kLogCvmfs, kLogVerboseMsg, "File or directory for symlink [%s] exists, skipping symlink creation", symlink.name.c_str() );
1054  noop = true;
1055  } else if (S_ISLNK(dir.mode_)) {
1056  LogCvmfs(kLogCvmfs, kLogVerboseMsg, "Removing existing symlink [%s]",
1057  symlink.name.c_str());
1058  catalog_manager.RemoveFile(symlink.name);
1059  } else {
1060  CUSTOM_ASSERT(0, "unknown mode for dirent: %d", dir.mode_);
1061  }
1062  } else {
1063  CUSTOM_ASSERT(!S_ISDIR(dir.mode_), "Not removing directory [%s] to create symlink", symlink.name.c_str());
1064  LogCvmfs(kLogCvmfs, kLogVerboseMsg, "Removing existing file/symlink [%s]",
1065  symlink.name.c_str());
1066  catalog_manager.RemoveFile(symlink.name);
1067  }
1068  }
1069  if(!noop) {
1070  string parent = get_parent(symlink.name);
1071  LogCvmfs(kLogCvmfs, kLogVerboseMsg, "Adding symlink [%s] -> [%s]", symlink.name.c_str(), symlink.target.c_str());
1072  catalog_manager.AddFile(dir2, xattr, parent);
1073  }
1074  }
1075  return 0;
1076 }
1077 
1078 static int check_hash( const char*hash) {
1079  if (strlen(hash)!=40) { return 1;}
1080  for(int i=0; i<40; i++ ) {
1081  // < '0' || > 'f' || ( > '9' && < 'a' )
1082  if( hash[i]<0x30 || hash[i]>0x66 || ( hash[i]>0x39 && hash[i] <0x61 )) {
1083  return 1;
1084  }
1085  }
1086  return 0;
1087 }
1088 
1089 bool check_prefix(const std::string &path , const std::string &prefix) {
1090  if (prefix=="" || prefix=="/") {return true;}
1091  if ( "/"+path == prefix ) { return true; }
1092  if(!HasPrefix( path, prefix, false)) {
1093  LogCvmfs(kLogCvmfs, kLogVerboseMsg, "Entry %s is outside lease path: %s", path.c_str(), prefix.c_str());
1094  return false;
1095  }
1096  return true;
1097 }
1098 
1099 void swissknife::IngestSQL::load_dirs(sqlite3 *db, const std::string &lease_path, const std::string &additional_prefix, std::map<std::string, Directory> &all_dirs) {
1100  sqlite3_stmt *stmt;
1101  int schema_revision = get_db_schema_revision(db);
1102  string select_stmt = "SELECT name, mode, mtime, owner, grp, acl, nested FROM dirs";
1103  if (schema_revision <= 3) {
1104  select_stmt = "SELECT name, mode, mtime, owner, grp, acl FROM dirs";
1105  }
1106  int ret = sqlite3_prepare_v2(db, select_stmt.c_str(), -1, &stmt, NULL);
1107  CHECK_SQLITE_ERROR(ret, SQLITE_OK);
1108  while (sqlite3_step(stmt) == SQLITE_ROW) {
1109  char *name_cstr = (char *)sqlite3_column_text(stmt, 0);
1110  mode_t mode = sqlite3_column_int(stmt, 1);
1111  time_t mtime = sqlite3_column_int64(stmt, 2);
1112  uid_t owner = sqlite3_column_int(stmt, 3);
1113  gid_t grp = sqlite3_column_int(stmt, 4);
1114  int nested = schema_revision <= 3 ? 1 : sqlite3_column_int(stmt, 6);
1115 
1116  string name = additional_prefix + sanitise_name(name_cstr);
1117  CUSTOM_ASSERT(check_prefix(name, lease_path), "%s is not below lease path %s", name.c_str(), lease_path.c_str());
1118 
1119  Directory dir(name, mtime, mode, owner, grp, nested);
1120  char *acl = (char *)sqlite3_column_text(stmt, 5);
1121  dir.xattr = marshal_xattrs(acl);
1122  all_dirs.insert(std::make_pair(name, dir));
1123  }
1124  CHECK_SQLITE_ERROR(sqlite3_finalize(stmt), SQLITE_OK);
1125 }
1126 
1127 void swissknife::IngestSQL::load_files(sqlite3 *db, const std::string &lease_path, const std::string &additional_prefix, std::map<std::string, std::vector<File>> &all_files) {
1128  sqlite3_stmt *stmt;
1129  int schema_revision = get_db_schema_revision(db);
1130  string select_stmt = "SELECT name, mode, mtime, owner, grp, size, hashes, internal, compressed FROM files";
1131  if (schema_revision <= 2) {
1132  select_stmt = "SELECT name, mode, mtime, owner, grp, size, hashes, internal FROM files";
1133  }
1134  int ret = sqlite3_prepare_v2(db, select_stmt.c_str(), -1, &stmt, NULL);
1135  CHECK_SQLITE_ERROR(ret, SQLITE_OK);
1136  while (sqlite3_step(stmt) == SQLITE_ROW) {
1137  char *name = (char *)sqlite3_column_text(stmt, 0);
1138  mode_t mode = sqlite3_column_int(stmt, 1);
1139  time_t mtime = sqlite3_column_int64(stmt, 2);
1140  uid_t owner = sqlite3_column_int(stmt, 3);
1141  gid_t grp = sqlite3_column_int(stmt, 4);
1142  size_t size = sqlite3_column_int64(stmt, 5);
1143  char *hashes_cstr = (char *)sqlite3_column_text(stmt, 6);
1144  int internal = sqlite3_column_int(stmt, 7);
1145  int compressed = schema_revision <= 2 ? 0 : sqlite3_column_int(stmt, 8);
1146 
1147  string names = additional_prefix + sanitise_name(name);
1148  CUSTOM_ASSERT(check_prefix(names, lease_path), "%s is not below lease path %s", names.c_str(), lease_path.c_str());
1149  string parent_dir = get_parent(names);
1150 
1151  if (!all_files.count(parent_dir)) {
1152  all_files[parent_dir] = vector<swissknife::IngestSQL::File>();
1153  }
1154  all_files[parent_dir].emplace_back(std::move(names), mtime, size, owner, grp, mode, internal, compressed);
1155 
1156  // tokenize hashes
1157  char *ref;
1158  char *tok;
1159  tok = strtok_r(hashes_cstr, ",", &ref);
1160  vector<off_t> offsets;
1161  vector<size_t> sizes;
1162  vector<shash::Any> hashes;
1163  off_t offset = 0;
1164 
1165  CUSTOM_ASSERT(size>=0, "file size cannot be negative [%s]", names.c_str());
1166  size_t kChunkSize = internal ? kInternalChunkSize : kExternalChunkSize;
1167 
1168  while (tok) {
1169  offsets.push_back(offset);
1170  // TODO: check the hash format
1171  CUSTOM_ASSERT(check_hash(tok)==0, "provided hash for [%s] is invalid: %s", names.c_str(), tok);
1172  hashes.push_back(
1174  tok = strtok_r(NULL, ",", &ref);
1175  offset += kChunkSize; // in the future we might want variable chunk
1176  // sizes specified in the DB
1177  }
1178  size_t expected_num_chunks = size/kChunkSize;
1179  if (expected_num_chunks * (size_t)kChunkSize < (size_t) size || size==0 ) { expected_num_chunks++; }
1180  CUSTOM_ASSERT(offsets.size() == expected_num_chunks, "offsets size %ld does not match expected number of chunks %ld", offsets.size(), expected_num_chunks);
1181  for (size_t i = 0; i < offsets.size() - 1; i++) {
1182  sizes.push_back(size_t(offsets[i + 1] - offsets[i]));
1183  }
1184 
1185  sizes.push_back(size_t(size - offsets[offsets.size() - 1]));
1186  for (size_t i = 0; i < offsets.size(); i++) {
1187  FileChunk chunk = FileChunk(hashes[i], offsets[i], sizes[i]);
1188  all_files[parent_dir].back().chunks.PushBack(chunk);
1189  }
1190  }
1191  CHECK_SQLITE_ERROR(sqlite3_finalize(stmt), SQLITE_OK);
1192 }
1193 
1194 void swissknife::IngestSQL::load_symlinks(sqlite3 *db, const std::string &lease_path, const std::string &additional_prefix, std::map<std::string, std::vector<Symlink>> &all_symlinks) {
1195  sqlite3_stmt *stmt;
1196  string select_stmt = "SELECT name, target, mtime, owner, grp, skip_if_file_or_dir FROM links";
1197  int ret = sqlite3_prepare_v2(db, select_stmt.c_str(), -1, &stmt, NULL);
1198  CHECK_SQLITE_ERROR(ret, SQLITE_OK);
1199  while (sqlite3_step(stmt) == SQLITE_ROW) {
1200  char *name_cstr = (char *)sqlite3_column_text(stmt, 0);
1201  char *target_cstr = (char *)sqlite3_column_text(stmt, 1);
1202  time_t mtime = sqlite3_column_int64(stmt, 2);
1203  uid_t owner = sqlite3_column_int(stmt, 3);
1204  gid_t grp = sqlite3_column_int(stmt, 4);
1205  int skip_if_file_or_dir = sqlite3_column_int(stmt, 5);
1206 
1207  string names = additional_prefix + sanitise_name(name_cstr);
1208  CUSTOM_ASSERT(check_prefix(names, lease_path), "%s is not below lease path %s", names.c_str(), lease_path.c_str());
1209  string target= target_cstr;
1210  string parent_dir = get_parent(names);
1211 
1212  if (!all_symlinks.count(parent_dir)) {
1213  all_symlinks[parent_dir] = vector<swissknife::IngestSQL::Symlink>();
1214  }
1215  all_symlinks[parent_dir].emplace_back(std::move(names), std::move(target), mtime, owner, grp, skip_if_file_or_dir);
1216  }
1217  CHECK_SQLITE_ERROR(sqlite3_finalize(stmt), SQLITE_OK);
1218 }
1219 
1221  catalog::WritableCatalogManager &catalog_manager, const std::vector<File> &files)
1222 {
1223  for (auto&& file : files) {
1225  XattrList xattr;
1226  bool exists = false;
1227  exists = catalog_manager.LookupDirEntry(MakeCatalogPath(file.name),
1228  catalog::kLookupDefault, &dir);
1229 
1230  dir.name_ = NameString(get_basename(file.name));
1231  dir.mtime_ = file.mtime / 1000000000;
1232  dir.mode_ = file.mode | S_IFREG;
1233  dir.mode_ &= (S_IFREG | 0777);
1234  dir.uid_ = file.owner;
1235  dir.gid_ = file.grp;
1236  dir.size_ = file.size;
1237  dir.has_xattrs_ = false;
1238  dir.is_external_file_ = !file.internal;
1239  dir.set_is_chunked_file(true);
1241  shash::HexPtr("0000000000000000000000000000000000000000"),
1243 
1244  // compression is permitted only for internal data
1245  CUSTOM_ASSERT(file.internal || (!file.internal && file.compressed<2), "compression is only allowed for internal data [%s]", file.name.c_str());
1246 
1247  switch (file.compressed) {
1248  case 1: // Uncompressed
1250  break;
1251  case 2: // Compressed with Zlib
1253  break;
1254  // future cases: different compression schemes
1255  default: // default behaviour: compressed if internal, content-addressed. Uncompressed if external
1257  }
1258 
1259  if (exists) {
1260  CUSTOM_ASSERT(!S_ISDIR(dir.mode()) && !S_ISLNK(dir.mode()), "Refusing to replace existing dir/symlink at %s with a file", file.name.c_str());
1261  LogCvmfs(kLogCvmfs, kLogVerboseMsg, "Removing existing file [%s]", file.name.c_str());
1262  catalog_manager.RemoveFile(file.name);
1263  }
1264  string parent = get_parent(file.name);
1265  LogCvmfs(kLogCvmfs, kLogVerboseMsg, "Adding chunked file [%s]", file.name.c_str());
1266  catalog_manager.AddChunkedFile(dir, xattr, parent, file.chunks);
1267  }
1268 
1269  return 0;
1270 }
1271 
1273  sqlite3 *db, catalog::WritableCatalogManager &catalog_manager, const std::string &lease_path, const std::string &additional_prefix) {
1274  sqlite3_stmt *stmt;
1275  int row_count = get_row_count(db, "deletions");
1276  int print_every = calculate_print_frequency(row_count);
1277  int curr_row = 0;
1278  int ret = sqlite3_prepare_v2(db, "SELECT name, directory, file, link FROM deletions ORDER BY length(name) DESC", -1, &stmt, NULL);
1279  CHECK_SQLITE_ERROR(ret, SQLITE_OK);
1280  while (sqlite3_step(stmt) == SQLITE_ROW) {
1281  curr_row++;
1282 
1283  char *name = (char *)sqlite3_column_text(stmt, 0);
1284  int64_t isdir = sqlite3_column_int64(stmt, 1);
1285  int64_t isfile = sqlite3_column_int64(stmt, 2);
1286  int64_t islink = sqlite3_column_int64(stmt, 3);
1287 
1288  string names = additional_prefix + sanitise_name(name);
1289  CUSTOM_ASSERT(check_prefix( names, lease_path), "%s is not below lease path %s", names.c_str(), lease_path.c_str());
1290 
1291  catalog::DirectoryEntry dirent;
1292  bool exists = false;
1293  exists = catalog_manager.LookupDirEntry(MakeCatalogPath(names),
1294  catalog::kLookupDefault, &dirent);
1295  if(exists) {
1296  if( (isdir && S_ISDIR(dirent.mode()) )
1297  || (islink && S_ISLNK(dirent.mode()) )
1298  || (isfile && S_ISREG(dirent.mode()) ) ) {
1299  if (S_ISDIR(dirent.mode())) {
1300  PathString names_path(names);
1301  recursively_delete_directory(names_path, catalog_manager);
1302  } else {
1303  LogCvmfs(kLogCvmfs, kLogVerboseMsg, "Removing link/file [%s]", names.c_str());
1304  catalog_manager.RemoveFile(names);
1305  }
1306  } else {
1307  LogCvmfs(kLogCvmfs, kLogVerboseMsg, "Mismatch in deletion type, not deleting: [%s] (dir %ld/%d , link %ld/%d, file %ld/%d)", names.c_str(), isdir, S_ISDIR(dirent.mode()), islink, S_ISLNK(dirent.mode()), isfile, S_ISREG(dirent.mode()) );
1308  }
1309  } else {
1310  LogCvmfs(kLogCvmfs, kLogVerboseMsg, "Not Removing non-existent [%s]",
1311  names.c_str());
1312  }
1313 
1314  SHOW_PROGRESS("deletions", print_every, curr_row, row_count);
1315  }
1316  ret = sqlite3_finalize(stmt);
1317  return ret;
1318 }
1319 
1320 const char*schema[] = {
1321  "PRAGMA journal_mode=WAL;",
1322 
1323  "CREATE TABLE IF NOT EXISTS dirs ( \
1324  name TEXT PRIMARY KEY, \
1325  mode INTEGER NOT NULL DEFAULT 493,\
1326  mtime INTEGER NOT NULL DEFAULT (unixepoch()),\
1327  owner INTEGER NOT NULL DEFAULT 0, \
1328  grp INTEGER NOT NULL DEFAULT 0, \
1329  acl TEXT NOT NULL DEFAULT '', \
1330  nested INTEGER DEFAULT 1);",
1331 
1332  "CREATE TABLE IF NOT EXISTS files ( \
1333  name TEXT PRIMARY KEY, \
1334  mode INTEGER NOT NULL DEFAULT 420, \
1335  mtime INTEGER NOT NULL DEFAULT (unixepoch()),\
1336  owner INTEGER NOT NULL DEFAULT 0,\
1337  grp INTEGER NOT NULL DEFAULT 0,\
1338  size INTEGER NOT NULL DEFAULT 0,\
1339  hashes TEXT NOT NULL DEFAULT '',\
1340  internal INTEGER NOT NULL DEFAULT 0,\
1341  compressed INTEGER NOT NULL DEFAULT 0\
1342  );",
1343 
1344  "CREATE TABLE IF NOT EXISTS links (\
1345  name TEXT PRIMARY KEY,\
1346  target TEXT NOT NULL DEFAULT '',\
1347  mtime INTEGER NOT NULL DEFAULT (unixepoch()),\
1348  owner INTEGER NOT NULL DEFAULT 0,\
1349  grp INTEGER NOT NULL DEFAULT 0,\
1350  skip_if_file_or_dir INTEGER NOT NULL DEFAULT 0\
1351  );",
1352 
1353  "CREATE TABLE IF NOT EXISTS deletions (\
1354  name TEXT PRIMARY KEY,\
1355  directory INTEGER NOT NULL DEFAULT 0,\
1356  file INTEGER NOT NULL DEFAULT 0,\
1357  link INTEGER NOT NULL DEFAULT 0\
1358  );",
1359 
1360  "CREATE TABLE IF NOT EXISTS properties (\
1361  key TEXT PRIMARY KEY,\
1362  value TEXT NOT NULL\
1363  );",
1364 
1365  "INSERT INTO properties VALUES ('schema_revision', '4') ON CONFLICT DO NOTHING;",
1366  NULL
1367 };
1368 
1369 static void create_empty_database( string& filename ) {
1370  sqlite3 *db_out;
1371  LogCvmfs(kLogCvmfs, kLogStdout, "Creating empty database file %s", filename.c_str());
1372  int ret = sqlite3_open_v2(filename.c_str(), &db_out, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL);
1373  CHECK_SQLITE_ERROR(ret, SQLITE_OK);
1374  relax_db_locking(db_out);
1375 
1376  const char **ptr = schema;
1377  while (*ptr!=NULL) {
1378  ret = sqlite3_exec(db_out, *ptr, NULL, NULL, NULL);
1379  CHECK_SQLITE_ERROR(ret, SQLITE_OK);
1380  ptr++;
1381  }
1382  sqlite3_close(db_out);
1383 }
1384 
1387 
1388  // Add all names
1389  catalog::StatEntryList listing_from_catalog;
1390  bool retval = catalog_manager.ListingStat(PathString( "/" + path.ToString()), &listing_from_catalog);
1391 
1392  CUSTOM_ASSERT(retval, "failed to call ListingStat for %s", path.c_str());
1393 
1394  if(!catalog_manager.IsTransitionPoint(path.ToString())) {
1395 
1396  for (unsigned i = 0; i < listing_from_catalog.size(); ++i) {
1397  PathString entry_path;
1398  entry_path.Assign(path);
1399  entry_path.Append("/", 1);
1400  entry_path.Append(listing_from_catalog.AtPtr(i)->name.GetChars(),
1401  listing_from_catalog.AtPtr(i)->name.GetLength());
1402 
1403  if( S_ISDIR( listing_from_catalog.AtPtr(i)->info.st_mode) ) {
1404  LogCvmfs(kLogCvmfs, kLogVerboseMsg, "Recursing into %s/", entry_path.ToString().c_str());
1405  recursively_delete_directory(entry_path, catalog_manager);
1406 
1407 
1408  } else {
1409  LogCvmfs(kLogCvmfs, kLogVerboseMsg, " Recursively removing %s", entry_path.ToString().c_str());
1410  catalog_manager.RemoveFile(entry_path.ToString());
1411  }
1412 
1413  }
1414  } else {
1415  LogCvmfs(kLogCvmfs, kLogVerboseMsg, "Removing nested catalog %s", path.ToString().c_str());
1416  catalog_manager.RemoveNestedCatalog(path.ToString(), false);
1417  }
1418  LogCvmfs(kLogCvmfs, kLogVerboseMsg, "Removing directory %s", path.ToString().c_str());
1419  catalog_manager.RemoveDirectory(path.ToString());
1420 
1421 }
1422 
1423 
1424 static void relax_db_locking(sqlite3 *db) {
1425  int ret=0;
1426  ret = sqlite3_exec(db, "PRAGMA temp_store=2", NULL, NULL, NULL);
1427  CHECK_SQLITE_ERROR(ret, SQLITE_OK);
1428  ret = sqlite3_exec(db, "PRAGMA synchronous=OFF", NULL, NULL, NULL);
1429  CHECK_SQLITE_ERROR(ret, SQLITE_OK);
1430 }
1431 
1432 
1433 extern "C" void* lease_refresh_thread(void *payload) {
1434  while( !g_stop_refresh ) {
1435  sleep(2);
1436  refresh_lease();
1437  }
1438  return NULL;
1439 }
1440 
1441 static bool isDatabaseMarkedComplete(const char *dbfile) {
1442  int ret;
1443  sqlite3 *db;
1444  sqlite3_stmt *stmt;
1445  bool retval=false;
1446 
1447  ret = sqlite3_open(dbfile, &db);
1448  if (ret!=SQLITE_OK) { return false; }
1449 
1450  const char *req = "SELECT value FROM properties WHERE key='completed_graft'";
1451  ret = sqlite3_prepare_v2(db, req, -1, &stmt, NULL);
1452  if (ret!=SQLITE_OK) {
1453  return false;
1454  }
1455  if(sqlite3_step(stmt) == SQLITE_ROW) {
1456  int id = sqlite3_column_int(stmt,0);
1457  if(id>0) {
1458  retval=true;
1459  }
1460  }
1461  sqlite3_close(db);
1462  return retval;
1463 }
1464 
1465 static void setDatabaseMarkedComplete(const char *dbfile) {
1466  int ret;
1467  sqlite3 *db;
1468  char *err;
1469 
1470  ret = sqlite3_open(dbfile, &db);
1471  if (ret!=SQLITE_OK) { return; }
1472 
1473  const char *req = "INSERT INTO properties (key, value) VALUES ('completed_graft',1) ON CONFLICT(key) DO UPDATE SET value=1 WHERE key='completed_graft'";
1474 
1475  ret = sqlite3_exec(db, req, 0, 0, &err);
1476  if (ret!=SQLITE_OK) {
1477  return;
1478  }
1479  sqlite3_close(db);
1480 }
1481 
static bool g_lease_acquired
void load_symlinks(sqlite3 *db, const std::string &lease_path, const std::string &additional_prefix, std::map< std::string, std::vector< Symlink >> &all_symlinks)
void SetLogVerbosity(const LogLevels max_level)
Definition: logging.cc:258
static int check_hash(const char *hash)
bool IsSubPath(const PathString &parent, const PathString &path)
Definition: shortstring.cc:47
void AddChunkedFile(const DirectoryEntryBase &entry, const XattrList &xattrs, const std::string &parent_directory, const FileChunkList &file_chunks)
static const size_t kDefaultMaxFileChunkSize
const manifest::Manifest * manifest() const
Definition: repository.h:125
ShortString< kDefaultMaxName, 1 > NameString
Definition: shortstring.h:214
std::map< std::string, std::vector< File > > FileMap
static string g_gateway_secret
std::map< std::string, std::vector< Symlink > > SymlinkMap
void set_is_chunked_file(const bool val)
void AddDirectory(const DirectoryEntryBase &entry, const XattrList &xattrs, const std::string &parent_directory)
bool MakeEndRequest(const std::string &method, const std::string &key_id, const std::string &secret, const std::string &session_token, const std::string &repo_service_url, const std::string &request_payload, CurlBuffer *reply, bool expect_final_revision)
T * weak_ref() const
Definition: pointer.h:46
static void create_empty_database(string &filename)
void process_sqlite(const std::vector< sqlite3 * > &dbs, catalog::WritableCatalogManager &catalog_manager, bool allow_additions, bool allow_deletions, const std::string &lease_path, const std::string &additional_prefix)
static string get_parent(const string &path)
SpoolerDefinition Dup2DefaultCompression() const
static vector< string > get_all_dirs_from_sqlite(vector< string > &sqlite_db_vec, bool include_additions, bool include_deletions)
static const unsigned kDefaultMaxWeight
LeaseReply ParseAcquireReplyWithRevision(const CurlBuffer &buffer, std::string *session_token, uint64_t *current_revision, std::string &current_root_hash)
static string MakeCatalogPath(const std::string &relative_path)
bool Set(const std::string &key, const std::string &value)
Definition: xattr.cc:163
static string g_gateway_url
static const unsigned kDefaultFileMbyteLimit
void RemoveDirectory(const std::string &directory_path)
void Assign(const char *chars, const unsigned length)
Definition: shortstring.h:61
static void cancel_lease()
void load_files(sqlite3 *db, const std::string &lease_path, const std::string &additional_prefix, std::map< std::string, std::vector< File >> &all_files)
bool g_log_with_time
gid_t gid_
static const unsigned kDefaultNestedKcatalogLimit
static void setDatabaseMarkedComplete(const char *dbfile)
static const unsigned kDefaultRootKcatalogLimit
static string get_basename(const string &path)
int kCatalogDownloadMultiplier
#define SHOW_PROGRESS(item, freq, curr, total)
static void recursively_delete_directory(PathString &path, catalog::WritableCatalogManager &catalog_manager)
manifest::Manifest * FetchRemoteManifest(const std::string &repository_url, const std::string &repository_name, const shash::Any &base_hash=shash::Any()) const
Definition: server_tool.cc:121
bool IsEmpty() const
Definition: xattr.h:40
bool ListingStat(const PathString &path, StatEntryList *listing)
assert((mem||(size==0))&&"Out Of Memory")
static string get_lease_from_paths(vector< string > paths)
std::map< std::string, Directory > DirMap
void load_dirs(sqlite3 *db, const std::string &lease_path, const std::string &additional_prefix, std::map< std::string, Directory > &all_dirs)
static vector< string > get_file_list(string &path)
static string acquire_lease(const string &key_id, const string &secret, const string &lease_path, const string &repo_service_url, bool force_cancel_lease, uint64_t *current_revision, string &current_root_hash, unsigned int refresh_interval)
bool has_xattrs_
static void on_signal(int sig)
unsigned int mode() const
static int g_priority
const unsigned kLookupDefault
Definition: catalog_mgr.h:43
uint64_t size_
static bool check_prefix(const std::string &path, const std::string &prefix)
static const unsigned kInternalChunkSize
static const size_t kDefaultMinFileChunkSize
static void relax_db_locking(sqlite3 *db)
bool IsNestedCatalogMountpoint() const
bool IsTransitionPoint(const std::string &mountpoint)
void add_dir_to_tree(std::string path, std::unordered_map< std::string, std::set< std::string >> &tree, const std::string &lease_path)
bool IsNestedCatalogRoot() const
NameString name_
void TouchDirectory(const DirectoryEntryBase &entry, const XattrList &xattrs, const std::string &directory_path)
static int get_db_schema_revision(sqlite3 *db, const std::string &db_name="")
std::vector< DirectoryEntry > DirectoryEntryList
zlib::Algorithms compression_algorithm_
static bool g_add_missing_catalogs
#define CHECK_SQLITE_ERROR(ret, expected)
download::DownloadManager * download_manager() const
Definition: server_tool.cc:96
static void refresh_lease()
void replaceAllSubstrings(std::string &str, const std::string &from, const std::string &to)
static const unsigned kDefaultLeaseBusyRetryInterval
std::unordered_map< string, string > load_config(const string &config_file)
static int calculate_print_frequency(int total)
static string g_session_token
bool HasSuffix(const std::string &str, const std::string &suffix, const bool ignore_case)
Definition: string.cc:296
static const unsigned kDefaultMinWeight
const char kSuffixCatalog
Definition: hash.h:54
int acl_from_text_to_xattr_value(const string &textual_acl, char *&o_binary_acl, size_t &o_size, bool &o_equiv_mode)
Definition: acl.cc:250
static string g_gateway_key_id
int add_symlinks(catalog::WritableCatalogManager &catalog_manager, const std::vector< Symlink > &symlinks)
perf::Statistics * statistics()
Definition: server_tool.h:47
std::string data
bool is_external_file_
int Main(const ArgumentList &args)
int add_files(catalog::WritableCatalogManager &catalog_manager, const std::vector< File > &files)
static time_t g_last_lease_refresh
void AddCatalogToQueue(const std::string &path)
bool ReadKeys(const std::string &key_file_name, std::string *key_id, std::string *secret)
Definition: gateway_util.cc:37
static bool g_stop_refresh
time_t mtime_
void Append(const char *chars, const unsigned length)
Definition: shortstring.h:80
static const unsigned kExternalChunkSize
bool IsValid() const
Definition: pointer.h:47
bool HasPrefix(const string &str, const string &prefix, const bool ignore_case)
Definition: string.cc:279
#define CUSTOM_ASSERT(check, msg,...)
void RemoveFile(const std::string &file_path)
string retrieve_config(std::unordered_map< string, string > &config_map, const string &key)
int do_additions(const DirMap &all_dirs, const FileMap &all_files, const SymlinkMap &all_symlinks, const std::string &lease_path, catalog::WritableCatalogManager &catalog_manager)
static int get_row_count(sqlite3 *db, const std::string &table_name)
void RemoveNestedCatalog(const std::string &mountpoint, const bool merge=true)
bool InitSignatureManager(const std::string &pubkey_path, const std::string &certificate_path="", const std::string &private_key_path="")
Definition: server_tool.cc:44
std::string ToString() const
Definition: shortstring.h:139
static string g_s3_file
size_t writeFunction(void *ptr, size_t size, size_t nmemb, std::string *data)
int do_deletions(sqlite3 *db, catalog::WritableCatalogManager &catalog_manager, const std::string &lease_path, const std::string &additional_prefix)
ShortString< kDefaultMaxLink, 2 > LinkString
Definition: shortstring.h:215
static void MakeAcquireRequest(const gateway::GatewayKey &key, const std::string &repo_path, const std::string &repo_service_url, int llvl, CurlBuffer *buffer)
uint64_t String2Uint64(const string &value)
Definition: string.cc:240
std::map< char, SharedPtr< std::string > > ArgumentList
Definition: swissknife.h:72
unsigned int mode_
void * lease_refresh_thread(void *payload)
ShortString< kDefaultMaxPath, 0 > PathString
Definition: shortstring.h:213
void LoadCatalogs(const std::string &base_path, const std::unordered_set< std::string > &dirs)
static const size_t kDefaultAvgFileChunkSize
static uint64_t make_commit_on_gateway(const std::string &old_root_hash, const std::string &new_root_hash, int priority)
Any MkFromHexPtr(const HexPtr hex, const char suffix)
Definition: hash.cc:82
const int kLogVerboseMsg
static string sanitise_name(const char *name_cstr, bool allow_leading_slash)
static const unsigned kLeaseRefreshInterval
shash::Any checksum_
std::string MakeCanonicalPath(const std::string &path)
Definition: posix.cc:98
const upload::Spooler * spooler_catalogs() const
Definition: repository.h:321
bool InitDownloadManager(const bool follow_redirects, const std::string &proxy, const unsigned max_pool_handles=1)
Definition: server_tool.cc:17
void AddFile(const DirectoryEntryBase &entry, const XattrList &xattrs, const std::string &parent_directory)
static XattrList marshal_xattrs(const char *acl)
const char * c_str() const
Definition: shortstring.h:143
LinkString symlink_
static LeaseReply ParseDropReply(const CurlBuffer &buffer, int llvl)
static void size_t size
Definition: smalloc.h:54
const char kSuffixNone
Definition: hash.h:53
static bool isDatabaseMarkedComplete(const char *dbfile)
bool LookupDirEntry(const std::string &path, const LookupOptions options, DirectoryEntry *dirent)
const Item * AtPtr(const size_t index) const
Definition: bigvector.h:53
void CreateNestedCatalog(const std::string &mountpoint)
static string g_session_token_file
size_t size() const
Definition: bigvector.h:117
uid_t uid_
const char * schema[]
CVMFS_EXPORT void LogCvmfs(const LogSource source, const int mask, const char *format,...)
Definition: logging.cc:545