Coverage Report

Created: 2026-10-28 07:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/serenity/Userland/Libraries/LibFileSystem/FileSystem.cpp
Line
Count
Source
1
/*
2
 * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
3
 * Copyright (c) 2023, Cameron Youell <cameronyouell@gmail.com>
4
 *
5
 * SPDX-License-Identifier: BSD-2-Clause
6
 */
7
8
#include <AK/LexicalPath.h>
9
#include <AK/ScopeGuard.h>
10
#include <LibCore/DirIterator.h>
11
#include <LibCore/System.h>
12
#include <LibFileSystem/FileSystem.h>
13
#include <limits.h>
14
15
#if defined(AK_OS_SERENITY)
16
#    include <serenity.h>
17
#elif defined(AK_OS_BSD_GENERIC)
18
#    include <sys/disk.h>
19
#elif defined(AK_OS_LINUX)
20
#    include <linux/fs.h>
21
#endif
22
23
// On Linux distros that use glibc `basename` is defined as a macro that expands to `__xpg_basename`, so we undefine it
24
#if defined(AK_OS_LINUX) && defined(basename)
25
#    undef basename
26
#endif
27
28
namespace FileSystem {
29
30
ErrorOr<ByteString> current_working_directory()
31
0
{
32
0
    return Core::System::getcwd();
33
0
}
34
35
ErrorOr<ByteString> absolute_path(StringView path)
36
0
{
37
0
    if (exists(path))
38
0
        return real_path(path);
39
40
0
    if (path.starts_with("/"sv))
41
0
        return LexicalPath::canonicalized_path(path);
42
43
0
    auto working_directory = TRY(current_working_directory());
44
0
    return LexicalPath::absolute_path(working_directory, path);
45
0
}
46
47
ErrorOr<ByteString> real_path(StringView path)
48
0
{
49
0
    if (path.is_null())
50
0
        return Error::from_errno(ENOENT);
51
52
0
    ByteString dep_path = path;
53
0
    char* real_path = realpath(dep_path.characters(), nullptr);
54
0
    ScopeGuard free_path = [real_path]() { free(real_path); };
55
56
0
    if (!real_path)
57
0
        return Error::from_syscall("realpath"sv, -errno);
58
59
0
    return ByteString { real_path, strlen(real_path) };
60
0
}
61
62
bool exists(StringView path)
63
0
{
64
0
    return !Core::System::stat(path).is_error();
65
0
}
66
67
bool exists(int fd)
68
0
{
69
0
    return !Core::System::fstat(fd).is_error();
70
0
}
71
72
bool is_device(StringView path)
73
0
{
74
0
    auto st_or_error = Core::System::stat(path);
75
0
    if (st_or_error.is_error())
76
0
        return false;
77
0
    auto st = st_or_error.release_value();
78
0
    return S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode);
79
0
}
80
81
bool is_device(int fd)
82
0
{
83
0
    auto st_or_error = Core::System::fstat(fd);
84
0
    if (st_or_error.is_error())
85
0
        return false;
86
0
    auto st = st_or_error.release_value();
87
0
    return S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode);
