Serenity Operating System
1/*
2 * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
3 * Copyright (c) 2021, sin-ack <sin-ack@protonmail.com>
4 *
5 * SPDX-License-Identifier: BSD-2-Clause
6 */
7
8#include <LibCore/File.h>
9#include <LibCore/System.h>
10#include <fcntl.h>
11#include <unistd.h>
12
13namespace Core {
14
15ErrorOr<NonnullOwnPtr<File>> File::open(StringView filename, OpenMode mode, mode_t permissions)
16{
17 auto file = TRY(adopt_nonnull_own_or_enomem(new (nothrow) File(mode)));
18 TRY(file->open_path(filename, permissions));
19 return file;
20}
21
22ErrorOr<NonnullOwnPtr<File>> File::adopt_fd(int fd, OpenMode mode, ShouldCloseFileDescriptor should_close_file_descriptor)
23{
24 if (fd < 0) {
25 return Error::from_errno(EBADF);
26 }
27
28 if (!has_any_flag(mode, OpenMode::ReadWrite)) {
29 dbgln("Core::File::adopt_fd: Attempting to adopt a file with neither Read nor Write specified in mode");
30 return Error::from_errno(EINVAL);
31 }
32
33 auto file = TRY(adopt_nonnull_own_or_enomem(new (nothrow) File(mode, should_close_file_descriptor)));
34 file->m_fd = fd;
35 return file;
36}
37
38ErrorOr<NonnullOwnPtr<File>> File::standard_input()
39{
40 return File::adopt_fd(STDIN_FILENO, OpenMode::Read, ShouldCloseFileDescriptor::No);
41}
42ErrorOr<NonnullOwnPtr<File>> File::standard_output()
43{
44 return File::adopt_fd(STDOUT_FILENO, OpenMode::Write, ShouldCloseFileDescriptor::No);
45}
46ErrorOr<NonnullOwnPtr<File>> File::standard_error()
47{
48 return File::adopt_fd(STDERR_FILENO, OpenMode::Write, ShouldCloseFileDescriptor::No);
49}
50
51ErrorOr<NonnullOwnPtr<File>> File::open_file_or_standard_stream(StringView filename, OpenMode mode)
52{
53 if (!filename.is_empty() && filename != "-"sv)
54 return File::open(filename, mode);
55
56 switch (mode) {
57 case OpenMode::Read:
58 return standard_input();
59 case OpenMode::Write:
60 return standard_output();
61 default:
62 VERIFY_NOT_REACHED();
63 }
64}
65
66int File::open_mode_to_options(OpenMode mode)
67{
68 int flags = 0;
69 if (has_flag(mode, OpenMode::ReadWrite)) {
70 flags |= O_RDWR | O_CREAT;
71 } else if (has_flag(mode, OpenMode::Read)) {
72 flags |= O_RDONLY;
73 } else if (has_flag(mode, OpenMode::Write)) {
74 flags |= O_WRONLY | O_CREAT;
75 bool should_truncate = !has_any_flag(mode, OpenMode::Append | OpenMode::MustBeNew);
76 if (should_truncate)
77 flags |= O_TRUNC;
78 }
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 if (!has_flag(mode, OpenMode::Nonblocking))
89 flags |= O_NONBLOCK;
90 return flags;
91}
92
93ErrorOr<void> File::open_path(StringView filename, mode_t permissions)
94{
95 VERIFY(m_fd == -1);
96 auto flags = open_mode_to_options(m_mode);
97
98 m_fd = TRY(System::open(filename, flags, permissions));
99 return {};
100}
101
102ErrorOr<Bytes> File::read_some(Bytes buffer)
103{
104 if (!has_flag(m_mode, OpenMode::Read)) {
105 // NOTE: POSIX says that if the fd is not open for reading, the call
106 // will return EBADF. Since we already know whether we can or
107 // can't read the file, let's avoid a syscall.
108 return Error::from_errno(EBADF);
109 }
110
111 ssize_t nread = TRY(System::read(m_fd, buffer));
112 m_last_read_was_eof = nread == 0;
113 return buffer.trim(nread);
114}
115
116ErrorOr<ByteBuffer> File::read_until_eof(size_t block_size)
117{
118 // Note: This is used as a heuristic, it's not valid for devices or virtual files.
119 auto const potential_file_size = TRY(System::fstat(m_fd)).st_size;
120
121 return read_until_eof_impl(block_size, potential_file_size);
122}
123
124ErrorOr<size_t> File::write_some(ReadonlyBytes buffer)
125{
126 if (!has_flag(m_mode, OpenMode::Write)) {
127 // NOTE: Same deal as Read.
128 return Error::from_errno(EBADF);
129 }
130
131 return TRY(System::write(m_fd, buffer));
132}
133
134bool File::is_eof() const { return m_last_read_was_eof; }
135bool File::is_open() const { return m_fd >= 0; }
136
137void File::close()
138{
139 if (!is_open()) {
140 return;
141 }
142
143 // NOTE: The closing of the file can be interrupted by a signal, in which
144 // case EINTR will be returned by the close syscall. So let's try closing
145 // the file until we aren't interrupted by rude signals. :^)
146 ErrorOr<void> result;
147 do {
148 result = System::close(m_fd);
149 } while (result.is_error() && result.error().code() == EINTR);
150
151 VERIFY(!result.is_error());
152 m_fd = -1;
153}
154
155ErrorOr<size_t> File::seek(i64 offset, SeekMode mode)
156{
157 int syscall_mode;
158 switch (mode) {
159 case SeekMode::SetPosition:
160 syscall_mode = SEEK_SET;
161 break;
162 case SeekMode::FromCurrentPosition:
163 syscall_mode = SEEK_CUR;
164 break;
165 case SeekMode::FromEndPosition:
166 syscall_mode = SEEK_END;
167 break;
168 default:
169 VERIFY_NOT_REACHED();
170 }
171
172 size_t seek_result = TRY(System::lseek(m_fd, offset, syscall_mode));
173 m_last_read_was_eof = false;
174 return seek_result;
175}
176
177ErrorOr<void> File::truncate(size_t length)
178{
179 if (length > static_cast<size_t>(NumericLimits<off_t>::max()))
180 return Error::from_string_literal("Length is larger than the maximum supported length");
181
182 return System::ftruncate(m_fd, length);
183}
184
185}