GCC Code Coverage Report
Directory: cvmfs/ Exec Total Coverage
File: cvmfs/fs_traversal.h Lines: 69 77 89.6 %
Date: 2019-02-03 02:48:13 Branches: 259 571 45.4 %

Line Branch Exec Source
1
/**
2
 * This file is part of the CernVM File System.
3
 *
4
 * It provides a file system traversal framework to abstract the traversal
5
 * of directories.
6
 */
7
8
#ifndef CVMFS_FS_TRAVERSAL_H_
9
#define CVMFS_FS_TRAVERSAL_H_
10
11
#include <errno.h>
12
13
#include <cassert>
14
#include <cstdlib>
15
16
#include <set>
17
#include <string>
18
19
#include "logging.h"
20
#include "platform.h"
21
#include "util/async.h"
22
23
#ifdef CVMFS_NAMESPACE_GUARD
24
namespace CVMFS_NAMESPACE_GUARD {
25
#endif
26
27
/**
28
 * @brief A simple recursion engine to abstract the recursion of directories.
29
 * It provides several callback hooks to instrument and control the recursion.
30
 * Hooks will be called on the provided delegate object of type T
31
 *
32
 * Callbacks are called for every directory entry found by the recursion engine.
33
 * The recursion can be influenced by return values of these callbacks.
34
 */
35
template <class T>
36
918
class FileSystemTraversal {
37
 public:
38
  typedef void (T::*VoidCallback)(const std::string &relative_path,
39
                                  const std::string &dir_name);
40
  typedef bool (T::*BoolCallback)(const std::string &relative_path,
41
                                  const std::string &dir_name);
42
43
44
  VoidCallback fn_enter_dir;
45
  VoidCallback fn_leave_dir;
46
  VoidCallback fn_new_file;
47
  VoidCallback fn_new_symlink;
48
  VoidCallback fn_new_socket;
49
  VoidCallback fn_new_block_dev;
50
  VoidCallback fn_new_character_dev;
51
  VoidCallback fn_new_fifo;
52
53
  /**
54
   * Optional callback for all files during recursion to decide
55
   * whether to completely ignore the file.  If this callback returns
56
   * true then the file will not be processed (this is a replacement
57
   * for the ignored_files set, and it allows to ignore based on names
58
   * or something else). If the function is not specified, no files
59
   * will be ignored (except for "." and "..").
60
   */
61
  BoolCallback fn_ignore_file;
62
63
  /**
64
   * Callback if a directory was found.  Depending on the response of
65
   * the callback, the recursion will continue in the found directory/
66
   * If this callback is not specified, it will recurse by default.
67
   */
68
  BoolCallback fn_new_dir_prefix;
69
70
  /**
71
   * Callback for a found directory after it was already recursed
72
   * e.g. for deletion of directories: first delete content,
73
   * then the directory itself
74
   */
75
  VoidCallback fn_new_dir_postfix;
76
77
78
  /**
79
   * Create a new recursion engine
80
   * @param delegate The object that will receive the callbacks
81
   * @param relative_to_directory The DirEntries will be created relative
82
   *        to this directory
83
   * @param recurse Should the traversal engine recurse? (if not,
84
   *        it just traverses the given directory)
85
   */
86
918
  FileSystemTraversal(T *delegate,
87
                      const std::string &relative_to_directory,
88
                      const bool recurse) :
89
    fn_enter_dir(NULL),
90
    fn_leave_dir(NULL),
91
    fn_new_file(NULL),
92
    fn_new_symlink(NULL),
93
    fn_new_socket(NULL),
94
    fn_new_block_dev(NULL),
95
    fn_new_character_dev(NULL),
96
    fn_new_fifo(NULL),
97
    fn_ignore_file(NULL),
98
    fn_new_dir_prefix(NULL),
99
    fn_new_dir_postfix(NULL),
100
    delegate_(delegate),
101
    relative_to_directory_(relative_to_directory),
102
918
    recurse_(recurse)
103
  {
104
918
    Init();
105
918
  }
106
107
  /**
108
   * Start the recursion.
109
   * @param dir_path The directory to start the recursion at
110
   */
111
918
  void Recurse(const std::string &dir_path) const {
112






















918
    assert(fn_enter_dir != NULL ||
113
           fn_leave_dir != NULL ||
114
           fn_new_file != NULL ||
115
           fn_new_symlink != NULL ||
116
           fn_new_dir_prefix != NULL ||
117
           fn_new_block_dev != NULL ||
118
           fn_new_character_dev != NULL ||
119
           fn_new_fifo != NULL ||
120
           fn_new_socket != NULL);
121
122










918
    assert(relative_to_directory_.length() == 0 ||
123
           dir_path.substr(0, relative_to_directory_.length()) ==
124
             relative_to_directory_);
125
126
918
    DoRecursion(dir_path, "");
127
918
  }
128
129
 private:
130
  // The delegate all hooks are called on
131
  T *delegate_;
132
133
  /** dir_path in callbacks will be relative to this directory */
134
  std::string relative_to_directory_;
135
  bool recurse_;
136
137
138
918
  void Init() {
139
918
  }
140
141
3172
  void DoRecursion(const std::string &parent_path, const std::string &dir_name)
142
    const
143
  {
144
    DIR *dip;
145
    platform_dirent64 *dit;
146
    const std::string path = parent_path + ((!dir_name.empty()) ?
147








3172
                                           ("/" + dir_name) : "");
148
149
    // Change into directory and notify the user
150
3172
    LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "entering %s (%s -- %s)",
151
             path.c_str(), parent_path.c_str(), dir_name.c_str());
152
3172
    dip = opendir(path.c_str());
153


3172
    if (!dip) {
154
      LogCvmfs(kLogFsTraversal, kLogStderr, "Failed to open %s (%d).\n"
155
               "Please check directory permissions.",
156
               path.c_str(), errno);
157
      abort();
158
    }
159
3172
    Notify(fn_enter_dir, parent_path, dir_name);
160
161
    // Walk through the open directory notifying the user about contents
162


114623
    while ((dit = platform_readdir(dip)) != NULL) {
163
      // Check if file should be ignored
164




























108279
      if (std::string(dit->d_name) == "." || std::string(dit->d_name) == "..") {
165
6344
        continue;
166


101935
      } else if (fn_ignore_file != NULL) {
167



461
        if (Notify(fn_ignore_file, path, dit->d_name)) {
168
9
          LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "ignoring %s/%s",
169
                   path.c_str(), dit->d_name);
170
9
          continue;
171
        }
172
      } else {
173
101474
        LogCvmfs(kLogFsTraversal, kLogVerboseMsg,
174
                 "not ignoring %s/%s (fn_ignore_file not set)",
175
                 path.c_str(), dit->d_name);
176
      }
177
178
      // Notify user about found directory entry
179
      platform_stat64 info;
180
101926
      int retval = platform_lstat((path + "/" + dit->d_name).c_str(), &info);
181


101926
      if (retval != 0) {
182
        LogCvmfs(kLogFsTraversal, kLogStderr, "failed to lstat '%s' errno: %d",
183
                 (path + "/" + dit->d_name).c_str(), errno);
184
        abort();
185
      }
186


101926
      if (S_ISDIR(info.st_mode)) {
187
95915
        LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "passing directory %s/%s",
188
                 path.c_str(), dit->d_name);
189


















95915
        if (Notify(fn_new_dir_prefix, path, dit->d_name) && recurse_) {
190
2254
          DoRecursion(path, dit->d_name);
191
        }
192
95915
        Notify(fn_new_dir_postfix, path, dit->d_name);
193


6011
      } else if (S_ISREG(info.st_mode)) {
194
5586
        LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "passing regular file %s/%s",
195
                 path.c_str(), dit->d_name);
196
5586
        Notify(fn_new_file, path, dit->d_name);
197


425
      } else if (S_ISLNK(info.st_mode)) {
198
24
        LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "passing symlink %s/%s",
199
                 path.c_str(), dit->d_name);