88
0
}
89
90
bool is_block_device(StringView path)
91
0
{
92
0
    auto st_or_error = Core::System::stat(path);
93
0
    if (st_or_error.is_error())
94
0
        return false;
95
0
    auto st = st_or_error.release_value();
96
0
    return S_ISBLK(st.st_mode);
97
0
}
98
99
bool is_block_device(int fd)
100
0
{
101
0
    auto st_or_error = Core::System::fstat(fd);
102
0
    if (st_or_error.is_error())
103
0
        return false;
104
0
    auto st = st_or_error.release_value();
105
0
    return S_ISBLK(st.st_mode);
106
0
}
107
108
bool is_char_device(StringView path)
109
0
{
110
0
    auto st_or_error = Core::System::stat(path);
111
0
    if (st_or_error.is_error())
112
0
        return false;
113
0
    auto st = st_or_error.release_value();
114
0
    return S_ISCHR(st.st_mode);
115
0
}
116
117
bool is_char_device(int fd)
118
0
{
119
0
    auto st_or_error = Core::System::fstat(fd);
120
0
    if (st_or_error.is_error())
121
0
        return false;
122
0
    auto st = st_or_error.release_value();
123
0
    return S_ISCHR(st.st_mode);
124
0
}
125
126
bool is_regular_file(StringView path)
127
0
{
128
0
    auto st_or_error = Core::System::stat(path);
129
0
    if (st_or_error.is_error())
130
0
        return false;
131
0
    auto st = st_or_error.release_value();
132
0
    return S_ISREG(st.st_mode);
133
0
}
134
135
bool is_regular_file(int fd)
136
0
{
137
0
    auto st_or_error = Core::System::fstat(fd);
138
0
    if (st_or_error.is_error())
139
0
        return false;
140
0
    auto st = st_or_error.release_value();
141
0
    return S_ISREG(st.st_mode);
142
0
}
143
144
bool is_directory(StringView path)
145
0
{
146
0
    auto st_or_error = Core::System::stat(path);
147
0
    if (st_or_error.is_error())
148
0
        return false;
149
0
    auto st = st_or_error.release_value();
150
0
    return S_ISDIR(st.st_mode);
151
0
}
152
153
bool is_directory(int fd)
154
0
{
155
0
    auto st_or_error = Core::System::fstat(fd);
156
0
    if (st_or_error.is_error())
157
0
        return false;
158
0
    auto st = st_or_error.release_value();
159
0
    return S_ISDIR(st.st_mode);
160
0
}
161
162
bool is_link(StringView path)
163
0
{
164
0
    auto st_or_error = Core::System::lstat(path);
165
0
    if (st_or_error.is_error())
166
0
        return false;
167
0
    auto st = st_or_error.release_value();
168
0
    return S_ISLNK(st.st_mode);
169
0
}
170
171
bool is_link(int fd)
172
0
{
173
0
    auto st_or_error = Core::System::fstat(fd);
174
0
    if (st_or_error.is_error())
175
0
        return false;
176
0
    auto st = st_or_error.release_value();
177
0
    return S_ISLNK(st.st_mode);
178
0
}
179
180
static ErrorOr<ByteString> get_duplicate_file_name(StringView path)
181
0
{
182
0
    int duplicate_count = 0;
183
0
    LexicalPath lexical_path(path);
184
0
    auto parent_path = LexicalPath::canonicalized_path(lexical_path.dirname());
185
0
    auto basename = lexical_path.basename();
186
0
    auto current_name = LexicalPath::join(parent_path, basename).string();
187
188
0
    while (exists(current_name)) {
189
0
        ++duplicate_count;
190
0
        current_name = LexicalPath::join(parent_path, ByteString::formatted("{} ({})", basename, duplicate_count)).string();
191
0
    }
192
193
0
    return current_name;
194
0
}
195
196
ErrorOr<void> copy_file(StringView destination_path, StringView source_path, struct stat const& source_stat, Core::File& source, PreserveMode preserve_mode)
197
0
{
198
0
    auto destination_or_error = Core::File::open(destination_path, Core::File::OpenMode::Write, 0666);
199
0
    if (destination_or_error.is_error()) {
200
0
        if (destination_or_error.error().code() != EISDIR)
201
0
            return destination_or_error.release_error();
202
203
0
        auto destination_dir_path = ByteString::formatted("{}/{}", destination_path, LexicalPath::basename(source_path));
204
0
        destination_or_error = TRY(Core::File::open(destination_dir_path, Core::File::OpenMode::Write, 0666));
205
0
    }
206
0
    auto destination = destination_or_error.release_value();
207
208
0
    if (source_stat.st_size > 0)
209
0
        TRY(destination->truncate(source_stat.st_size));
210
211
0
    ByteBuffer buffer = TRY(ByteBuffer::create_uninitialized(1 * MiB));
212
0
    while (!source.is_eof()) {
213
0
        auto bytes = TRY(source.read_some(buffer));
214
0
        TRY(destination->write_until_depleted(bytes));
215
0
    }
216
217
0
    auto my_umask = umask(0);
218
0
    umask(my_umask);
219
    // NOTE: We don't copy the set-uid and set-gid bits unless requested.
220
0
    if (!has_flag(preserve_mode, PreserveMode::Permissions))
221
0
        my_umask |= 06000;
222
223
0
    if (auto result = Core::System::fchmod(destination->fd(), source_stat.st_mode & ~my_umask); result.is_error())
224
0
        if (result.error().is_errno() && result.error().code() != ENOTSUP)
225
0
            return result.release_error();
226
227
0
    if (has_flag(preserve_mode, PreserveMode::Ownership))
228
0
        if (auto result = Core::System::fchown(destination->fd(), source_stat.st_uid, source_stat.st_gid); result.is_error())
229
0
            if (result.error().is_errno() && result.error().code() != ENOTSUP)
230
0
                return result.release_error();
231
232
0
    if (has_flag(preserve_mode, PreserveMode::Timestamps)) {
233
0
        struct timespec times[2] = {
234
#if defined(AK_OS_MACOS)
235
            source_stat.st_atimespec,
236
            source_stat.st_mtimespec,
237
#else
238
0
            source_stat.st_atim,
239
0
            source_stat.st_mtim,
240
0
#endif
241
0
        };
242
0
        TRY(Core::System::utimensat(AT_FDCWD, destination_path, times, 0));
243
0
    }
244
0
    return {};
245
0
}
246
247
ErrorOr<void> copy_directory(StringView destination_path, StringView source_path, struct stat const& source_stat, LinkMode link, PreserveMode preserve_mode)
248
0
{
249
0
    TRY(Core::System::mkdir(destination_path, 0755));
250
251
0
    auto source_rp = TRY(real_path(source_path));
252
0
    source_rp = ByteString::formatted("{}/", source_rp);
253
254
0
    auto destination_rp = TRY(real_path(destination_path));
255
0
    destination_rp = ByteString::formatted("{}/", destination_rp);
256
257
0
    if (!destination_rp.is_empty() && destination_rp.starts_with(source_rp))
258
0
        return Error::from_errno(EINVAL);
259
260
0
    Core::DirIterator di(source_path, Core::DirIterator::SkipParentAndBaseDir);
261
0
    if (di.has_error())
262
0
        return di.error();
263
264
0
    while (di.has_next()) {
265
0
        auto filename = di.next_path();
266
0
        TRY(copy_file_or_directory(
267
0
            ByteString::formatted("{}/{}", destination_path, filename),
268
0
            ByteString::formatted("{}/{}", source_path, filename),
269
0
            RecursionMode::Allowed, link, AddDuplicateFileMarker::Yes, preserve_mode));
270
0
    }
271
272
0
    auto my_umask = umask(0);
273
0
    umask(my_umask);
274
275
0
    if (auto result = Core::System::chmod(destination_path, source_stat.st_mode & ~my_umask); result.is_error())
276
0
        if (result.error().is_errno() && result.error().code() != ENOTSUP)
277
0
            return result.release_error();
278
279
0
    if (has_flag(preserve_mode, PreserveMode::Ownership))
280
0
        if (auto result = Core::System::chown(destination_path, source_stat.st_uid, source_stat.st_gid); result.is_error())
281
0
            if (result.error().is_errno() && result.error().code() != ENOTSUP)
282
0
                return result.release_error();
283
284
0
    if (has_flag(preserve_mode, PreserveMode::Timestamps)) {
285
0
        struct timespec times[2] = {
286
#if defined(AK_OS_MACOS)
287
            source_stat.st_atimespec,
288
            source_stat.st_mtimespec,
289
#else
290
0
            source_stat.st_atim,
291
0
            source_stat.st_mtim,
292
0
#endif
293
0
        };
294
0
        TRY(Core::System::utimensat(AT_FDCWD, destination_path, times, 0));
295
0
    }
296
297
0
    return {};
298
0
}
299
300
ErrorOr<void> copy_file_or_directory(StringView destination_path, StringView source_path, RecursionMode recursion_mode, LinkMode link_mode, AddDuplicateFileMarker add_duplicate_file_marker, PreserveMode preserve_mode)
301
0
{
302
0
    ByteString final_destination_path;
303
0
    if (add_duplicate_file_marker == AddDuplicateFileMarker::Yes)
304
0
        final_destination_path = TRY(get_duplicate_file_name(destination_path));
305
0
    else
306
0
        final_destination_path = destination_path;
307
308
0
    auto source = TRY(Core::File::open(source_path, Core::File::OpenMode::Read));
309
310
0
    auto source_stat = TRY(Core::System::fstat(source->fd()));
311
312
0
    if (is_directory(source_path)) {
313
0
        if (recursion_mode == RecursionMode::Disallowed) {
314
0
            return Error::from_errno(EISDIR);
315
0
        }
316
317
0
        return copy_directory(final_destination_path, source_path, source_stat);
318
0
    }
319
320
0
    if (link_mode == LinkMode::Allowed)
321
0
        return TRY(Core::System::link(source_path, final_destination_path));
322
323
0
    return copy_file(final_destination_path, source_path, source_stat, *source, preserve_mode);
324
0
}
325
326
ErrorOr<void> move_file(StringView destination_path, StringView source_path, PreserveMode preserve_mode)
327
0
{
328
0
    auto maybe_error = Core::System::rename(source_path, destination_path);
329
0
    if (!maybe_error.is_error())
330
0
        return {};
331
332
0
    if (!maybe_error.error().is_errno() || maybe_error.error().code() != EXDEV)
333
0
        return maybe_error;
334
335
0
    auto source = TRY(Core::File::open(source_path, Core::File::OpenMode::Read));
336
337
0
    auto source_stat = TRY(Core::System::fstat(source->fd()));
338
339
0
    TRY(copy_file(destination_path, source_path, source_stat, *source, preserve_mode));
340
341
0
    return Core::System::unlink(source_path);
342
0
}
343
344
ErrorOr<void> remove(StringView path, RecursionMode mode)
345
0
{
346
0
    if (is_directory(path) && mode == RecursionMode::Allowed) {
347
0
        auto di = Core::DirIterator(path, Core::DirIterator::SkipParentAndBaseDir);
348
0
        if (di.has_error())
349
0
            return di.error();
350
351
0
        while (di.has_next())
352
0
            TRY(remove(di.next_full_path(), RecursionMode::Allowed));
353
354
0
        TRY(Core::System::rmdir(path));
355
0
    } else {
356
0
        TRY(Core::System::unlink(path));
357
0
    }
358
359
0
    return {};
360
0
}
361
362
ErrorOr<off_t> size_from_stat(StringView path)
363
0
{
364
0
    auto st = TRY(Core::System::stat(path));
365
0
    return st.st_size;
366
0
}
367
368
ErrorOr<off_t> size_from_fstat(int fd)
369
0
{
370
0
    auto st = TRY(Core::System::fstat(fd));
371
0
    return st.st_size;
372
0
}
373
374
ErrorOr<off_t> block_device_size_from_ioctl(StringView path)
375
0
{
376
0
    if (!path.characters_without_null_termination())
377
0
        return Error::from_syscall("ioctl"sv, -EFAULT);
378
379
0
    ByteString path_string = path;
380
0
    int fd = open(path_string.characters(), O_RDONLY);
381
382
0
    if (fd < 0)
383
0
        return Error::from_errno(errno);
384
385
0
    off_t size = TRY(block_device_size_from_ioctl(fd));
386
387
0
    if (close(fd) != 0)
388
0
        return Error::from_errno(errno);
389
390
0
    return size;
391
0
}
392
393
ErrorOr<off_t> block_device_size_from_ioctl(int fd)
394
0
{
395
#if defined(AK_OS_SERENITY)
396
    u64 size = 0;
397
    TRY(Core::System::ioctl(fd, STORAGE_DEVICE_GET_SIZE, &size));
398
    return static_cast<off_t>(size);
399
#elif defined(AK_OS_MACOS)
400
    u64 block_count = 0;
401
    u32 block_size = 0;
402
    TRY(Core::System::ioctl(fd, DKIOCGETBLOCKCOUNT, &block_count));
403
    TRY(Core::System::ioctl(fd, DKIOCGETBLOCKSIZE, &block_size));
404
    return static_cast<off_t>(block_count * block_size);
405
#elif defined(AK_OS_FREEBSD) || defined(AK_OS_NETBSD)
406
    off_t size = 0;
407
    TRY(Core::System::ioctl(fd, DIOCGMEDIASIZE, &size));
408
    return size;
409
#elif defined(AK_OS_LINUX)
410
    u64 size = 0;
411
0
    TRY(Core::System::ioctl(fd, BLKGETSIZE64, &size));
412
0
    return static_cast<off_t>(size);
413
#else
414
    // FIXME: Add support for more platforms.
415
    (void)fd;
416
    return Error::from_string_literal("Platform does not support getting block device size");
417
#endif
418
0
}
419
420
bool can_delete_or_move(StringView path)
421
0
{
422
0
    VERIFY(!path.is_empty());
423
0
    auto directory = LexicalPath::dirname(path);
424
0
    auto directory_has_write_access = !Core::System::access(directory, W_OK).is_error();
425
0
    if (!directory_has_write_access)
426
0
        return false;
427
428
0
    auto stat_or_empty = [](StringView path) {
429
0
        auto stat_or_error = Core::System::stat(path);
430
0
        if (stat_or_error.is_error()) {
431
0
            struct stat stat {};
432
0
            return stat;
433
0
        }
434
0
        return stat_or_error.release_value();
435
0
    };
436
437
0
    auto directory_stat = stat_or_empty(directory);
438
0
    bool is_directory_sticky = (directory_stat.st_mode & S_ISVTX) != 0;
439
0
    if (!is_directory_sticky)
440
0
        return true;
441
442
    // Directory is sticky, only the file owner, directory owner, and root can modify (rename, remove) it.
443
0
    auto user_id = geteuid();
444
0
    return user_id == 0 || directory_stat.st_uid == user_id || stat_or_empty(path).st_uid == user_id;
445
0
}
446
447
ErrorOr<ByteString> read_link(StringView link_path)
448
0
{
449
0
    return Core::System::readlink(link_path);
450
0
}
451
452
ErrorOr<void> link_file(StringView destination_path, StringView source_path)
453
0
{
454
0
    return TRY(Core::System::symlink(source_path, TRY(get_duplicate_file_name(destination_path))));
455
0
}
456
457
bool looks_like_shared_library(StringView path)
458
0
{
459
0
    return path.ends_with(".so"sv) || path.contains(".so."sv);
460
0
}
461
462
}