Serenity Operating System
1/*
2 * Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include <AK/LexicalPath.h>
8#include <AK/Platform.h>
9#include <AK/ScopeGuard.h>
10#include <LibCore/DeprecatedFile.h>
11#include <LibCore/DirIterator.h>
12#include <LibCore/System.h>
13#include <errno.h>
14#include <fcntl.h>
15#include <libgen.h>
16#include <stdio.h>
17#include <string.h>
18#include <sys/stat.h>
19#include <unistd.h>
20#include <utime.h>
21
22#ifdef AK_OS_SERENITY
23# include <serenity.h>
24#endif
25
26// On Linux distros that use glibc `basename` is defined as a macro that expands to `__xpg_basename`, so we undefine it
27#if defined(AK_OS_LINUX) && defined(basename)
28# undef basename
29#endif
30
31namespace Core {
32
33ErrorOr<NonnullRefPtr<DeprecatedFile>> DeprecatedFile::open(DeprecatedString filename, OpenMode mode, mode_t permissions)
34{
35 auto file = DeprecatedFile::construct(move(filename));
36 if (!file->open_impl(mode, permissions))
37 return Error::from_errno(file->error());
38 return file;
39}
40
41DeprecatedFile::DeprecatedFile(DeprecatedString filename, Object* parent)
42 : IODevice(parent)
43 , m_filename(move(filename))
44{
45}
46
47DeprecatedFile::~DeprecatedFile()
48{
49 if (m_should_close_file_descriptor == ShouldCloseFileDescriptor::Yes && mode() != OpenMode::NotOpen)
50 close();
51}
52
53bool DeprecatedFile::open(int fd, OpenMode mode, ShouldCloseFileDescriptor should_close)
54{
55 set_fd(fd);
56 set_mode(mode);
57 m_should_close_file_descriptor = should_close;
58 return true;
59}
60
61bool DeprecatedFile::open(OpenMode mode)
62{
63 return open_impl(mode, 0666);
64}
65
66bool DeprecatedFile::open_impl(OpenMode mode, mode_t permissions)
67{
68 VERIFY(!m_filename.is_null());
69 int flags = 0;
70 if (has_flag(mode, OpenMode::ReadOnly) && has_flag(mode, OpenMode::WriteOnly)) {
71 flags |= O_RDWR | O_CREAT;
72 } else if (has_flag(mode, OpenMode::ReadOnly)) {
73 flags |= O_RDONLY;
74 } else if (has_flag(mode, OpenMode::WriteOnly)) {
75 flags |= O_WRONLY | O_CREAT;
76 bool should_truncate = !(has_flag(mode, OpenMode::Append) || has_flag(mode, OpenMode::MustBeNew));
77 if (should_truncate)
78 flags |= O_TRUNC;
79 }
80 if (has_flag(mode, OpenMode::Append))
81 flags |= O_APPEND;
82 if (has_flag(mode, OpenMode::Truncate))
83 flags |= O_TRUNC;
84 if (has_flag(mode, OpenMode::MustBeNew))
85 flags |= O_EXCL;
86 if (!has_flag(mode, OpenMode::KeepOnExec))
87 flags |= O_CLOEXEC;
88 int fd = ::open(m_filename.characters(), flags, permissions);
89 if (fd < 0) {
90 set_error(errno);
91 return false;
92 }
93
94 set_fd(fd);
95 set_mode(mode);
96 return true;
97}
98
99int DeprecatedFile::leak_fd()
100{
101 m_should_close_file_descriptor = ShouldCloseFileDescriptor::No;
102 return fd();
103}
104
105bool DeprecatedFile::is_device() const
106{
107 return is_device(fd());
108}
109
110bool DeprecatedFile::is_device(DeprecatedString const& filename)
111{
112 struct stat st;
113 if (stat(filename.characters(), &st) < 0)
114 return false;
115 return S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode);
116}
117
118bool DeprecatedFile::is_device(int fd)
119{
120 struct stat st;
121 if (fstat(fd, &st) < 0)
122 return false;
123 return S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode);
124}
125
126bool DeprecatedFile::is_block_device() const
127{
128 struct stat stat;
129 if (fstat(fd(), &stat) < 0)
130 return false;
131 return S_ISBLK(stat.st_mode);
132}
133
134bool DeprecatedFile::is_block_device(DeprecatedString const& filename)
135{
136 struct stat st;
137 if (stat(filename.characters(), &st) < 0)
138 return false;
139 return S_ISBLK(st.st_mode);
140}
141
142bool DeprecatedFile::is_char_device() const
143{
144 struct stat stat;
145 if (fstat(fd(), &stat) < 0)
146 return false;
147 return S_ISCHR(stat.st_mode);
148}
149
150bool DeprecatedFile::is_char_device(DeprecatedString const& filename)
151{
152 struct stat st;
153 if (stat(filename.characters(), &st) < 0)
154 return false;
155 return S_ISCHR(st.st_mode);
156}
157
158bool DeprecatedFile::is_directory() const
159{
160 return is_directory(fd());
161}
162
163bool DeprecatedFile::is_directory(DeprecatedString const& filename)
164{
165 struct stat st;
166 if (stat(filename.characters(), &st) < 0)
167 return false;
168 return S_ISDIR(st.st_mode);
169}
170
171bool DeprecatedFile::is_directory(int fd)
172{
173 struct stat st;
174 if (fstat(fd, &st) < 0)
175 return false;
176 return S_ISDIR(st.st_mode);
177}
178
179bool DeprecatedFile::is_link() const
180{
181 struct stat stat;
182 if (fstat(fd(), &stat) < 0)
183 return false;
184 return S_ISLNK(stat.st_mode);
185}
186
187bool DeprecatedFile::is_link(DeprecatedString const& filename)
188{
189 struct stat st;
190 if (lstat(filename.characters(), &st) < 0)
191 return false;
192 return S_ISLNK(st.st_mode);
193}
194
195bool DeprecatedFile::looks_like_shared_library() const
196{
197 return DeprecatedFile::looks_like_shared_library(m_filename);
198}
199
200bool DeprecatedFile::looks_like_shared_library(DeprecatedString const& filename)
201{
202 return filename.ends_with(".so"sv) || filename.contains(".so."sv);
203}
204
205bool DeprecatedFile::can_delete_or_move(StringView path)
206{
207 VERIFY(!path.is_empty());
208 auto directory = LexicalPath::dirname(path);
209 auto directory_has_write_access = !Core::System::access(directory, W_OK).is_error();
210 if (!directory_has_write_access)
211 return false;
212
213 auto stat_or_empty = [](StringView path) {
214 auto stat_or_error = Core::System::stat(path);
215 if (stat_or_error.is_error()) {
216 struct stat stat { };
217 return stat;
218 }
219 return stat_or_error.release_value();
220 };
221
222 auto directory_stat = stat_or_empty(directory);
223 bool is_directory_sticky = directory_stat.st_mode & S_ISVTX;
224 if (!is_directory_sticky)
225 return true;
226
227 // Directory is sticky, only the file owner, directory owner, and root can modify (rename, remove) it.
228 auto user_id = geteuid();
229 return user_id == 0 || directory_stat.st_uid == user_id || stat_or_empty(path).st_uid == user_id;
230}
231
232bool DeprecatedFile::exists(StringView filename)
233{
234 return !Core::System::stat(filename).is_error();
235}
236
237ErrorOr<size_t> DeprecatedFile::size(DeprecatedString const& filename)
238{
239 struct stat st;
240 if (stat(filename.characters(), &st) < 0)
241 return Error::from_errno(errno);
242 return st.st_size;
243}
244
245DeprecatedString DeprecatedFile::real_path_for(DeprecatedString const& filename)
246{
247 if (filename.is_null())
248 return {};
249 auto* path = realpath(filename.characters(), nullptr);
250 DeprecatedString real_path(path);
251 free(path);
252 return real_path;
253}
254
255DeprecatedString DeprecatedFile::current_working_directory()
256{
257 char* cwd = getcwd(nullptr, 0);
258 if (!cwd) {
259 perror("getcwd");
260 return {};
261 }
262
263 auto cwd_as_string = DeprecatedString(cwd);
264 free(cwd);
265
266 return cwd_as_string;
267}
268
269DeprecatedString DeprecatedFile::absolute_path(DeprecatedString const& path)
270{
271 if (DeprecatedFile::exists(path))
272 return DeprecatedFile::real_path_for(path);
273
274 if (path.starts_with("/"sv))
275 return LexicalPath::canonicalized_path(path);
276
277 auto working_directory = DeprecatedFile::current_working_directory();
278 auto full_path = LexicalPath::join(working_directory, path);
279
280 return LexicalPath::canonicalized_path(full_path.string());
281}
282
283#ifdef AK_OS_SERENITY
284
285ErrorOr<DeprecatedString> DeprecatedFile::read_link(DeprecatedString const& link_path)
286{
287 // First, try using a 64-byte buffer, that ought to be enough for anybody.
288 char small_buffer[64];
289
290 int rc = serenity_readlink(link_path.characters(), link_path.length(), small_buffer, sizeof(small_buffer));
291 if (rc < 0)
292 return Error::from_errno(errno);
293
294 size_t size = rc;
295 // If the call was successful, the syscall (unlike the LibC wrapper)
296 // returns the full size of the link. Let's see if our small buffer
297 // was enough to read the whole link.
298 if (size <= sizeof(small_buffer))
299 return DeprecatedString { small_buffer, size };
300 // Nope, but at least now we know the right size.
301 char* large_buffer_ptr;
302 auto large_buffer = StringImpl::create_uninitialized(size, large_buffer_ptr);
303
304 rc = serenity_readlink(link_path.characters(), link_path.length(), large_buffer_ptr, size);
305 if (rc < 0)
306 return Error::from_errno(errno);
307
308 size_t new_size = rc;
309 if (new_size == size)
310 return { *large_buffer };
311
312 // If we're here, the symlink has changed while we were looking at it.
313 // If it became shorter, our buffer is valid, we just have to trim it a bit.
314 if (new_size < size)
315 return DeprecatedString { large_buffer_ptr, new_size };
316 // Otherwise, here's not much we can do, unless we want to loop endlessly
317 // in this case. Let's leave it up to the caller whether to loop.
318 errno = EAGAIN;
319 return Error::from_errno(errno);
320}
321
322#else
323
324// This is a sad version for other systems. It has to always make a copy of the
325// link path, and to always make two syscalls to get the right size first.
326ErrorOr<DeprecatedString> DeprecatedFile::read_link(DeprecatedString const& link_path)
327{
328 struct stat statbuf = {};
329 int rc = lstat(link_path.characters(), &statbuf);
330 if (rc < 0)
331 return Error::from_errno(errno);
332 char* buffer_ptr;
333 auto buffer = StringImpl::create_uninitialized(statbuf.st_size, buffer_ptr);
334 if (readlink(link_path.characters(), buffer_ptr, statbuf.st_size) < 0)
335 return Error::from_errno(errno);
336 // (See above.)
337 if (rc == statbuf.st_size)
338 return { *buffer };
339 return DeprecatedString { buffer_ptr, (size_t)rc };
340}
341
342#endif
343
344static RefPtr<DeprecatedFile> stdin_file;
345static RefPtr<DeprecatedFile> stdout_file;
346static RefPtr<DeprecatedFile> stderr_file;
347
348NonnullRefPtr<DeprecatedFile> DeprecatedFile::standard_input()
349{
350 if (!stdin_file) {
351 stdin_file = DeprecatedFile::construct();
352 stdin_file->open(STDIN_FILENO, OpenMode::ReadOnly, ShouldCloseFileDescriptor::No);
353 }
354 return *stdin_file;
355}
356
357NonnullRefPtr<DeprecatedFile> DeprecatedFile::standard_output()
358{
359 if (!stdout_file) {
360 stdout_file = DeprecatedFile::construct();
361 stdout_file->open(STDOUT_FILENO, OpenMode::WriteOnly, ShouldCloseFileDescriptor::No);
362 }
363 return *stdout_file;
364}
365
366NonnullRefPtr<DeprecatedFile> DeprecatedFile::standard_error()
367{
368 if (!stderr_file) {
369 stderr_file = DeprecatedFile::construct();
370 stderr_file->open(STDERR_FILENO, OpenMode::WriteOnly, ShouldCloseFileDescriptor::No);
371 }
372 return *stderr_file;
373}
374
375static DeprecatedString get_duplicate_name(DeprecatedString const& path, int duplicate_count)
376{
377 if (duplicate_count == 0) {
378 return path;
379 }
380 LexicalPath lexical_path(path);
381 StringBuilder duplicated_name;
382 duplicated_name.append('/');
383 auto& parts = lexical_path.parts_view();
384 for (size_t i = 0; i < parts.size() - 1; ++i) {
385 duplicated_name.appendff("{}/", parts[i]);
386 }
387 auto prev_duplicate_tag = DeprecatedString::formatted("({})", duplicate_count);
388 auto title = lexical_path.title();
389 if (title.ends_with(prev_duplicate_tag)) {
390 // remove the previous duplicate tag "(n)" so we can add a new tag.
391 title = title.substring_view(0, title.length() - prev_duplicate_tag.length());
392 }
393 duplicated_name.appendff("{} ({})", title, duplicate_count);
394 if (!lexical_path.extension().is_empty()) {
395 duplicated_name.appendff(".{}", lexical_path.extension());
396 }
397 return duplicated_name.to_deprecated_string();
398}
399
400ErrorOr<void, DeprecatedFile::CopyError> DeprecatedFile::copy_file_or_directory(DeprecatedString const& dst_path, DeprecatedString const& src_path, RecursionMode recursion_mode, LinkMode link_mode, AddDuplicateFileMarker add_duplicate_file_marker, PreserveMode preserve_mode)
401{
402 if (add_duplicate_file_marker == AddDuplicateFileMarker::Yes) {
403 int duplicate_count = 0;
404 while (access(get_duplicate_name(dst_path, duplicate_count).characters(), F_OK) == 0) {
405 ++duplicate_count;
406 }
407 if (duplicate_count != 0) {
408 return copy_file_or_directory(get_duplicate_name(dst_path, duplicate_count), src_path, RecursionMode::Allowed, LinkMode::Disallowed, AddDuplicateFileMarker::Yes, preserve_mode);
409 }
410 }
411
412 auto source_or_error = DeprecatedFile::open(src_path, OpenMode::ReadOnly);
413 if (source_or_error.is_error())
414 return CopyError { errno, false };
415
416 auto& source = *source_or_error.value();
417
418 struct stat src_stat;
419 if (fstat(source.fd(), &src_stat) < 0)
420 return CopyError { errno, false };
421
422 if (source.is_directory()) {
423 if (recursion_mode == RecursionMode::Disallowed)
424 return CopyError { errno, true };
425 return copy_directory(dst_path, src_path, src_stat);
426 }
427
428 if (link_mode == LinkMode::Allowed) {
429 if (link(src_path.characters(), dst_path.characters()) < 0)
430 return CopyError { errno, false };
431
432 return {};
433 }
434
435 return copy_file(dst_path, src_stat, source, preserve_mode);
436}
437
438ErrorOr<void, DeprecatedFile::CopyError> DeprecatedFile::copy_file(DeprecatedString const& dst_path, struct stat const& src_stat, DeprecatedFile& source, PreserveMode preserve_mode)
439{
440 int dst_fd = creat(dst_path.characters(), 0666);
441 if (dst_fd < 0) {
442 if (errno != EISDIR)
443 return CopyError { errno, false };
444
445 auto dst_dir_path = DeprecatedString::formatted("{}/{}", dst_path, LexicalPath::basename(source.filename()));
446 dst_fd = creat(dst_dir_path.characters(), 0666);
447 if (dst_fd < 0)
448 return CopyError { errno, false };
449 }
450
451 ScopeGuard close_fd_guard([dst_fd]() { ::close(dst_fd); });
452
453 if (src_stat.st_size > 0) {
454 if (ftruncate(dst_fd, src_stat.st_size) < 0)
455 return CopyError { errno, false };
456 }
457
458 for (;;) {
459 char buffer[32768];
460 ssize_t nread = ::read(source.fd(), buffer, sizeof(buffer));
461 if (nread < 0) {
462 return CopyError { errno, false };
463 }
464 if (nread == 0)
465 break;
466 ssize_t remaining_to_write = nread;
467 char* bufptr = buffer;
468 while (remaining_to_write) {
469 ssize_t nwritten = ::write(dst_fd, bufptr, remaining_to_write);
470 if (nwritten < 0)
471 return CopyError { errno, false };
472
473 VERIFY(nwritten > 0);
474 remaining_to_write -= nwritten;
475 bufptr += nwritten;
476 }
477 }
478
479 auto my_umask = umask(0);
480 umask(my_umask);
481 // NOTE: We don't copy the set-uid and set-gid bits unless requested.
482 if (!has_flag(preserve_mode, PreserveMode::Permissions))
483 my_umask |= 06000;
484
485 if (fchmod(dst_fd, src_stat.st_mode & ~my_umask) < 0)
486 return CopyError { errno, false };
487
488 if (has_flag(preserve_mode, PreserveMode::Ownership)) {
489 if (fchown(dst_fd, src_stat.st_uid, src_stat.st_gid) < 0)
490 return CopyError { errno, false };
491 }
492
493 if (has_flag(preserve_mode, PreserveMode::Timestamps)) {
494 struct timespec times[2] = {
495#ifdef AK_OS_MACOS
496 src_stat.st_atimespec,
497 src_stat.st_mtimespec,
498#else
499 src_stat.st_atim,
500 src_stat.st_mtim,
501#endif
502 };
503 if (utimensat(AT_FDCWD, dst_path.characters(), times, 0) < 0)
504 return CopyError { errno, false };
505 }
506
507 return {};
508}
509
510ErrorOr<void, DeprecatedFile::CopyError> DeprecatedFile::copy_directory(DeprecatedString const& dst_path, DeprecatedString const& src_path, struct stat const& src_stat, LinkMode link, PreserveMode preserve_mode)
511{
512 if (mkdir(dst_path.characters(), 0755) < 0)
513 return CopyError { errno, false };
514
515 DeprecatedString src_rp = DeprecatedFile::real_path_for(src_path);
516 src_rp = DeprecatedString::formatted("{}/", src_rp);
517 DeprecatedString dst_rp = DeprecatedFile::real_path_for(dst_path);
518 dst_rp = DeprecatedString::formatted("{}/", dst_rp);
519
520 if (!dst_rp.is_empty() && dst_rp.starts_with(src_rp))
521 return CopyError { errno, false };
522
523 DirIterator di(src_path, DirIterator::SkipParentAndBaseDir);
524 if (di.has_error())
525 return CopyError { errno, false };
526
527 while (di.has_next()) {
528 DeprecatedString filename = di.next_path();
529 auto result = copy_file_or_directory(
530 DeprecatedString::formatted("{}/{}", dst_path, filename),
531 DeprecatedString::formatted("{}/{}", src_path, filename),
532 RecursionMode::Allowed, link, AddDuplicateFileMarker::Yes, preserve_mode);
533 if (result.is_error())
534 return result.release_error();
535 }
536
537 auto my_umask = umask(0);
538 umask(my_umask);
539
540 if (chmod(dst_path.characters(), src_stat.st_mode & ~my_umask) < 0)
541 return CopyError { errno, false };
542
543 if (has_flag(preserve_mode, PreserveMode::Ownership)) {
544 if (chown(dst_path.characters(), src_stat.st_uid, src_stat.st_gid) < 0)
545 return CopyError { errno, false };
546 }
547
548 if (has_flag(preserve_mode, PreserveMode::Timestamps)) {
549 struct timespec times[2] = {
550#ifdef AK_OS_MACOS
551 src_stat.st_atimespec,
552 src_stat.st_mtimespec,
553#else
554 src_stat.st_atim,
555 src_stat.st_mtim,
556#endif
557 };
558 if (utimensat(AT_FDCWD, dst_path.characters(), times, 0) < 0)
559 return CopyError { errno, false };
560 }
561
562 return {};
563}
564
565ErrorOr<void> DeprecatedFile::link_file(DeprecatedString const& dst_path, DeprecatedString const& src_path)
566{
567 int duplicate_count = 0;
568 while (access(get_duplicate_name(dst_path, duplicate_count).characters(), F_OK) == 0) {
569 ++duplicate_count;
570 }
571 if (duplicate_count != 0) {
572 return link_file(get_duplicate_name(dst_path, duplicate_count), src_path);
573 }
574 if (symlink(src_path.characters(), dst_path.characters()) < 0)
575 return Error::from_errno(errno);
576 return {};
577}
578
579ErrorOr<void> DeprecatedFile::remove(StringView path, RecursionMode mode)
580{
581 auto path_stat = TRY(Core::System::lstat(path));
582
583 if (S_ISDIR(path_stat.st_mode) && mode == RecursionMode::Allowed) {
584 auto di = DirIterator(path, DirIterator::SkipParentAndBaseDir);
585 if (di.has_error())
586 return di.error();
587
588 while (di.has_next()) {
589 TRY(remove(di.next_full_path(), RecursionMode::Allowed));
590 }
591
592 TRY(Core::System::rmdir(path));
593 } else {
594 TRY(Core::System::unlink(path));
595 }
596
597 return {};
598}
599
600Optional<DeprecatedString> DeprecatedFile::resolve_executable_from_environment(StringView filename)
601{
602 if (filename.is_empty())
603 return {};
604
605 // Paths that aren't just a file name generally count as already resolved.
606 if (filename.contains('/')) {
607 if (access(DeprecatedString { filename }.characters(), X_OK) != 0)
608 return {};
609
610 return filename;
611 }
612
613 auto const* path_str = getenv("PATH");
614 StringView path;
615 if (path_str)
616 path = { path_str, strlen(path_str) };
617 if (path.is_empty())
618 path = DEFAULT_PATH_SV;
619
620 auto directories = path.split_view(':');
621
622 for (auto directory : directories) {
623 auto file = DeprecatedString::formatted("{}/{}", directory, filename);
624
625 if (access(file.characters(), X_OK) == 0)
626 return file;
627 }
628
629 return {};
630};
631
632}