bes  Updated for version 3.20.6
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  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  string msg;
345  if(exclude_item || !include_item){
346  msg = string("Excluded the item '").append(item_path).append("' from the catalog '").append(get_catalog_name()).append("' node listing.");
347  }
348  else {
349  string msg = string("Unable to create CatalogItem for '").append("' from the catalog '").append(get_catalog_name()).append(",' SKIPPING.");
350  }
351  BESDEBUG(MODULE, PROLOG << msg << endl);
352  VERBOSE(msg);
353 
354  return 0;
355 }
356 
357 // path must start with a '/'. By this class it will be interpreted as a
358 // starting at the CatalogDirectory instance's root directory. It may either
359 // end in a '/' or not.
360 //
361 // If it is not a directory - that is an error. (return null or throw?)
362 //
363 // Item names are relative
364 
387 CatalogNode *
388 BESCatalogDirectory::get_node(const string &path) const
389 {
390  if (path[0] != '/')
391  throw BESInternalError("The path sent to BESCatalogDirectory::get_node() must start with a slash (/)", __FILE__, __LINE__);
392 
393  string rootdir = get_catalog_utils()->get_root_dir();
394 
395  // This will throw the appropriate exception (Forbidden or Not Found).
396  // Checks to make sure the different elements of the path are not
397  // symbolic links if follow_sym_links is set to false, and checks to
398  // make sure have permission to access node and the node exists.
399  // TODO Make BESUtil::check_path() return the stat struct so we don't have to stat again here.
400  BESUtil::check_path(path, rootdir, get_catalog_utils()->follow_sym_links());
401  string fullpath = BESUtil::assemblePath(rootdir, path);
402  struct stat full_path_stat_buf;
403  int stat_result = stat(fullpath.c_str(), &full_path_stat_buf);
404  if(stat_result){
405  throw BESForbiddenError(
406  string("Unable to 'stat' the path '") + fullpath + "' errno says: " + std::strerror(errno),
407  __FILE__, __LINE__);
408  }
409 
410  CatalogNode *node = new CatalogNode(path);
411  if(S_ISREG(full_path_stat_buf.st_mode)){
412  BESDEBUG(MODULE, PROLOG << "The requested node '"+fullpath+"' is actually a leaf. Wut do?" << endl);
413 
414  CatalogItem *item = make_item(rootdir, path);
415  if(item){
416  node->set_leaf(item);
417  }
418  else {
419  string msg(__func__);
420  msg += "() - Failed to build CatalogItem for "+ path + " BESCatlogDirectory::make_item() returned NULL.",
421  throw BESInternalError(msg,__FILE__, __LINE__);
422  }
423 
424  BESDEBUG(MODULE, PROLOG << "Actually, I'm a LEAF (" << (void*)item << ")" << endl);
425  return node;
426  }
427  else if(S_ISDIR(full_path_stat_buf.st_mode)){
428  BESDEBUG(MODULE, PROLOG << "Processing directory node: "<< fullpath << endl);
429  DIR *dip = 0;
430  try {
431  // The node is a directory
432  // Based on other code (show_catalogs()), use BESCatalogUtils::exclude() on
433  // a directory, but BESCatalogUtils::include() on a file.
434  if (get_catalog_utils()->exclude(path))
435  throw BESForbiddenError(
436  string("The path '") + path + "' is not included in the catalog '" + get_catalog_name() + "'.",
437  __FILE__, __LINE__);
438 
439  node->set_catalog_name(get_catalog_name());
440  node->set_lmt(get_time(full_path_stat_buf.st_mtime));
441 
442  dip = opendir(fullpath.c_str());
443  if(dip == NULL){
444  // That went well...
445  // We need to return this "node", and at this point it is empty.
446  // Which is probably enough, so we do nothing more.
447  BESDEBUG(MODULE, PROLOG << "Unable to open '" << fullpath << "' SKIPPING (errno: " << std::strerror(errno) << ")"<< endl);
448  }
449  else {
450  // otherwise we grind through the node contents...
451  struct dirent *dit;
452  while ((dit = readdir(dip)) != NULL) {
453  CatalogItem * item = make_item(fullpath, dit->d_name);
454  if(item){
455  if(item->get_type() == CatalogItem::node){
456  node->add_node(item);
457  }
458  else {
459  node->add_leaf(item);
460  }
461  }
462  }
463  closedir(dip);
464  }
465 
467  sort(node->nodes_begin(), node->nodes_end(), ordering);
468  sort(node->leaves_begin(), node->leaves_end(), ordering);
469 
470  return node;
471  }
472  catch (...) {
473  closedir(dip);
474  throw;
475  }
476  }
477  throw BESInternalError(
478  "A BESCatalogDirectory can only return nodes for directories and regular files. The path '" + path
479  + "' is not a directory or a regular file for BESCatalog '" + get_catalog_name() + "'.", __FILE__, __LINE__);
480 }
481 
482 #if 0
483 // path must start with a '/'. By this class it will be interpreted as a
484 // starting at the CatalogDirectory instance's root directory. It may either
485 // end in a '/' or not.
486 //
487 // If it is not a directory - that is an error. (return null or throw?)
488 //
489 // Item names are relative
490 
506 CatalogNode *
507 BESCatalogDirectory::get_node(const string &path) const
508 {
509  if (path[0] != '/') throw BESInternalError("The path sent to BESCatalogDirectory::get_node() must start with a slash (/)", __FILE__, __LINE__);
510 
511  string rootdir = get_catalog_utils()->get_root_dir();
512 
513  // This will throw the appropriate exception (Forbidden or Not Found).
514  // Checks to make sure the different elements of the path are not
515  // symbolic links if follow_sym_links is set to false, and checks to
516  // make sure have permission to access node and the node exists.
517  BESUtil::check_path(path, rootdir, get_catalog_utils()->follow_sym_links());
518 
519  string fullpath = rootdir + path;
520 
521  DIR *dip = opendir(fullpath.c_str());
522  if (!dip)
523  throw BESInternalError(
524  "A BESCatalogDirectory can only return nodes for directory. The path '" + path
525  + "' is not a directory for BESCatalog '" + get_catalog_name() + "'.", __FILE__, __LINE__);
526 
527  try {
528  // The node is a directory
529 
530  // Based on other code (show_catalogs()), use BESCatalogUtils::exclude() on
531  // a directory, but BESCatalogUtils::include() on a file.
532  if (get_catalog_utils()->exclude(path))
533  throw BESForbiddenError(
534  string("The path '") + path + "' is not included in the catalog '" + get_catalog_name() + "'.",
535  __FILE__, __LINE__);
536 
537  CatalogNode *node = new CatalogNode(path);
538 
539  node->set_catalog_name(get_catalog_name());
540  struct stat buf;
541  int statret = stat(fullpath.c_str(), &buf);
542  if (statret == 0 /* && S_ISDIR(buf.st_mode) */)
543  node->set_lmt(get_time(buf.st_mtime));
544 
545  struct dirent *dit;
546  while ((dit = readdir(dip)) != NULL) {
547  string item = dit->d_name;
548  if (item == "." || item == "..") continue;
549 
550  string item_path = fullpath + "/" + item;
551 
552  // TODO add a test in configure for the readdir macro(s) DT_REG, DT_LNK
553  // and DT_DIR and use those, if present, to determine if the name is a
554  // link, directory or regular file. These are not present on all systems.
555  // Also, since we need mtime, these are not a huge time saver. But if we
556  // decide not to use the mtime, using these macros could save lots of system
557  // calls. jhrg 3/9/18
558 
559  // Skip this dir entry if it is a sym link and follow links is false
560  if (get_catalog_utils()->follow_sym_links() == false) {
561  struct stat lbuf;
562  (void) lstat(item_path.c_str(), &lbuf);
563  if (S_ISLNK(lbuf.st_mode)) continue;
564  }
565 
566  // Is this a directory or a file? Should it be excluded or included?
567  statret = stat(item_path.c_str(), &buf);
568  if (statret == 0 && S_ISDIR(buf.st_mode) && !get_catalog_utils()->exclude(item)) {
569 #if 0
570  // Add a new node; set the size to zero.
571  node->add_item(new CatalogItem(item, 0, get_time(buf.st_mtime), CatalogItem::node));
572 #endif
573  node->add_node(new CatalogItem(item, 0, get_time(buf.st_mtime), CatalogItem::node));
574  }
575  else if (statret == 0 && S_ISREG(buf.st_mode) && get_catalog_utils()->include(item)) {
576 #if 0
577  // Add a new leaf.
578  node->add_item(new CatalogItem(item, buf.st_size, get_time(buf.st_mtime),
579  get_catalog_utils()->is_data(item), CatalogItem::leaf));
580 #endif
581  node->add_leaf(new CatalogItem(item, buf.st_size, get_time(buf.st_mtime),
582  get_catalog_utils()->is_data(item), CatalogItem::leaf));
583  }
584  else {
585  VERBOSE("Excluded the item '" << item_path << "' from the catalog '" << get_catalog_name() << "' node listing.");
586  }
587  } // end of the while loop
588 
589  closedir(dip);
590 
592 
593  sort(node->nodes_begin(), node->nodes_end(), ordering);
594  sort(node->leaves_begin(), node->leaves_end(), ordering);
595 
596  return node;
597  }
598  catch (...) {
599  closedir(dip);
600  throw;
601  }
602 }
603 #endif
604 
624 void BESCatalogDirectory::get_site_map(const string &prefix, const string &node_suffix, const string &leaf_suffix,
625  ostream &out, const string &path) const
626 {
627  auto_ptr<CatalogNode> node(get_node(path));
628 
629 #if ITEMS
630  for (CatalogNode::item_citer i = node->items_begin(), e = node->items_end(); i != e; ++i) {
631  if ((*i)->get_type() == CatalogItem::leaf && (*i)->is_data()) {
632  out << prefix << path << (*i)->get_name() << leaf_suffix << endl;
633  }
634  else if ((*i)->get_type() == CatalogItem::node) {
635  get_site_map(prefix, leaf_suffix, out, path + (*i)->get_name() + "/");
636  }
637  }
638 #endif
639 
640  if (!node_suffix.empty())
641  out << prefix << path << node_suffix << endl;
642 
643  // Depth-first node traversal. Assume the nodes and leaves are sorted
644  for (CatalogNode::item_citer i = node->nodes_begin(), e = node->nodes_end(); i != e; ++i) {
645  assert((*i)->get_type() == CatalogItem::node);
646  get_site_map(prefix, node_suffix, leaf_suffix, out, path + (*i)->get_name() + "/");
647  }
648 
649  // For leaves, only write the data items
650  for (CatalogNode::item_citer i = node->leaves_begin(), e = node->leaves_end(); i != e; ++i) {
651  assert((*i)->get_type() == CatalogItem::leaf);
652  if ((*i)->is_data() && !leaf_suffix.empty())
653  out << prefix << path << (*i)->get_name() << leaf_suffix << endl;
654  }
655 }
656 
664 void BESCatalogDirectory::dump(ostream &strm) const
665 {
666  strm << BESIndent::LMarg << "BESCatalogDirectory::dump - (" << (void *) this << ")" << endl;
667  BESIndent::Indent();
668 
669  strm << BESIndent::LMarg << "catalog utilities: " << endl;
670  BESIndent::Indent();
671  get_catalog_utils()->dump(strm);
672  BESIndent::UnIndent();
673  BESIndent::UnIndent();
674 }
675 
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