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 "fixture.h"
6
7#include <dirent.h>
8#include <errno.h>
9#include <fcntl.h>
10#include <inttypes.h>
11#include <limits.h>
12#include <stdarg.h>
13#include <stdint.h>
14#include <string.h>
15#include <sys/stat.h>
16#include <sys/types.h>
17#include <unistd.h>
18
19#include <fbl/string.h>
20#include <fbl/string_buffer.h>
21#include <fbl/string_printf.h>
22#include <fbl/unique_fd.h>
23#include <unittest/unittest.h>
24#include <zircon/syscalls.h>
25
26namespace fuzzing {
27namespace testing {
28
29// Public methods
30
31Fixture::~Fixture() {
32    Reset();
33}
34
35fbl::String Fixture::path(const char* fmt, ...) const {
36    va_list ap;
37    va_start(ap, fmt);
38    fbl::String result = path(fmt, ap);
39    va_end(ap);
40    return result;
41}
42
43fbl::String Fixture::path(const char* fmt, va_list ap) const {
44    if (!fmt) {
45        return root_;
46    }
47
48    fbl::StringBuffer<PATH_MAX> buffer;
49    buffer.AppendVPrintf(fmt, ap);
50    fbl::String relpath = buffer.ToString();
51    if (relpath[0] == '/') {
52        return relpath;
53    }
54
55    buffer.Clear();
56    buffer.Append(root_);
57    buffer.Append(relpath);
58
59    return buffer.ToString();
60}
61
62// Protected methods
63
64Fixture::Fixture() {}
65
66bool Fixture::Create() {
67    BEGIN_HELPER;
68    Reset();
69
70    uint64_t randnum;
71    zx_cprng_draw(&randnum, sizeof(randnum));
72    root_ = fbl::StringPrintf("/tmp/path-unit-test-%" PRIu64 "/", randnum);
73    ASSERT_TRUE(CreateDirectory(nullptr));
74
75    END_HELPER;
76}
77
78bool Fixture::CreateFile(const char* pathname, const char* contents) {
79    BEGIN_HELPER;
80    ASSERT_NONNULL(pathname);
81
82    fbl::String local = path(pathname);
83    pathname = local.c_str();
84
85    const char* sep = strrchr(pathname, '/');
86    fbl::String basename(pathname, sep - pathname);
87    ASSERT_TRUE(CreateDirectory(basename.c_str()));
88
89    fbl::unique_fd fd(open(pathname, O_RDWR | O_CREAT, 0777));
90    ASSERT_TRUE(!!fd);
91    if (contents) {
92        ASSERT_GE(write(fd.get(), contents, strlen(contents) + 1), 0);
93    }
94
95    END_HELPER;
96}
97
98bool Fixture::CreateDirectory(const char* pathname) {
99    BEGIN_HELPER;
100
101    fbl::String local = path(pathname);
102    pathname = local.c_str();
103
104    struct stat buf;
105    if (stat(pathname, &buf) == 0) {
106        ASSERT_TRUE(S_ISDIR(buf.st_mode));
107    } else {
108        ASSERT_EQ(errno, ENOENT);
109        // Trim trailing slashes
110        size_t len = strlen(pathname);
111        while (len > 0 && pathname[len - 1] == '/') {
112            --len;
113        }
114        local.Set(pathname, len);
115        pathname = local.c_str();
116
117        // Find last segment
118        const char* sep = strrchr(pathname, '/');
119        ASSERT_NONNULL(sep);
120        fbl::String basename(pathname, sep - pathname);
121
122        ASSERT_TRUE(CreateDirectory(basename.c_str()));
123        ASSERT_GE(mkdir(pathname, 0777), 0);
124    }
125
126    END_HELPER;
127}
128
129bool Fixture::RemoveDirectory(const char* pathname) {
130    BEGIN_HELPER;
131    ASSERT_NONNULL(pathname);
132
133    char buffer[PATH_MAX];
134    DIR* dir = opendir(pathname);
135    if (dir) {
136        struct dirent* ent;
137        while ((ent = readdir(dir))) {
138            if (strcmp(".", ent->d_name) == 0) {
139                continue;
140            }
141            snprintf(buffer, sizeof(buffer), "%s/%s", pathname, ent->d_name);
142            if (ent->d_type == DT_DIR) {
143                EXPECT_TRUE(RemoveDirectory(buffer));
144            } else {
145                EXPECT_GE(unlink(buffer), 0);
146            }
147        }
148        closedir(dir);
149        EXPECT_GE(rmdir(pathname), 0);
150    }
151
152    END_HELPER;
153}
154
155void Fixture::Reset() {
156    if (!root_.empty()) {
157        RemoveDirectory(root_.c_str());
158    }
159    root_.clear();
160    unsafe_.clear();
161}
162
163} // namespace testing
164} // namespace fuzzing
165