bes  Updated for version 3.20.8
BESCatalogDirectory.cc
1 // BESCatalogDirectory.cc
2 
3 // This file is part of bes, A C++ back-end server implementation framework
4 // for the OPeNDAP Data Access Protocol.
5 
6 // Copyright (c) 2004-2009 University Corporation for Atmospheric Research
7 // Author: Patrick West <pwest@ucar.edu> and Jose Garcia <jgarcia@ucar.edu>
8 //
9 // This library is free software; you can redistribute it and/or
10 // modify it under the terms of the GNU Lesser General Public
11 // License as published by the Free Software Foundation; either
12 // version 2.1 of the License, or (at your option) any later version.
13 //
14 // This library is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 // Lesser General Public License for more details.
18 //
19 // You should have received a copy of the GNU Lesser General Public
20 // License along with this library; if not, write to the Free Software
21 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22 //
23 // You can contact University Corporation for Atmospheric Research at
24 // 3080 Center Green Drive, Boulder, CO 80301
25 
26 // (c) COPYRIGHT University Corporation for Atmospheric Research 2004-2005
27 // Please read the full copyright statement in the file COPYRIGHT_UCAR.
28 //
29 // Authors:
30 // pwest Patrick West <pwest@ucar.edu>
31 // jgarcia Jose Garcia <jgarcia@ucar.edu>
32 
33 #include "config.h"
34 
35 #include <sys/types.h>
36 #include <sys/stat.h>
37 #include <dirent.h>
38 
39 #include <cstring>
40 #include <cerrno>
41 
42 #include <sstream>
43 #include <cassert>
44 
45 #include <memory>
46 #include <algorithm>
47 
48 #include "BESUtil.h"
49 #include "BESCatalogDirectory.h"
50 #include "BESCatalogUtils.h"
51 #include "BESCatalogEntry.h"
52 
53 #include "CatalogNode.h"
54 #include "CatalogItem.h"
55 
56 #include "BESInfo.h"
57 #include "BESContainerStorageList.h"
58 #include "BESFileContainerStorage.h"
59 #include "BESLog.h"
60 
61 #include "BESInternalError.h"
62 #include "BESForbiddenError.h"
63 #include "BESNotFoundError.h"
64 
65 #include "BESDebug.h"
66 
67 using namespace bes;
68 using namespace std;
69 
70 #define MODULE "bes"
71 #define PROLOG "BESCatalogDirectory::" << __func__ << "() - "
72 
86  BESCatalog(name)
87 {
88 #if 0
89  get_catalog_utils() = BESCatalogUtils::Utils(name);
90 #endif
91 }
92 
93 BESCatalogDirectory::~BESCatalogDirectory()
94 {
95 }
96 
109 {
110  string use_node = node;
111  // use_node should only end in '/' if that's the only character in which
112  // case there's no need to call find()
113  if (!node.empty() && node != "/") {
114  string::size_type pos = use_node.find_last_not_of("/");
115  use_node = use_node.substr(0, pos + 1);
116  }
117 
118  // This takes care of bizarre cases like "///" where use_node would be
119  // empty after the substring call.
120  if (use_node.empty()) use_node = "/";
121 
122  string rootdir = get_catalog_utils()->get_root_dir();
123  string fullnode = rootdir;
124  if (!use_node.empty()) {
125  // TODO It's hard to know just what this code is supposed to do, but I
126  // think the following can be an error. Above, if use_node is empty(), the use_node becomes
127  // "/" and then it's not empty() and fullnode becomes "<stuff>//" but we just
128  // jumped through all kinds of hoops to make sure there was either zero
129  // or one trailing slash. jhrg 2.26.18
130  fullnode = fullnode + "/" + use_node;
131  }
132 
133  string basename;
134  string::size_type slash = fullnode.rfind("/");
135  if (slash != string::npos) {
136  basename = fullnode.substr(slash + 1, fullnode.length() - slash);
137  }
138  else {
139  basename = fullnode;
140  }
141 
142  // fullnode is the full pathname of the node, including the 'root' pathanme
143  // basename is the last component of fullnode
144 
145  BESDEBUG(MODULE,
146  "BESCatalogDirectory::show_catalog: " << "use_node = " << use_node << endl << "rootdir = " << rootdir << endl << "fullnode = " << fullnode << endl << "basename = " << basename << endl);
147 
148  // This will throw the appropriate exception (Forbidden or Not Found).
149  // Checks to make sure the different elements of the path are not
150  // symbolic links if follow_sym_links is set to false, and checks to
151  // make sure have permission to access node and the node exists.
152  // TODO Move up; this can be done once use_node is set. jhrg 2.26.18
153  BESUtil::check_path(use_node, rootdir, get_catalog_utils()->follow_sym_links());
154 
155  // If null is passed in, then return the new entry, else add the new entry to the
156  // existing Entry object. jhrg 2.26.18
157  BESCatalogEntry *myentry = new BESCatalogEntry(use_node, get_catalog_name());
158  if (entry) {
159  // if an entry was passed, then add this one to it
160  entry->add_entry(myentry);
161  }
162  else {
163  // else we want to return the new entry created
164  entry = myentry;
165  }
166 
167  // Is this node a directory?
168  // TODO use stat() instead. jhrg 2.26.18
169  DIR *dip = opendir(fullnode.c_str());
170  if (dip != NULL) {
171  try {
172  // The node is a directory
173 
174  // if the directory requested is in the exclude list then we won't
175  // let the user see it.
176  if (get_catalog_utils()->exclude(basename)) {
177  string error = "You do not have permission to view the node " + use_node;
178  throw BESForbiddenError(error, __FILE__, __LINE__);
179  }
180 
181  // Now that we are ready to start building the response data we
182  // cancel any pending timeout alarm according to the configuration.
184 
185  bool dirs_only = false;
186  // TODO This is the only place in the code where get_entries() is called
187  // jhrg 2.26.18
188  get_catalog_utils()->get_entries(dip, fullnode, use_node, myentry, dirs_only);
189  }
190  catch (... /*BESError &e */) {
191  closedir(dip);
192  throw /* e */;
193  }
194  closedir(dip);
195 
196  // TODO This is the only place this method is called. replace the static method
197  // with an object call (i.e., get_catalog_utils())? jhrg 2.26.18
198  BESCatalogUtils::bes_add_stat_info(myentry, fullnode);
199  }
200  else {
201  // if the node is not in the include list then the requester does
202  // not have access to that node
203  if (get_catalog_utils()->include(basename)) {
204  struct stat buf;
205  int statret = 0;
206  if (get_catalog_utils()->follow_sym_links() == false) {
207  /*statret =*/(void) lstat(fullnode.c_str(), &buf);
208  if (S_ISLNK(buf.st_mode)) {
209  string error = "You do not have permission to access node " + use_node;
210  throw BESForbiddenError(error, __FILE__, __LINE__);
211  }
212  }
213  statret = stat(fullnode.c_str(), &buf);
214  if (statret == 0 && S_ISREG(buf.st_mode)) {
215  BESCatalogUtils::bes_add_stat_info(myentry, fullnode);
216 
217  list<string> services;
218  BESCatalogUtils::isData(node, get_catalog_name(), services);
219  myentry->set_service_list(services);
220  }
221  else if (statret == 0) {
222  string error = "You do not have permission to access " + use_node;
223  throw BESForbiddenError(error, __FILE__, __LINE__);
224  }
225  else {
226  // ENOENT means that the path or part of the path does not
227  // exist
228  if (errno == ENOENT) {
229  string error = "Node " + use_node + " does not exist";
230  char *s_err = strerror(errno);
231  if (s_err) {
232  error = s_err;
233  }
234  throw BESNotFoundError(error, __FILE__, __LINE__);
235  }
236  // any other error means that access is denied for some reason
237  else {
238  string error = "Access denied for node " + use_node;
239  char *s_err = strerror(errno);
240  if (s_err) {
241  error = error + s_err;
242  }
243  throw BESNotFoundError(error, __FILE__, __LINE__);
244  }
245  }
246  }
247  else {
248  string error = "You do not have permission to access " + use_node;
249  throw BESForbiddenError(error, __FILE__, __LINE__);
250  }
251  }
252 
253  return entry;
254 }
255 
261 string
263 {
264  return get_catalog_utils()->get_root_dir();
265 }
266 
275 static string get_time(time_t the_time, bool use_local_time = false)
276 {
277  char buf[sizeof "YYYY-MM-DDTHH:MM:SSzone"];
278  int status = 0;
279 
280  // From StackOverflow:
281  // This will work too, if your compiler doesn't support %F or %T:
282  // strftime(buf, sizeof buf, "%Y-%m-%dT%H:%M:%S%Z", gmtime(&now));
283  //
284  // Apologies for the twisted logic - UTC is the default. Override to
285  // local time using BES.LogTimeLocal=yes in bes.conf. jhrg 11/15/17
286  if (!use_local_time)
287  status = strftime(buf, sizeof buf, "%FT%T%Z", gmtime(&the_time));
288  else
289  status = strftime(buf, sizeof buf, "%FT%T%Z", localtime(&the_time));
290 
291  if (!status)
292  ERROR_LOG("Error getting last modified time time for a leaf item in BESCatalogDirectory.");
293 
294  return buf;
295 }
296 
300 CatalogItem *BESCatalogDirectory::make_item(string path_prefix, string item) const
301 {
302  if (item == "." || item == "..")
303  return 0;
304 
305  string item_path = BESUtil::assemblePath(path_prefix,item);
306  BESDEBUG(MODULE, PROLOG << "Processing POSIX entry: " << item_path << endl);
307 
308  bool include_item = get_catalog_utils()->include(item);
309  bool exclude_item = get_catalog_utils()->exclude(item);
310 
311  BESDEBUG(MODULE, PROLOG << "catalog: " << this->get_catalog_name() << endl);
312  BESDEBUG(MODULE, PROLOG << "include_item: " << (include_item?"true":"false") << endl);
313  BESDEBUG(MODULE, PROLOG << "exclude_item: " << (exclude_item?"true":"false") << endl);
314 
315  // TODO add a test in configure for the readdir macro(s) DT_REG, DT_LNK
316  // and DT_DIR and use those, if present, to dßetermine if the name is a
317  // link, directory or regular file. These are not present on all systems.
318  // Also, since we need mtime, these are not a huge time saver. But if we
319  // decide not to use the mtime, using these macros could save lots of system
320  // calls. jhrg 3/9/18
321 
322  // Skip this dir entry if it is a sym link and follow links is false
323  if (get_catalog_utils()->follow_sym_links() == false) {
324  struct stat lbuf;
325  (void) lstat(item_path.c_str(), &lbuf);
326  if (S_ISLNK(lbuf.st_mode))
327  return 0;
328  }
329  // Is this a directory or a file? Should it be excluded or included?
330  struct stat buf;
331  int statret = stat(item_path.c_str(), &buf);
332  if (statret == 0 && S_ISDIR(buf.st_mode) && !exclude_item) {
333  BESDEBUG(MODULE, PROLOG << item_path << " is NODE" << endl);
334  return new CatalogItem(item, 0, get_time(buf.st_mtime), CatalogItem::node);
335  }
336  else if (statret == 0 && S_ISREG(buf.st_mode) && include_item) {
337  BESDEBUG(MODULE, PROLOG << item_path << " is LEAF" << endl);
338  return new CatalogItem(item, buf.st_size, get_time(buf.st_mtime),
339  get_catalog_utils()->is_data(item), CatalogItem::leaf);
340  }
341 
342  // This is the error case; it only is run when the item_path is neither a
343  // directory nor a regular file.
344  stringstream msg;
345  if(exclude_item || !include_item){
346  msg << "Excluded the item '" << item_path << "' from the catalog '" <<
347  get_catalog_name() << "' node listing." << endl;
348  }
349  else {
350  msg << "Unable to create CatalogItem for '" << item_path << "' from the catalog '" <<
351  get_catalog_name() << ",' SKIPPING." << endl;
352  }
353  BESDEBUG(MODULE, PROLOG << msg.str());
354  VERBOSE(msg.str());
355 
356  return 0;
357 }
358 
359 // path must start with a '/'. By this class it will be interpreted as a
360 // starting at the CatalogDirectory instance's root directory. It may either
361 // end in a '/' or not.
362 //
363 // If it is not a directory - that is an error. (return null or throw?)
364 //
365 // Item names are relative
366 
389 CatalogNode *
390 BESCatalogDirectory::get_node(const string &path) const
391 {
392  if (path[0] != '/')
393  throw BESInternalError("The path sent to BESCatalogDirectory::get_node() must start with a slash (/)", __FILE__, __LINE__);
394 
395  string rootdir = get_catalog_utils()->get_root_dir();
396 
397  // This will throw the appropriate exception (Forbidden or Not Found).
398  // Checks to make sure the different elements of the path are not
399  // symbolic links if follow_sym_links is set to false, and checks to
400  // make sure have permission to access node and the node exists.
401  // TODO Make BESUtil::check_path() return the stat struct so we don't have to stat again here.
402  BESUtil::check_path(path, rootdir, get_catalog_utils()->follow_sym_links());
403  string fullpath = BESUtil::assemblePath(rootdir, path);
404  struct stat full_path_stat_buf;
405  int stat_result = stat(fullpath.c_str(), &full_path_stat_buf);
406  if(stat_result){
407  throw BESForbiddenError(
408  string("Unable to 'stat' the path '") + fullpath + "' errno says: " + std::strerror(errno),
409  __FILE__, __LINE__);
410  }
411 
412  CatalogNode *node = new CatalogNode(path);
413  if(S_ISREG(full_path_stat_buf.st_mode)){
414  BESDEBUG(MODULE, PROLOG << "The requested node '"+fullpath+"' is actually a leaf. Wut do?" << endl);
415 
416  CatalogItem *item = make_item(rootdir, path);
417  if(item){
418  node->set_leaf(item);
419  }
420  else {
421  string msg(__func__);
422  msg += "() - Failed to build CatalogItem for "+ path + " BESCatlogDirectory::make_item() returned NULL.",
423  throw BESInternalError(msg,__FILE__, __LINE__);
424  }
425 
426  BESDEBUG(MODULE, PROLOG << "Actually, I'm a LEAF (" << (void*)item << ")" << endl);
427  return node;
428  }
429  else if(S_ISDIR(full_path_stat_buf.st_mode)){
430  BESDEBUG(MODULE, PROLOG << "Processing directory node: "<< fullpath << endl);
431  DIR *dip = 0;
432  try {
433  // The node is a directory
434  // Based on other code (show_catalogs()), use BESCatalogUtils::exclude() on
435  // a directory, but BESCatalogUtils::include() on a file.
436  if (get_catalog_utils()->exclude(path))
437  throw BESForbiddenError(
438  string("The path '") + path + "' is not included in the catalog '" + get_catalog_name() + "'.",
439  __FILE__, __LINE__);
440 
441  node->set_catalog_name(get_catalog_name());
442  node->set_lmt(get_time(full_path_stat_buf.st_mtime));
443 
444  dip = opendir(fullpath.c_str());
445  if(dip == NULL){
446  // That went well...
447  // We need to return this "node", and at this point it is empty.
448  // Which is probably enough, so we do nothing more.
449  BESDEBUG(MODULE, PROLOG << "Unable to open '" << fullpath << "' SKIPPING (errno: " << std::strerror(errno) << ")"<< endl);
450  }
451  else {
452  // otherwise we grind through the node contents...
453  struct dirent *dit;
454  while ((dit = readdir(dip)) != NULL) {
455  CatalogItem * item = make_item(fullpath, dit->d_name);
456  if(item){
457  if(item->get_type() == CatalogItem::node){
458  node->add_node(item);
459  }
460  else {
461  node->add_leaf(item);
462  }
463  }
464  }
465  closedir(dip);
466  }
467 
469  sort(node->nodes_begin(), node->nodes_end(), ordering);
470  sort(node->leaves_begin(), node->leaves_end(), ordering);
471 
472  return node;
473  }
474  catch (...) {
475  closedir(dip);
476  throw;
477  }
478  }
479  throw BESInternalError(
480  "A BESCatalogDirectory can only return nodes for directories and regular files. The path '" + path
481  + "' is not a directory or a regular file for BESCatalog '" + get_catalog_name() + "'.", __FILE__, __LINE__);
482 }
483 
484 #if 0
485 // path must start with a '/'. By this class it will be interpreted as a
486 // starting at the CatalogDirectory instance's root directory. It may either
487 // end in a '/' or not.
488 //
489 // If it is not a directory - that is an error. (return null or throw?)
490 //
491 // Item names are relative
492 
508 CatalogNode *
509 BESCatalogDirectory::get_node(const string &path) const
510 {
511  if (path[0] != '/') throw BESInternalError("The path sent to BESCatalogDirectory::get_node() must start with a slash (/)", __FILE__, __LINE__);
512 
513  string rootdir = get_catalog_utils()->get_root_dir();
514 
515  // This will throw the appropriate exception (Forbidden or Not Found).
516  // Checks to make sure the different elements of the path are not
517  // symbolic links if follow_sym_links is set to false, and checks to
518  // make sure have permission to access node and the node exists.
519  BESUtil::check_path(path, rootdir, get_catalog_utils()->follow_sym_links());
520 
521  string fullpath = rootdir + path;
522 
523  DIR *dip = opendir(fullpath.c_str());
524  if (!dip)
525  throw BESInternalError(
526  "A BESCatalogDirectory can only return nodes for directory. The path '" + path
527  + "' is not a directory for BESCatalog '" + get_catalog_name() + "'.", __FILE__, __LINE__);
528 
529  try {
530  // The node is a directory
531 
532  // Based on other code (show_catalogs()), use BESCatalogUtils::exclude() on
533  // a directory, but BESCatalogUtils::include() on a file.
534  if (get_catalog_utils()->exclude(path))
535  throw BESForbiddenError(
536  string("The path '") + path + "' is not included in the catalog '" + get_catalog_name() + "'.",
537  __FILE__, __LINE__);
538 
539  CatalogNode *node = new CatalogNode(path);
540 
541  node->set_catalog_name(get_catalog_name());
542  struct stat buf;
543  int statret = stat(fullpath.c_str(), &buf);
544  if (statret == 0 /* && S_ISDIR(buf.st_mode) */)
545  node->set_lmt(get_time(buf.st_mtime));
546 
547  struct dirent *dit;
548  while ((dit = readdir(dip)) != NULL) {
549  string item = dit->d_name;
550  if (item == "." || item == "..") continue;
551 
552  string item_path = fullpath + "/" + item;
553 
554  // TODO add a test in configure for the readdir macro(s) DT_REG, DT_LNK
555  // and DT_DIR and use those, if present, to determine if the name is a
556  // link, directory or regular file. These are not present on all systems.
557  // Also, since we need mtime, these are not a huge time saver. But if we
558  // decide not to use the mtime, using these macros could save lots of system
559  // calls. jhrg 3/9/18
560 
561  // Skip this dir entry if it is a sym link and follow links is false
562  if (get_catalog_utils()->follow_sym_links() == false) {
563  struct stat lbuf;
564  (void) lstat(item_path.c_str(), &lbuf);
565  if (S_ISLNK(lbuf.st_mode)) continue;
566  }
567 
568  // Is this a directory or a file? Should it be excluded or included?
569  statret = stat(item_path.c_str(), &buf);
570  if (statret == 0 && S_ISDIR(buf.st_mode) && !get_catalog_utils()->exclude(item)) {
571 #if 0
572  // Add a new node; set the size to zero.
573  node->add_item(new CatalogItem(item, 0, get_time(buf.st_mtime), CatalogItem::node));
574 #endif
575  node->add_node(new CatalogItem(item, 0, get_time(buf.st_mtime), CatalogItem::node));
576  }
577  else if (statret == 0 && S_ISREG(buf.st_mode) && get_catalog_utils()->include(item)) {
578 #if 0
579  // Add a new leaf.
580  node->add_item(new CatalogItem(item, buf.st_size, get_time(buf.st_mtime),
581  get_catalog_utils()->is_data(item), CatalogItem::leaf));
582 #endif
583  node->add_leaf(new CatalogItem(item, buf.st_size, get_time(buf.st_mtime),
584  get_catalog_utils()->is_data(item), CatalogItem::leaf));
585  }
586  else {
587  VERBOSE("Excluded the item '" << item_path << "' from the catalog '" << get_catalog_name() << "' node listing.");
588  }
589  } // end of the while loop
590 
591  closedir(dip);
592 
594 
595  sort(node->nodes_begin(), node->nodes_end(), ordering);
596  sort(node->leaves_begin(), node->leaves_end(), ordering);
597 
598  return node;
599  }
600  catch (...) {
601  closedir(dip);
602  throw;
603  }
604 }
605 #endif
606 
626 void BESCatalogDirectory::get_site_map(const string &prefix, const string &node_suffix, const string &leaf_suffix,
627  ostream &out, const string &path) const
628 {
629  auto_ptr<CatalogNode> node(get_node(path));
630 
631 #if ITEMS
632  for (CatalogNode::item_citer i = node->items_begin(), e = node->items_end(); i != e; ++i) {
633  if ((*i)->get_type() == CatalogItem::leaf && (*i)->is_data()) {
634  out << prefix << path << (*i)->get_name() << leaf_suffix << endl;
635  }
636  else if ((*i)->get_type() == CatalogItem::node) {
637  get_site_map(prefix, leaf_suffix, out, path + (*i)->get_name() + "/");
638  }
639  }
640 #endif
641 
642  if (!node_suffix.empty())
643  out << prefix << path << node_suffix << endl;
644 
645  // Depth-first node traversal. Assume the nodes and leaves are sorted
646  for (CatalogNode::item_citer i = node->nodes_begin(), e = node->nodes_end(); i != e; ++i) {
647  assert((*i)->get_type() == CatalogItem::node);
648  get_site_map(prefix, node_suffix, leaf_suffix, out, path + (*i)->get_name() + "/");
649  }
650 
651  // For leaves, only write the data items
652  for (CatalogNode::item_citer i = node->leaves_begin(), e = node->leaves_end(); i != e; ++i) {
653  assert((*i)->get_type() == CatalogItem::leaf);
654  if ((*i)->is_data() && !leaf_suffix.empty())
655  out << prefix << path << (*i)->get_name() << leaf_suffix << endl;
656  }
657 }
658 
666 void BESCatalogDirectory::dump(ostream &strm) const
667 {
668  strm << BESIndent::LMarg << "BESCatalogDirectory::dump - (" << (void *) this << ")" << endl;
669  BESIndent::Indent();
670 
671  strm << BESIndent::LMarg << "catalog utilities: " << endl;
672  BESIndent::Indent();
673  get_catalog_utils()->dump(strm);
674  BESIndent::UnIndent();
675  BESIndent::UnIndent();
676 }
677 
virtual bes::CatalogNode * get_node(const std::string &path) const
Get a CatalogNode for the given path in the current catalog.
virtual std::string get_root() const
Get the root directory for the catalog.
BESCatalogDirectory(const std::string &name)
A catalog for POSIX file systems.
virtual BESCatalogEntry * show_catalog(const std::string &container, BESCatalogEntry *entry)
Get the CatalogEntry for the given node.
virtual void get_site_map(const std::string &prefix, const std::string &node_suffix, const std::string &leaf_suffix, std::ostream &out, const std::string &path="/") const
Write the site map for this catalog to the stream.
virtual void dump(std::ostream &strm) const
dumps information about this object
virtual bool exclude(const std::string &inQuestion) const
Should this file/directory be excluded in the catalog?
virtual unsigned int get_entries(DIR *dip, const std::string &fullnode, const std::string &use_node, BESCatalogEntry *entry, bool dirs_only)
virtual bool include(const std::string &inQuestion) const
Should this file/directory be included in the catalog?
const std::string & get_root_dir() const
Get the root directory of the catalog.
virtual void dump(std::ostream &strm) const
dump the contents of this object to the specified ostream
Catalogs provide a hierarchical organization for data.
Definition: BESCatalog.h:51
virtual std::string get_catalog_name() const
Get the name for this catalog.
Definition: BESCatalog.h:103
virtual BESCatalogUtils * get_catalog_utils() const
Get a pointer to the utilities, customized for this catalog.
Definition: BESCatalog.h:113
error thrown if the BES is not allowed to access the resource requested
exception thrown if internal error encountered
error thrown if the resource requested cannot be found
static void conditional_timeout_cancel()
Definition: BESUtil.cc:967
static void check_path(const std::string &path, const std::string &root, bool follow_sym_links)
Check if the specified path is valid.
Definition: BESUtil.cc:254
static std::string assemblePath(const std::string &firstPart, const std::string &secondPart, bool leadingSlash=false, bool trailingSlash=false)
Assemble path fragments making sure that they are separated by a single '/' character.
Definition: BESUtil.cc:821
item_type get_type() const
Get the type of this item (unknown, node or leaf)
Definition: CatalogItem.h:153