200
24
        Notify(fn_new_symlink, path, dit->d_name);
201


401
      } else if (S_ISSOCK(info.st_mode)) {
202
24
        LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "passing socket %s/%s",
203
                 path.c_str(), dit->d_name);
204
24
        Notify(fn_new_socket, path, dit->d_name);
205


377
      } else if (S_ISBLK(info.st_mode)) {
206
54
        LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "passing block-device %s/%s",
207
                 path.c_str(), dit->d_name);
208
54
        Notify(fn_new_block_dev, path, dit->d_name);
209


323
      } else if (S_ISCHR(info.st_mode)) {
210
300
        LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "passing character-device "
211
                                                  "%s/%s",
212
                 path.c_str(), dit->d_name);
213
300
        Notify(fn_new_character_dev, path, dit->d_name);
214


23
      } else if (S_ISFIFO(info.st_mode)) {
215
23
        LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "passing FIFO %s/%s",
216
                 path.c_str(), dit->d_name);
217
23
        Notify(fn_new_fifo, path, dit->d_name);
218
      } else {
219
        LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "unknown file type %s/%s",
220
                 path.c_str(), dit->d_name);
221
      }
222
    }
223
224
    // Close directory and notify user
225
3172
    closedir(dip);
226
3172
    LogCvmfs(kLogFsTraversal, kLogVerboseMsg, "leaving %s", path.c_str());
227
3172
    Notify(fn_leave_dir, parent_path, dir_name);
228
3172
  }
229
230
96376
  inline bool Notify(const BoolCallback callback,
231
                     const std::string &parent_path,
232
                     const std::string &entry_name) const
233
  {
234
    return (callback == NULL) ? true :
235
      (delegate_->*callback)(GetRelativePath(parent_path),
236










96376
                             entry_name);
237
  }
238
239
108270
  inline void Notify(const VoidCallback callback,
240
                     const std::string &parent_path,
241
                     const std::string &entry_name) const
242
  {
243


108270
    if (callback != NULL) {
244


9140
      (delegate_->*callback)(GetRelativePath(parent_path),
245
                             entry_name);
246
    }
247
108270
  }
248
249
100051
  std::string GetRelativePath(const std::string &absolute_path) const {
250
100051
    const unsigned int rel_dir_len = relative_to_directory_.length();
251


100051
    if (rel_dir_len >= absolute_path.length()) {
252
620
      return "";
253


99431
    } else if (rel_dir_len > 1) {
254
542
      return absolute_path.substr(rel_dir_len + 1);
255


98889
    } else if (rel_dir_len == 0) {
256
98889
      return absolute_path;
257
    } else if (relative_to_directory_ == "/") {
258
      return absolute_path.substr(1);
259
    } else {
260
      return "";
261
    }
262
  }
263
};  // FileSystemTraversal
264
265
#ifdef CVMFS_NAMESPACE_GUARD
266
}  // namespace CVMFS_NAMESPACE_GUARD
267
#endif
268
269
#endif  // CVMFS_FS_TRAVERSAL_H_