blob: af5f20930ab37beb5853f02103d0e50a325c8b60 [file] [log] [blame]
adlr@google.com3defe6a2009-12-04 20:57:17 +00001// Copyright (c) 2009 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "update_engine/filesystem_iterator.h"
6#include <sys/types.h>
7#include <dirent.h>
8#include <errno.h>
9#include <set>
10#include <string>
11#include <vector>
Chris Masone790e62e2010-08-12 10:41:18 -070012#include "base/logging.h"
adlr@google.com3defe6a2009-12-04 20:57:17 +000013#include "update_engine/utils.h"
14
15using std::set;
16using std::string;
17using std::vector;
18
19namespace chromeos_update_engine {
20
21// We use a macro here for two reasons:
22// 1. We want to be able to return from the caller's function.
23// 2. We can use the #macro_arg ism to get a string of the calling statement,
24// which we can log.
25
26#define RETURN_ERROR_IF_FALSE(_statement) \
27 do { \
28 bool _result = (_statement); \
29 if (!_result) { \
30 string _message = utils::ErrnoNumberAsString(errno); \
31 LOG(INFO) << #_statement << " failed: " << _message << ". Aborting"; \
32 is_end_ = true; \
33 is_err_ = true; \
34 return; \
35 } \
36 } while (0)
37
38FilesystemIterator::FilesystemIterator(
39 const std::string& path,
40 const std::set<std::string>& excl_prefixes)
41 : excl_prefixes_(excl_prefixes),
42 is_end_(false),
43 is_err_(false) {
44 root_path_ = utils::NormalizePath(path, true);
45 RETURN_ERROR_IF_FALSE(lstat(root_path_.c_str(), &stbuf_) == 0);
46 root_dev_ = stbuf_.st_dev;
47}
48
49FilesystemIterator::~FilesystemIterator() {
50 for (vector<DIR*>::iterator it = dirs_.begin(); it != dirs_.end(); ++it) {
51 LOG_IF(ERROR, closedir(*it) != 0) << "closedir failed";
52 }
53}
54
55// Returns full path for current file
56std::string FilesystemIterator::GetFullPath() const {
57 return root_path_ + GetPartialPath();
58}
59
60std::string FilesystemIterator::GetPartialPath() const {
61 std::string ret;
62 for (vector<string>::const_iterator it = names_.begin();
63 it != names_.end(); ++it) {
64 ret += "/";
65 ret += *it;
66 }
67 return ret;
68}
69
70// Increments to the next file
71void FilesystemIterator::Increment() {
72 // If we're currently on a dir, descend into children, but only if
73 // we're on the same device as the root device
74
75 bool entering_dir = false; // true if we're entering into a new dir
76 if (S_ISDIR(stbuf_.st_mode) && (stbuf_.st_dev == root_dev_)) {
77 DIR* dir = opendir(GetFullPath().c_str());
78 if ((!dir) && ((errno == ENOTDIR) || (errno == ENOENT))) {
79 // opendir failed b/c either it's not a dir or it doesn't exist.
80 // that's fine. let's just skip over this.
81 LOG(ERROR) << "Can't descend into " << GetFullPath();
82 } else {
83 RETURN_ERROR_IF_FALSE(dir);
84 entering_dir = true;
85 dirs_.push_back(dir);
86 }
87 }
88
89 if (!entering_dir && names_.empty()) {
90 // root disappeared while we tried to descend into it
91 is_end_ = true;
92 return;
93 }
94
95 if (!entering_dir)
96 names_.pop_back();
97
98 IncrementInternal();
99 for (set<string>::const_iterator it = excl_prefixes_.begin();
100 it != excl_prefixes_.end(); ++it) {
101 if (utils::StringHasPrefix(GetPartialPath(), *it)) {
102 Increment();
103 break;
104 }
105 }
106 return;
107}
108
109// Assumes that we need to find the next child of dirs_.back(), or if
110// there are none more, go up the chain
111void FilesystemIterator::IncrementInternal() {
112 CHECK_EQ(dirs_.size(), names_.size() + 1);
113 for (;;) {
114 struct dirent dir_entry;
115 struct dirent* dir_entry_pointer;
116 int r;
117 RETURN_ERROR_IF_FALSE(
118 (r = readdir_r(dirs_.back(), &dir_entry, &dir_entry_pointer)) == 0);
119 if (dir_entry_pointer) {
120 // Found an entry
121 names_.push_back(dir_entry_pointer->d_name);
122 // Validate
123 RETURN_ERROR_IF_FALSE(lstat(GetFullPath().c_str(), &stbuf_) == 0);
124 if (strcmp(dir_entry_pointer->d_name, ".") &&
125 strcmp(dir_entry_pointer->d_name, "..")) {
126 // Done
127 return;
128 }
129 // Child didn't work out. Try again
130 names_.pop_back();
131 } else {
132 // No more children in this dir. Pop it and try again
133 RETURN_ERROR_IF_FALSE(closedir(dirs_.back()) == 0);
134 dirs_.pop_back();
135 if (dirs_.empty()) {
136 CHECK(names_.empty());
137 // Done with the entire iteration
138 is_end_ = true;
139 return;
140 }
141 CHECK(!names_.empty());
142 names_.pop_back();
143 }
144 }
145}
146
147} // namespace chromeos_update_engine