1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
|
// Copyright 2018 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "test/util/fs_util.h"
#include <dirent.h>
#ifdef __linux__
#include <linux/magic.h>
#endif // __linux__
#include <sys/stat.h>
#include <sys/statfs.h>
#include <sys/types.h>
#include <unistd.h>
#include "gmock/gmock.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "test/util/cleanup.h"
#include "test/util/file_descriptor.h"
#include "test/util/posix_error.h"
namespace gvisor {
namespace testing {
namespace {
PosixError WriteContentsToFD(int fd, absl::string_view contents) {
int written = 0;
while (static_cast<absl::string_view::size_type>(written) < contents.size()) {
int wrote = write(fd, contents.data() + written, contents.size() - written);
if (wrote < 0) {
if (errno == EINTR) {
continue;
}
return PosixError(
errno, absl::StrCat("WriteContentsToFD fd: ", fd, " write failure."));
}
written += wrote;
}
return NoError();
}
} // namespace
namespace internal {
// Given a collection of file paths, append them all together,
// ensuring that the proper path separators are inserted between them.
std::string JoinPathImpl(std::initializer_list<absl::string_view> paths) {
std::string result;
if (paths.size() != 0) {
// This size calculation is worst-case: it assumes one extra "/" for every
// path other than the first.
size_t total_size = paths.size() - 1;
for (const absl::string_view path : paths) total_size += path.size();
result.resize(total_size);
auto begin = result.begin();
auto out = begin;
bool trailing_slash = false;
for (absl::string_view path : paths) {
if (path.empty()) continue;
if (path.front() == '/') {
if (trailing_slash) {
path.remove_prefix(1);
}
} else {
if (!trailing_slash && out != begin) *out++ = '/';
}
const size_t this_size = path.size();
memcpy(&*out, path.data(), this_size);
out += this_size;
trailing_slash = out[-1] == '/';
}
result.erase(out - begin);
}
return result;
}
} // namespace internal
// Returns a status or the current working directory.
PosixErrorOr<std::string> GetCWD() {
char buffer[PATH_MAX + 1] = {};
if (getcwd(buffer, PATH_MAX) == nullptr) {
return PosixError(errno, "GetCWD() failed");
}
return std::string(buffer);
}
PosixErrorOr<struct stat> Stat(absl::string_view path) {
struct stat stat_buf;
int res = stat(std::string(path).c_str(), &stat_buf);
if (res < 0) {
return PosixError(errno, absl::StrCat("stat ", path));
}
return stat_buf;
}
PosixErrorOr<struct stat> Lstat(absl::string_view path) {
struct stat stat_buf;
int res = lstat(std::string(path).c_str(), &stat_buf);
if (res < 0) {
return PosixError(errno, absl::StrCat("lstat ", path));
}
return stat_buf;
}
PosixErrorOr<struct stat> Fstat(int fd) {
struct stat stat_buf;
int res = fstat(fd, &stat_buf);
if (res < 0) {
return PosixError(errno, absl::StrCat("fstat ", fd));
}
return stat_buf;
}
PosixErrorOr<bool> Exists(absl::string_view path) {
struct stat stat_buf;
int res = lstat(std::string(path).c_str(), &stat_buf);
if (res < 0) {
if (errno == ENOENT) {
return false;
}
return PosixError(errno, absl::StrCat("lstat ", path));
}
return true;
}
PosixErrorOr<bool> IsDirectory(absl::string_view path) {
ASSIGN_OR_RETURN_ERRNO(struct stat stat_buf, Lstat(path));
if (S_ISDIR(stat_buf.st_mode)) {
return true;
}
return false;
}
PosixError Delete(absl::string_view path) {
int res = unlink(std::string(path).c_str());
if (res < 0) {
return PosixError(errno, absl::StrCat("unlink ", path));
}
return NoError();
}
PosixError Truncate(absl::string_view path, int length) {
int res = truncate(std::string(path).c_str(), length);
if (res < 0) {
return PosixError(errno,
absl::StrCat("truncate ", path, " to length ", length));
}
return NoError();
}
PosixError Chmod(absl::string_view path, int mode) {
int res = chmod(std::string(path).c_str(), mode);
if (res < 0) {
return PosixError(errno, absl::StrCat("chmod ", path));
}
return NoError();
}
PosixError MknodAt(const FileDescriptor& dfd, absl::string_view path, int mode,
dev_t dev) {
int res = mknodat(dfd.get(), std::string(path).c_str(), mode, dev);
if (res < 0) {
return PosixError(errno, absl::StrCat("mknod ", path));
}
return NoError();
}
PosixError Unlink(absl::string_view path) {
int res = unlink(std::string(path).c_str());
if (res < 0) {
return PosixError(errno, absl::StrCat("unlink ", path));
}
return NoError();
}
PosixError UnlinkAt(const FileDescriptor& dfd, absl::string_view path,
int flags) {
int res = unlinkat(dfd.get(), std::string(path).c_str(), flags);
if (res < 0) {
return PosixError(errno, absl::StrCat("unlink ", path));
}
return NoError();
}
PosixError Mkdir(absl::string_view path, int mode) {
int res = mkdir(std::string(path).c_str(), mode);
if (res < 0) {
return PosixError(errno,
absl::StrFormat("mkdir \"%s\" mode %#o", path, mode));
}
return NoError();
}
PosixError Rmdir(absl::string_view path) {
int res = rmdir(std::string(path).c_str());
if (res < 0) {
return PosixError(errno, absl::StrCat("rmdir ", path));
}
return NoError();
}
PosixError SetContents(absl::string_view path, absl::string_view contents) {
ASSIGN_OR_RETURN_ERRNO(bool exists, Exists(path));
if (!exists) {
return PosixError(
ENOENT, absl::StrCat("SetContents file ", path, " doesn't exist."));
}
ASSIGN_OR_RETURN_ERRNO(auto fd, Open(std::string(path), O_WRONLY | O_TRUNC));
return WriteContentsToFD(fd.get(), contents);
}
// Create a file with the given contents (if it does not already exist with the
// given mode) and then set the contents.
PosixError CreateWithContents(absl::string_view path,
absl::string_view contents, int mode) {
ASSIGN_OR_RETURN_ERRNO(
auto fd, Open(std::string(path), O_WRONLY | O_CREAT | O_TRUNC, mode));
return WriteContentsToFD(fd.get(), contents);
}
PosixError GetContents(absl::string_view path, std::string* output) {
ASSIGN_OR_RETURN_ERRNO(auto fd, Open(std::string(path), O_RDONLY));
output->clear();
// Keep reading until we hit an EOF or an error.
return GetContentsFD(fd.get(), output);
}
PosixErrorOr<std::string> GetContents(absl::string_view path) {
std::string ret;
RETURN_IF_ERRNO(GetContents(path, &ret));
return ret;
}
PosixErrorOr<std::string> GetContentsFD(int fd) {
std::string ret;
RETURN_IF_ERRNO(GetContentsFD(fd, &ret));
return ret;
}
PosixError GetContentsFD(int fd, std::string* output) {
// Keep reading until we hit an EOF or an error.
while (true) {
char buf[16 * 1024] = {}; // Read in 16KB chunks.
int bytes_read = read(fd, buf, sizeof(buf));
if (bytes_read < 0) {
if (errno == EINTR) {
continue;
}
return PosixError(errno, "GetContentsFD read failure.");
}
if (bytes_read == 0) {
break; // EOF.
}
output->append(buf, bytes_read);
}
return NoError();
}
PosixErrorOr<std::string> ReadLink(absl::string_view path) {
char buf[PATH_MAX + 1] = {};
int ret = readlink(std::string(path).c_str(), buf, PATH_MAX);
if (ret < 0) {
return PosixError(errno, absl::StrCat("readlink ", path));
}
return std::string(buf, ret);
}
PosixError WalkTree(
absl::string_view path, bool recursive,
const std::function<void(absl::string_view, const struct stat&)>& cb) {
DIR* dir = opendir(std::string(path).c_str());
if (dir == nullptr) {
return PosixError(errno, absl::StrCat("opendir ", path));
}
auto dir_closer = Cleanup([&dir]() { closedir(dir); });
while (true) {
// Readdir(3): If the end of the directory stream is reached, NULL is
// returned and errno is not changed. If an error occurs, NULL is returned
// and errno is set appropriately. To distinguish end of stream and from an
// error, set errno to zero before calling readdir() and then check the
// value of errno if NULL is returned.
errno = 0;
struct dirent* dp = readdir(dir);
if (dp == nullptr) {
if (errno != 0) {
return PosixError(errno, absl::StrCat("readdir ", path));
}
break; // We're done.
}
if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) {
// Skip dots.
continue;
}
auto full_path = JoinPath(path, dp->d_name);
ASSIGN_OR_RETURN_ERRNO(struct stat s, Stat(full_path));
if (S_ISDIR(s.st_mode) && recursive) {
RETURN_IF_ERRNO(WalkTree(full_path, recursive, cb));
} else {
cb(full_path, s);
}
}
// We're done walking so let's invoke our cleanup callback now.
dir_closer.Release()();
// And we have to dispatch the callback on the base directory.
ASSIGN_OR_RETURN_ERRNO(struct stat s, Stat(path));
cb(path, s);
return NoError();
}
PosixErrorOr<std::vector<std::string>> ListDir(absl::string_view abspath,
bool skipdots) {
std::vector<std::string> files;
DIR* dir = opendir(std::string(abspath).c_str());
if (dir == nullptr) {
return PosixError(errno, absl::StrCat("opendir ", abspath));
}
auto dir_closer = Cleanup([&dir]() { closedir(dir); });
while (true) {
// Readdir(3): If the end of the directory stream is reached, NULL is
// returned and errno is not changed. If an error occurs, NULL is returned
// and errno is set appropriately. To distinguish end of stream and from an
// error, set errno to zero before calling readdir() and then check the
// value of errno if NULL is returned.
errno = 0;
struct dirent* dp = readdir(dir);
if (dp == nullptr) {
if (errno != 0) {
return PosixError(errno, absl::StrCat("readdir ", abspath));
}
break; // We're done.
}
if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) {
if (skipdots) {
continue;
}
}
files.push_back(std::string(dp->d_name));
}
return files;
}
PosixError DirContains(absl::string_view path,
const std::vector<std::string>& expect,
const std::vector<std::string>& exclude) {
ASSIGN_OR_RETURN_ERRNO(auto listing, ListDir(path, false));
for (auto& expected_entry : expect) {
auto cursor = std::find(listing.begin(), listing.end(), expected_entry);
if (cursor == listing.end()) {
return PosixError(ENOENT, absl::StrFormat("Failed to find '%s' in '%s'",
expected_entry, path));
}
}
for (auto& excluded_entry : exclude) {
auto cursor = std::find(listing.begin(), listing.end(), excluded_entry);
if (cursor != listing.end()) {
return PosixError(ENOENT, absl::StrCat("File '", excluded_entry,
"' found in path '", path, "'"));
}
}
return NoError();
}
PosixError EventuallyDirContains(absl::string_view path,
const std::vector<std::string>& expect,
const std::vector<std::string>& exclude) {
constexpr int kRetryCount = 100;
const absl::Duration kRetryDelay = absl::Milliseconds(100);
for (int i = 0; i < kRetryCount; ++i) {
auto res = DirContains(path, expect, exclude);
if (res.ok()) {
return res;
}
if (i < kRetryCount - 1) {
// Sleep if this isn't the final iteration.
absl::SleepFor(kRetryDelay);
}
}
return PosixError(ETIMEDOUT,
"Timed out while waiting for directory to contain files ");
}
PosixError RecursivelyDelete(absl::string_view path, int* undeleted_dirs,
int* undeleted_files) {
ASSIGN_OR_RETURN_ERRNO(bool exists, Exists(path));
if (!exists) {
return PosixError(ENOENT, absl::StrCat(path, " does not exist"));
}
ASSIGN_OR_RETURN_ERRNO(bool dir, IsDirectory(path));
if (!dir) {
// Nothing recursive needs to happen we can just call Delete.
auto status = Delete(path);
if (!status.ok() && undeleted_files) {
(*undeleted_files)++;
}
return status;
}
return WalkTree(path, /*recursive=*/true,
[&](absl::string_view absolute_path, const struct stat& s) {
if (S_ISDIR(s.st_mode)) {
auto rm_status = Rmdir(absolute_path);
if (!rm_status.ok() && undeleted_dirs) {
(*undeleted_dirs)++;
}
} else {
auto delete_status = Delete(absolute_path);
if (!delete_status.ok() && undeleted_files) {
(*undeleted_files)++;
}
}
});
}
PosixError RecursivelyCreateDir(absl::string_view path) {
if (path.empty() || path == "/") {
return PosixError(EINVAL, "Cannot create root!");
}
// Does it already exist, if so we're done.
ASSIGN_OR_RETURN_ERRNO(bool exists, Exists(path));
if (exists) {
return NoError();
}
// Do we need to create directories under us?
auto dirname = Dirname(path);
ASSIGN_OR_RETURN_ERRNO(exists, Exists(dirname));
if (!exists) {
RETURN_IF_ERRNO(RecursivelyCreateDir(dirname));
}
return Mkdir(path);
}
// Makes a path absolute with respect to an optional base. If no base is
// provided it will use the current working directory.
PosixErrorOr<std::string> MakeAbsolute(absl::string_view filename,
absl::string_view base) {
if (filename.empty()) {
return PosixError(EINVAL, "filename cannot be empty.");
}
if (filename[0] == '/') {
// This path is already absolute.
return std::string(filename);
}
std::string actual_base;
if (!base.empty()) {
actual_base = std::string(base);
} else {
auto cwd_or = GetCWD();
RETURN_IF_ERRNO(cwd_or.error());
actual_base = cwd_or.ValueOrDie();
}
// Reverse iterate removing trailing slashes, effectively right trim '/'.
for (int i = actual_base.size() - 1; i >= 0 && actual_base[i] == '/'; --i) {
actual_base.erase(i, 1);
}
if (filename == ".") {
return actual_base.empty() ? "/" : actual_base;
}
return absl::StrCat(actual_base, "/", filename);
}
std::string CleanPath(const absl::string_view unclean_path) {
std::string path = std::string(unclean_path);
const char* src = path.c_str();
std::string::iterator dst = path.begin();
// Check for absolute path and determine initial backtrack limit.
const bool is_absolute_path = *src == '/';
if (is_absolute_path) {
*dst++ = *src++;
while (*src == '/') ++src;
}
std::string::const_iterator backtrack_limit = dst;
// Process all parts
while (*src) {
bool parsed = false;
if (src[0] == '.') {
// 1dot ".<whateverisnext>", check for END or SEP.
if (src[1] == '/' || !src[1]) {
if (*++src) {
++src;
}
parsed = true;
} else if (src[1] == '.' && (src[2] == '/' || !src[2])) {
// 2dot END or SEP (".." | "../<whateverisnext>").
src += 2;
if (dst != backtrack_limit) {
// We can backtrack the previous part
for (--dst; dst != backtrack_limit && dst[-1] != '/'; --dst) {
// Empty.
}
} else if (!is_absolute_path) {
// Failed to backtrack and we can't skip it either. Rewind and copy.
src -= 2;
*dst++ = *src++;
*dst++ = *src++;
if (*src) {
*dst++ = *src;
}
// We can never backtrack over a copied "../" part so set new limit.
backtrack_limit = dst;
}
if (*src) {
++src;
}
parsed = true;
}
}
// If not parsed, copy entire part until the next SEP or EOS.
if (!parsed) {
while (*src && *src != '/') {
*dst++ = *src++;
}
if (*src) {
*dst++ = *src++;
}
}
// Skip consecutive SEP occurrences
while (*src == '/') {
++src;
}
}
// Calculate and check the length of the cleaned path.
int path_length = dst - path.begin();
if (path_length != 0) {
// Remove trailing '/' except if it is root path ("/" ==> path_length := 1)
if (path_length > 1 && path[path_length - 1] == '/') {
--path_length;
}
path.resize(path_length);
} else {
// The cleaned path is empty; assign "." as per the spec.
path.assign(1, '.');
}
return path;
}
PosixErrorOr<std::string> GetRelativePath(absl::string_view source,
absl::string_view dest) {
if (!absl::StartsWith(source, "/") || !absl::StartsWith(dest, "/")) {
// At least one of the inputs is not an absolute path.
return PosixError(
EINVAL,
"GetRelativePath: At least one of the inputs is not an absolute path.");
}
const std::string clean_source = CleanPath(source);
const std::string clean_dest = CleanPath(dest);
auto source_parts = absl::StrSplit(clean_source, '/', absl::SkipEmpty());
auto dest_parts = absl::StrSplit(clean_dest, '/', absl::SkipEmpty());
auto source_iter = source_parts.begin();
auto dest_iter = dest_parts.begin();
// Advance past common prefix.
while (source_iter != source_parts.end() && dest_iter != dest_parts.end() &&
*source_iter == *dest_iter) {
++source_iter;
++dest_iter;
}
// Build result backtracking.
std::string result = "";
while (source_iter != source_parts.end()) {
absl::StrAppend(&result, "../");
++source_iter;
}
// Add remaining path to dest.
while (dest_iter != dest_parts.end()) {
absl::StrAppend(&result, *dest_iter, "/");
++dest_iter;
}
if (result.empty()) {
return std::string(".");
}
// Remove trailing slash.
result.erase(result.size() - 1);
return result;
}
absl::string_view Dirname(absl::string_view path) {
return SplitPath(path).first;
}
absl::string_view Basename(absl::string_view path) {
return SplitPath(path).second;
}
std::pair<absl::string_view, absl::string_view> SplitPath(
absl::string_view path) {
std::string::size_type pos = path.find_last_of('/');
// Handle the case with no '/' in 'path'.
if (pos == absl::string_view::npos) {
return std::make_pair(path.substr(0, 0), path);
}
// Handle the case with a single leading '/' in 'path'.
if (pos == 0) {
return std::make_pair(path.substr(0, 1), absl::ClippedSubstr(path, 1));
}
return std::make_pair(path.substr(0, pos),
absl::ClippedSubstr(path, pos + 1));
}
std::string JoinPath(absl::string_view path1, absl::string_view path2) {
if (path1.empty()) {
return std::string(path2);
}
if (path2.empty()) {
return std::string(path1);
}
if (path1.back() == '/') {
if (path2.front() == '/') {
return absl::StrCat(path1, absl::ClippedSubstr(path2, 1));
}
} else {
if (path2.front() != '/') {
return absl::StrCat(path1, "/", path2);
}
}
return absl::StrCat(path1, path2);
}
PosixErrorOr<std::string> ProcessExePath(int pid) {
if (pid <= 0) {
return PosixError(EINVAL, "Invalid pid specified");
}
return ReadLink(absl::StrCat("/proc/", pid, "/exe"));
}
#ifdef __linux__
PosixErrorOr<bool> IsTmpfs(const std::string& path) {
struct statfs stat;
if (statfs(path.c_str(), &stat)) {
if (errno == ENOENT) {
// Nothing at path, don't raise this as an error. Instead, just report no
// tmpfs at path.
return false;
}
return PosixError(errno,
absl::StrFormat("statfs(\"%s\", %#p)", path, &stat));
}
return stat.f_type == TMPFS_MAGIC;
}
#endif // __linux__
PosixErrorOr<bool> IsOverlayfs(const std::string& path) {
struct statfs stat;
if (statfs(path.c_str(), &stat)) {
if (errno == ENOENT) {
// Nothing at path, don't raise this as an error. Instead, just report no
// overlayfs at path.
return false;
}
return PosixError(errno,
absl::StrFormat("statfs(\"%s\", %#p)", path, &stat));
}
return stat.f_type == OVERLAYFS_SUPER_MAGIC;
}
PosixError CheckSameFile(const FileDescriptor& fd1, const FileDescriptor& fd2) {
struct stat stat_result1, stat_result2;
int res = fstat(fd1.get(), &stat_result1);
if (res < 0) {
return PosixError(errno, absl::StrCat("fstat ", fd1.get()));
}
res = fstat(fd2.get(), &stat_result2);
if (res < 0) {
return PosixError(errno, absl::StrCat("fstat ", fd2.get()));
}
EXPECT_EQ(stat_result1.st_dev, stat_result2.st_dev);
EXPECT_EQ(stat_result1.st_ino, stat_result2.st_ino);
return NoError();
}
} // namespace testing
} // namespace gvisor
|