blob: 75026af6b310e17789bd742bb63066bffe082655 [file] [log] [blame]
Laszlo Csomor9c951962016-11-10 13:31:27 +00001// Copyright 2016 The Bazel Authors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14#include <fcntl.h>
Laszlo Csomorfd310612016-12-21 13:34:09 +000015#include <limits.h>
Laszlo Csomor9c951962016-11-10 13:31:27 +000016#include <unistd.h>
17
Laszlo Csomor251bf032016-11-16 11:01:32 +000018#include <algorithm>
19
Laszlo Csomor1324e832016-12-16 10:25:14 +000020#include "src/main/cpp/util/file.h"
Laszlo Csomor9c951962016-11-10 13:31:27 +000021#include "src/main/cpp/util/file_platform.h"
ccalvarinac69da02018-06-05 15:27:26 -070022#include "src/main/cpp/util/path.h"
23#include "src/main/cpp/util/path_platform.h"
Laszlo Csomorb535e6d2017-02-15 18:07:29 +000024#include "src/test/cpp/util/test_util.h"
ccalvarin8e9f4a82018-03-23 08:19:37 -070025#include "googletest/include/gtest/gtest.h"
Laszlo Csomor9c951962016-11-10 13:31:27 +000026
27namespace blaze_util {
28
Laszlo Csomor251bf032016-11-16 11:01:32 +000029using std::pair;
30using std::string;
31using std::vector;
32
Laszlo Csomor1324e832016-12-16 10:25:14 +000033static bool Symlink(const string& old_path, const string& new_path) {
34 return symlink(old_path.c_str(), new_path.c_str()) == 0;
35}
36
37static bool CreateEmptyFile(const string& path) {
38 // From the man page of open (man 2 open):
39 // int open(const char *pathname, int flags, mode_t mode);
40 //
41 // mode specifies the permissions to use in case a new file is created.
42 // This argument must be supplied when O_CREAT is specified in flags;
43 // if O_CREAT is not specified, then mode is ignored.
44 int fd = open(path.c_str(), O_CREAT | O_WRONLY, 0700);
45 if (fd == -1) {
46 return false;
47 }
48 return close(fd) == 0;
49}
50
Laszlo Csomor1324e832016-12-16 10:25:14 +000051void MockDirectoryListingFunction(const string& path,
52 DirectoryEntryConsumer* consume) {
53 if (path == "root") {
54 consume->Consume("root/file1", false);
55 consume->Consume("root/dir2", true);
56 consume->Consume("root/dir1", true);
57 } else if (path == "root/dir1") {
58 consume->Consume("root/dir1/dir3", true);
59 consume->Consume("root/dir1/file2", false);
60 } else if (path == "root/dir2") {
61 consume->Consume("root/dir2/file3", false);
62 } else if (path == "root/dir1/dir3") {
63 consume->Consume("root/dir1/dir3/file4", false);
64 consume->Consume("root/dir1/dir3/file5", false);
65 } else {
66 // Unexpected path
67 GTEST_FAIL();
68 }
69}
70
Laszlo Csomord2cba292017-02-10 10:38:14 +000071TEST(FilePosixTest, GetAllFilesUnder) {
Laszlo Csomor1324e832016-12-16 10:25:14 +000072 vector<string> result;
73 _GetAllFilesUnder("root", &result, &MockDirectoryListingFunction);
74 std::sort(result.begin(), result.end());
75
76 vector<string> expected({"root/dir1/dir3/file4", "root/dir1/dir3/file5",
Laszlo Csomorb535e6d2017-02-15 18:07:29 +000077 "root/dir1/file2", "root/dir2/file3",
78 "root/file1"});
Laszlo Csomor1324e832016-12-16 10:25:14 +000079 ASSERT_EQ(expected, result);
80}
81
Laszlo Csomord2cba292017-02-10 10:38:14 +000082TEST(FilePosixTest, MakeDirectories) {
Laszlo Csomor1324e832016-12-16 10:25:14 +000083 const char* tmp_dir = getenv("TEST_TMPDIR");
84 ASSERT_STRNE(tmp_dir, NULL);
85 const char* test_src_dir = getenv("TEST_SRCDIR");
86 ASSERT_STRNE(NULL, test_src_dir);
87
88 string dir = JoinPath(tmp_dir, "x/y/z");
89 bool ok = MakeDirectories(dir, 0755);
90 ASSERT_TRUE(ok);
91
92 // Changing permissions on an existing dir should work.
93 ok = MakeDirectories(dir, 0750);
94 ASSERT_TRUE(ok);
95 struct stat filestat = {};
96 ASSERT_EQ(0, stat(dir.c_str(), &filestat));
Benjamin Petersonf02b7552018-04-17 01:09:20 -070097 ASSERT_EQ(mode_t(0750), filestat.st_mode & 0777);
Laszlo Csomor1324e832016-12-16 10:25:14 +000098
99 // srcdir shouldn't be writable.
100 // TODO(ulfjack): Fix this!
101 // string srcdir = JoinPath(test_src_dir, "x/y/z");
102 // ok = MakeDirectories(srcdir, 0755);
103 // ASSERT_FALSE(ok);
104 // ASSERT_EQ(EACCES, errno);
105
106 // Can't make a dir out of a file.
107 string non_dir = JoinPath(dir, "w");
108 ASSERT_TRUE(CreateEmptyFile(non_dir));
109 ok = MakeDirectories(non_dir, 0755);
110 ASSERT_FALSE(ok);
111 ASSERT_EQ(ENOTDIR, errno);
112
113 // Valid symlink should work.
114 string symlink = JoinPath(tmp_dir, "z");
115 ASSERT_TRUE(Symlink(dir, symlink));
116 ok = MakeDirectories(symlink, 0755);
117 ASSERT_TRUE(ok);
118
119 // Error: Symlink to a file.
120 symlink = JoinPath(tmp_dir, "w");
121 ASSERT_TRUE(Symlink(non_dir, symlink));
122 ok = MakeDirectories(symlink, 0755);
123 ASSERT_FALSE(ok);
124 ASSERT_EQ(ENOTDIR, errno);
125
126 // Error: Symlink to a dir with wrong perms.
127 symlink = JoinPath(tmp_dir, "s");
128 ASSERT_TRUE(Symlink("/", symlink));
129
130 // These perms will force a chmod()
131 // TODO(ulfjack): Fix this!
132 // ok = MakeDirectories(symlink, 0000);
133 // ASSERTFALSE(ok);
134 // ASSERT_EQ(EPERM, errno);
135
136 // Edge cases.
137 ASSERT_FALSE(MakeDirectories("", 0755));
138 ASSERT_EQ(EACCES, errno);
139 ASSERT_FALSE(MakeDirectories("/", 0755));
140 ASSERT_EQ(EACCES, errno);
141}
142
Laszlo Csomord2cba292017-02-10 10:38:14 +0000143TEST(FilePosixTest, HammerMakeDirectories) {
Laszlo Csomor1324e832016-12-16 10:25:14 +0000144 const char* tmp_dir = getenv("TEST_TMPDIR");
145 ASSERT_STRNE(tmp_dir, NULL);
146
147 string path = JoinPath(tmp_dir, "x/y/z");
148 // TODO(ulfjack): Fix this!
149 // ASSERT_LE(0, fork());
150 // ASSERT_TRUE(MakeDirectories(path, 0755));
151}
Laszlo Csomor760f7862016-12-19 15:46:47 +0000152
Laszlo Csomor9c951962016-11-10 13:31:27 +0000153TEST(FilePosixTest, PathExists) {
154 ASSERT_FALSE(PathExists("/this/should/not/exist/mkay"));
155 ASSERT_FALSE(PathExists("non.existent"));
156 ASSERT_FALSE(PathExists(""));
157
158 // /usr/bin/yes exists on Linux, Darwin, and MSYS
159 ASSERT_TRUE(PathExists("/"));
160 ASSERT_TRUE(PathExists("/usr"));
161 ASSERT_TRUE(PathExists("/usr/"));
162 ASSERT_TRUE(PathExists("/usr/bin/yes"));
163}
164
165TEST(FilePosixTest, CanAccess) {
Laszlo Csomor00549b42017-01-11 09:12:10 +0000166 ASSERT_FALSE(CanReadFile("/this/should/not/exist/mkay"));
167 ASSERT_FALSE(CanExecuteFile("/this/should/not/exist/mkay"));
168 ASSERT_FALSE(CanAccessDirectory("/this/should/not/exist/mkay"));
Laszlo Csomor9c951962016-11-10 13:31:27 +0000169
Laszlo Csomor00549b42017-01-11 09:12:10 +0000170 ASSERT_FALSE(CanReadFile("non.existent"));
171 ASSERT_FALSE(CanExecuteFile("non.existent"));
172 ASSERT_FALSE(CanAccessDirectory("non.existent"));
Laszlo Csomor9c951962016-11-10 13:31:27 +0000173
Laszlo Csomor00549b42017-01-11 09:12:10 +0000174 const char* tmpdir = getenv("TEST_TMPDIR");
175 ASSERT_NE(nullptr, tmpdir);
176 ASSERT_NE(0, *tmpdir);
Laszlo Csomor9c951962016-11-10 13:31:27 +0000177
Laszlo Csomor00549b42017-01-11 09:12:10 +0000178 string dir(JoinPath(tmpdir, "canaccesstest"));
179 ASSERT_EQ(0, mkdir(dir.c_str(), 0700));
Laszlo Csomor9c951962016-11-10 13:31:27 +0000180
Laszlo Csomor00549b42017-01-11 09:12:10 +0000181 ASSERT_FALSE(CanReadFile(dir));
182 ASSERT_FALSE(CanExecuteFile(dir));
183 ASSERT_TRUE(CanAccessDirectory(dir));
Laszlo Csomor9c951962016-11-10 13:31:27 +0000184
Laszlo Csomor00549b42017-01-11 09:12:10 +0000185 string file(JoinPath(dir, "foo.txt"));
Laszlo Csomorb535e6d2017-02-15 18:07:29 +0000186 AutoFileStream fh(fopen(file.c_str(), "wt"));
187 EXPECT_TRUE(fh.IsOpen());
Laszlo Csomor00549b42017-01-11 09:12:10 +0000188 ASSERT_LT(0, fprintf(fh, "hello"));
Laszlo Csomorb535e6d2017-02-15 18:07:29 +0000189 fh.Close();
Laszlo Csomor9c951962016-11-10 13:31:27 +0000190
Laszlo Csomor00549b42017-01-11 09:12:10 +0000191 ASSERT_TRUE(CanReadFile(file));
192 ASSERT_FALSE(CanExecuteFile(file));
193 ASSERT_FALSE(CanAccessDirectory(file));
194
195 ASSERT_EQ(0, chmod(file.c_str(), 0100));
196 ASSERT_FALSE(CanReadFile(file));
197 ASSERT_TRUE(CanExecuteFile(file));
198 ASSERT_FALSE(CanAccessDirectory(file));
199
200 ASSERT_EQ(0, chmod(dir.c_str(), 0500));
201 ASSERT_FALSE(CanReadFile(dir));
202 ASSERT_FALSE(CanExecuteFile(dir));
203 ASSERT_FALSE(CanAccessDirectory(dir));
204 ASSERT_EQ(0, chmod(dir.c_str(), 0700));
205
206 ASSERT_EQ(0, unlink(file.c_str()));
207 ASSERT_EQ(0, rmdir(dir.c_str()));
Laszlo Csomor9c951962016-11-10 13:31:27 +0000208}
209
Laszlo Csomor9c951962016-11-10 13:31:27 +0000210TEST(FilePosixTest, ChangeDirectory) {
211 // Retrieve the current working directory.
212 char old_wd[PATH_MAX];
213 ASSERT_EQ(old_wd, getcwd(old_wd, PATH_MAX));
214
215 // Change to a different directory and assert it was successful.
216 ASSERT_FALSE(blaze_util::ChangeDirectory("/non/existent/path"));
217 ASSERT_TRUE(blaze_util::ChangeDirectory("/usr"));
218 char new_wd[PATH_MAX];
219 ASSERT_EQ(new_wd, getcwd(new_wd, PATH_MAX));
220 ASSERT_EQ(string("/usr"), string(new_wd));
221
222 // Change back to the original CWD.
223 ASSERT_TRUE(blaze_util::ChangeDirectory(old_wd));
224 ASSERT_EQ(new_wd, getcwd(new_wd, PATH_MAX));
225 ASSERT_EQ(string(old_wd), string(new_wd));
226}
227
Laszlo Csomor251bf032016-11-16 11:01:32 +0000228class MockDirectoryEntryConsumer : public DirectoryEntryConsumer {
229 public:
Laszlo Csomorb535e6d2017-02-15 18:07:29 +0000230 void Consume(const std::string& name, bool is_directory) override {
Laszlo Csomor251bf032016-11-16 11:01:32 +0000231 entries.push_back(pair<string, bool>(name, is_directory));
232 }
233
234 vector<pair<string, bool> > entries;
235};
236
237TEST(FilePosixTest, ForEachDirectoryEntry) {
238 // Get the test's temp dir.
239 char* tmpdir_cstr = getenv("TEST_TMPDIR");
240 ASSERT_FALSE(tmpdir_cstr == NULL);
241 string tempdir(tmpdir_cstr);
242 ASSERT_FALSE(tempdir.empty());
243 if (tempdir.back() == '/') {
244 tempdir = tempdir.substr(0, tempdir.size() - 1);
245 }
246
247 // Create the root directory for the mock directory tree.
248 string root = tempdir + "/FilePosixTest.ForEachDirectoryEntry.root";
249 ASSERT_EQ(0, mkdir(root.c_str(), 0700));
250
251 // Names of mock files and directories.
252 string dir = root + "/dir";
253 string file = root + "/file";
254 string dir_sym = root + "/dir_sym";
255 string file_sym = root + "/file_sym";
256 string subfile = dir + "/subfile";
257 string subfile_through_sym = dir_sym + "/subfile";
258
259 // Create mock directory, file, and symlinks.
Laszlo Csomorb535e6d2017-02-15 18:07:29 +0000260 AutoFd fd(open(file.c_str(), O_CREAT, 0700));
261 ASSERT_TRUE(fd.IsOpen());
262 fd.Close();
Laszlo Csomor251bf032016-11-16 11:01:32 +0000263 ASSERT_EQ(0, mkdir(dir.c_str(), 0700));
264 ASSERT_EQ(0, symlink("dir", dir_sym.c_str()));
265 ASSERT_EQ(0, symlink("file", file_sym.c_str()));
266 fd = open(subfile.c_str(), O_CREAT, 0700);
Laszlo Csomorb535e6d2017-02-15 18:07:29 +0000267 ASSERT_TRUE(fd.IsOpen());
268 fd.Close();
Laszlo Csomor251bf032016-11-16 11:01:32 +0000269
Laszlo Csomorb535e6d2017-02-15 18:07:29 +0000270 // Assert that stat'ing the symlinks (with following them) point to the
271 // right filesystem entry types.
Laszlo Csomor251bf032016-11-16 11:01:32 +0000272 struct stat stat_buf;
273 ASSERT_EQ(0, stat(dir_sym.c_str(), &stat_buf));
274 ASSERT_TRUE(S_ISDIR(stat_buf.st_mode));
275 ASSERT_EQ(0, stat(file_sym.c_str(), &stat_buf));
276 ASSERT_FALSE(S_ISDIR(stat_buf.st_mode));
277
278 // Actual test: list the directory.
279 MockDirectoryEntryConsumer consumer;
280 ForEachDirectoryEntry(root, &consumer);
Benjamin Petersonf02b7552018-04-17 01:09:20 -0700281 ASSERT_EQ(size_t(4), consumer.entries.size());
Laszlo Csomor251bf032016-11-16 11:01:32 +0000282
283 // Sort the collected directory entries.
284 struct {
Laszlo Csomorb535e6d2017-02-15 18:07:29 +0000285 bool operator()(const pair<string, bool>& a,
286 const pair<string, bool>& b) {
Laszlo Csomor251bf032016-11-16 11:01:32 +0000287 return a.first < b.first;
288 }
289 } sort_pairs;
290
291 std::sort(consumer.entries.begin(), consumer.entries.end(), sort_pairs);
292
293 // Assert that the directory entries have the right name and type.
294 pair<string, bool> expected;
295 expected = pair<string, bool>(dir, true);
296 ASSERT_EQ(expected, consumer.entries[0]);
297 expected = pair<string, bool>(dir_sym, false);
298 ASSERT_EQ(expected, consumer.entries[1]);
299 expected = pair<string, bool>(file, false);
300 ASSERT_EQ(expected, consumer.entries[2]);
301 expected = pair<string, bool>(file_sym, false);
302 ASSERT_EQ(expected, consumer.entries[3]);
303
304 // Actual test: list a directory symlink.
305 consumer.entries.clear();
306 ForEachDirectoryEntry(dir_sym, &consumer);
Benjamin Petersonf02b7552018-04-17 01:09:20 -0700307 ASSERT_EQ(size_t(1), consumer.entries.size());
Laszlo Csomor251bf032016-11-16 11:01:32 +0000308 expected = pair<string, bool>(subfile_through_sym, false);
309 ASSERT_EQ(expected, consumer.entries[0]);
310
311 // Actual test: list a path that's actually a file, not a directory.
312 consumer.entries.clear();
313 ForEachDirectoryEntry(file, &consumer);
314 ASSERT_TRUE(consumer.entries.empty());
315
316 // Cleanup: delete mock directory tree.
317 rmdir(subfile.c_str());
318 rmdir(dir.c_str());
319 unlink(dir_sym.c_str());
320 unlink(file.c_str());
321 unlink(file_sym.c_str());
322 rmdir(root.c_str());
323}
324
Laszlo Csomor9c951962016-11-10 13:31:27 +0000325} // namespace blaze_util