1// Copyright 2018 The Fuchsia 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 <dirent.h>
6#include <errno.h>
7#include <limits.h>
8#include <sys/stat.h>
9#include <sys/types.h>
10#include <unistd.h>
11
12#include <fbl/alloc_checker.h>
13#include <fbl/auto_call.h>
14#include <fbl/ref_ptr.h>
15#include <fbl/unique_ptr.h>
16#include <fuzz-utils/path.h>
17#include <lib/fdio/debug.h>
18#include <zircon/errors.h>
19#include <zircon/status.h>
20
21#define ZXDEBUG 0
22
23namespace fuzzing {
24
25// Public methods
26
27Path::Path() {
28    fbl::AllocChecker ac;
29    path_ = AdoptRef(new (&ac) PathBuffer());
30    ZX_ASSERT(ac.check());
31    Reset();
32}
33
34Path::Path(fbl::RefPtr<PathBuffer> path) : path_(path), length_(path->buffer_.length()) {}
35
36Path::Path(const Path& other) : path_(other.path_), length_(other.length_) {}
37
38Path::~Path() {}
39
40fbl::String Path::Join(const char* relpath) const {
41    ZX_DEBUG_ASSERT(relpath);
42
43    fbl::StringBuffer<PATH_MAX> abspath;
44    abspath.Append(c_str(), length_ - 1);
45
46    // Add each path segment
47    const char* p = relpath;
48    const char* sep;
49    while (p && (sep = strchr(p, '/'))) {
50        // Skip repeated slashes
51        if (p != sep) {
52            abspath.Append('/');
53            abspath.Append(p, sep - p);
54        }
55        p = sep + 1;
56    }
57    if (*p) {
58        abspath.Append('/');
59        abspath.Append(p);
60    }
61
62    return fbl::move(abspath);
63}
64
65zx_status_t Path::GetSize(const char* relpath, size_t* out) const {
66    fbl::String abspath = Join(relpath);
67    struct stat buf;
68    if (stat(abspath.c_str(), &buf) != 0) {
69        xprintf("Failed to get status for '%s': %s\n", abspath.c_str(), strerror(errno));
70        return ZX_ERR_IO;
71    }
72    *out = buf.st_size;
73    return ZX_OK;
74}
75
76fbl::unique_ptr<StringList> Path::List() const {
77    fbl::AllocChecker ac;
78    fbl::unique_ptr<StringList> list(new (&ac) StringList());
79    ZX_ASSERT(ac.check());
80
81    DIR* dir = opendir(c_str());
82    if (!dir) {
83        return fbl::move(list);
84    }
85    auto close_dir = fbl::MakeAutoCall([&dir]() { closedir(dir); });
86
87    struct dirent* ent;
88    while ((ent = readdir(dir))) {
89        if (strcmp(".", ent->d_name) != 0) {
90            list->push_back(ent->d_name);
91        }
92    }
93    return fbl::move(list);
94}
95
96zx_status_t Path::Ensure(const char* relpath) {
97    ZX_DEBUG_ASSERT(relpath);
98    zx_status_t rc;
99
100    // First check if already exists
101    fbl::String abspath = fbl::move(Join(relpath));
102    struct stat buf;
103    if (stat(abspath.c_str(), &buf) == 0 && S_ISDIR(buf.st_mode)) {
104        return ZX_OK;
105    }
106
107    // Now recursively create the parent directories
108    const char* sep = strrchr(relpath, '/');
109    if (sep) {
110        fbl::String prefix(relpath, sep - relpath);
111        if ((rc = Ensure(prefix.c_str())) != ZX_OK) {
112            xprintf("Failed to ensure parent directory: %s\n", zx_status_get_string(rc));
113            return rc;
114        }
115    }
116
117    // Finally, create the last directory
118    if (mkdir(abspath.c_str(), 0777) != 0) {
119        xprintf("Failed to make directory '%s': %s.\n", abspath.c_str(), strerror(errno));
120        return ZX_ERR_IO;
121    }
122    return ZX_OK;
123}
124
125zx_status_t Path::Push(const char* relpath) {
126    ZX_DEBUG_ASSERT(relpath);
127    if (*relpath == '\0') {
128        xprintf("Can't push empty path.\n");
129        return ZX_ERR_INVALID_ARGS;
130    }
131    fbl::String abspath = fbl::move(Join(relpath));
132    struct stat buf;
133    if (stat(abspath.c_str(), &buf) != 0) {
134        xprintf("Failed to get status for '%s': %s\n", abspath.c_str(), strerror(errno));
135        return ZX_ERR_IO;
136    }
137    if (!S_ISDIR(buf.st_mode)) {
138        xprintf("Not a directory: %s\n", abspath.c_str());
139        return ZX_ERR_NOT_DIR;
140    }
141
142    fbl::AllocChecker ac;
143    fbl::unique_ptr<Path> cloned(new (&ac) Path(path_));
144    ZX_ASSERT(ac.check());
145
146    cloned->parent_.swap(parent_);
147    parent_.swap(cloned);
148    path_->buffer_.Clear();
149    path_->buffer_.Append(abspath);
150    path_->buffer_.Append('/');
151    length_ = path_->buffer_.length();
152
153    return ZX_OK;
154}
155
156void Path::Pop() {
157    if (!parent_) {
158        return;
159    }
160    length_ = parent_->length_;
161    path_->buffer_.Resize(length_);
162    fbl::unique_ptr<Path> parent;
163    parent.swap(parent_->parent_);
164    parent_.swap(parent);
165}
166
167zx_status_t Path::Remove(const char* relpath) {
168    ZX_DEBUG_ASSERT(relpath);
169    zx_status_t rc;
170
171    fbl::String abspath = fbl::move(Join(relpath));
172    struct stat buf;
173    if (stat(abspath.c_str(), &buf) != 0) {
174        // Ignore missing files
175        if (errno != ENOENT) {
176            xprintf("Failed to get status for '%s': %s\n", abspath.c_str(), strerror(errno));
177            return ZX_ERR_IO;
178        }
179
180    } else if (S_ISDIR(buf.st_mode)) {
181        // Recursively remove directories
182        if ((rc = Push(relpath)) != ZX_OK) {
183            xprintf("Failed to push subdirectory: %s\n", zx_status_get_string(rc));
184            return rc;
185        }
186        auto pop = fbl::MakeAutoCall([this]() { Pop(); });
187
188        auto names = List();
189        for (const char* name = names->first(); name; name = names->next()) {
190            if ((rc = Remove(name)) != ZX_OK) {
191                xprintf("Failed to remove subdirectory: %s\n", zx_status_get_string(rc));
192                return rc;
193            }
194        }
195        if (rmdir(c_str()) != 0) {
196            xprintf("Failed to remove directory '%s': %s\n", c_str(), strerror(errno));
197            return ZX_ERR_IO;
198        }
199        return ZX_OK;
200
201    } else {
202        // Remove file
203        if (unlink(abspath.c_str()) != 0) {
204            xprintf("Failed to unlink '%s': %s\n", abspath.c_str(), strerror(errno));
205            return ZX_ERR_IO;
206        }
207    }
208
209    return ZX_OK;
210}
211
212zx_status_t Path::Rename(const char* old_relpath, const char* new_relpath) {
213    fbl::String old_abspath = fbl::move(Join(old_relpath));
214    fbl::String new_abspath = fbl::move(Join(new_relpath));
215    if (rename(old_abspath.c_str(), new_abspath.c_str()) != 0) {
216        xprintf("Failed to rename '%s' to '%s': %s.\n", old_abspath.c_str(), new_abspath.c_str(),
217                strerror(errno));
218        return ZX_ERR_IO;
219    }
220    return ZX_OK;
221}
222
223void Path::Reset() {
224    parent_.reset();
225    path_->buffer_.Clear();
226    path_->buffer_.Append("/");
227    length_ = path_->buffer_.length();
228}
229
230} // namespace fuzzing
231