Serenity Operating System
1/*
2 * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include <AK/JsonArray.h>
8#include <AK/JsonObject.h>
9#include <AK/JsonValue.h>
10#include <LibCore/ArgsParser.h>
11#include <LibCore/DirIterator.h>
12#include <LibCore/File.h>
13#include <LibCore/System.h>
14#include <LibMain/Main.h>
15#include <fcntl.h>
16#include <stdio.h>
17#include <string.h>
18#include <unistd.h>
19
20static int parse_options(StringView options)
21{
22 int flags = 0;
23 Vector<StringView> parts = options.split_view(',');
24 for (auto& part : parts) {
25 if (part == "defaults")
26 continue;
27 else if (part == "nodev")
28 flags |= MS_NODEV;
29 else if (part == "noexec")
30 flags |= MS_NOEXEC;
31 else if (part == "nosuid")
32 flags |= MS_NOSUID;
33 else if (part == "bind")
34 flags |= MS_BIND;
35 else if (part == "ro")
36 flags |= MS_RDONLY;
37 else if (part == "remount")
38 flags |= MS_REMOUNT;
39 else if (part == "wxallowed")
40 flags |= MS_WXALLOWED;
41 else if (part == "axallowed")
42 flags |= MS_AXALLOWED;
43 else if (part == "noregular")
44 flags |= MS_NOREGULAR;
45 else
46 warnln("Ignoring invalid option: {}", part);
47 }
48 return flags;
49}
50
51static bool is_source_none(StringView source)
52{
53 return source == "none"sv;
54}
55
56static ErrorOr<int> get_source_fd(StringView source)
57{
58 if (is_source_none(source))
59 return -1;
60 auto fd_or_error = Core::System::open(source, O_RDWR);
61 if (fd_or_error.is_error())
62 fd_or_error = Core::System::open(source, O_RDONLY);
63 return fd_or_error;
64}
65
66static bool mount_by_line(DeprecatedString const& line)
67{
68 // Skip comments and blank lines.
69 if (line.is_empty() || line.starts_with('#'))
70 return true;
71
72 Vector<DeprecatedString> parts = line.split('\t');
73 if (parts.size() < 3) {
74 warnln("Invalid fstab entry: {}", line);
75 return false;
76 }
77
78 auto mountpoint = parts[1];
79 auto fstype = parts[2];
80 int flags = parts.size() >= 4 ? parse_options(parts[3]) : 0;
81
82 if (mountpoint == "/") {
83 dbgln("Skipping mounting root");
84 return true;
85 }
86
87 auto filename = parts[0];
88
89 auto fd_or_error = get_source_fd(filename);
90 if (fd_or_error.is_error()) {
91 outln("{}", fd_or_error.release_error());
92 return false;
93 }
94 auto const fd = fd_or_error.release_value();
95
96 dbgln("Mounting {} ({}) on {}", filename, fstype, mountpoint);
97
98 auto error_or_void = Core::System::mount(fd, mountpoint, fstype, flags);
99 if (error_or_void.is_error()) {
100 warnln("Failed to mount {} (FD: {}) ({}) on {}: {}", filename, fd, fstype, mountpoint, error_or_void.error());
101 return false;
102 }
103
104 return true;
105}
106
107static ErrorOr<void> mount_all()
108{
109 // Mount all filesystems listed in /etc/fstab.
110 dbgln("Mounting all filesystems...");
111 Array<u8, PAGE_SIZE> buffer;
112
113 bool all_ok = true;
114 auto process_fstab_entries = [&](StringView path) -> ErrorOr<void> {
115 auto file_unbuffered = TRY(Core::File::open(path, Core::File::OpenMode::Read));
116 auto file = TRY(Core::BufferedFile::create(move(file_unbuffered)));
117
118 while (TRY(file->can_read_line())) {
119 auto line = TRY(file->read_line(buffer));
120
121 if (!mount_by_line(line))
122 all_ok = false;
123 }
124 return {};
125 };
126
127 if (auto result = process_fstab_entries("/etc/fstab"sv); result.is_error())
128 dbgln("Failed to read '/etc/fstab': {}", result.error());
129
130 auto fstab_directory_iterator = Core::DirIterator("/etc/fstab.d", Core::DirIterator::SkipDots);
131
132 if (fstab_directory_iterator.has_error() && fstab_directory_iterator.error().code() != ENOENT) {
133 dbgln("Failed to open /etc/fstab.d: {}", fstab_directory_iterator.error());
134 } else if (!fstab_directory_iterator.has_error()) {
135 while (fstab_directory_iterator.has_next()) {
136 auto path = fstab_directory_iterator.next_full_path();
137 if (auto result = process_fstab_entries(path); result.is_error())
138 dbgln("Failed to read '{}': {}", path, result.error());
139 }
140 }
141
142 if (all_ok)
143 return {};
144
145 return Error::from_string_literal("One or more errors occurred. Please verify earlier output.");
146}
147
148static ErrorOr<void> print_mounts()
149{
150 // Output info about currently mounted filesystems.
151 auto df = TRY(Core::File::open("/sys/kernel/df"sv, Core::File::OpenMode::Read));
152
153 auto content = TRY(df->read_until_eof());
154 auto json = TRY(JsonValue::from_string(content));
155
156 json.as_array().for_each([](auto& value) {
157 auto& fs_object = value.as_object();
158 auto class_name = fs_object.get_deprecated_string("class_name"sv).value_or({});
159 auto mount_point = fs_object.get_deprecated_string("mount_point"sv).value_or({});
160 auto source = fs_object.get_deprecated_string("source"sv).value_or("none");
161 auto readonly = fs_object.get_bool("readonly"sv).value_or(false);
162 auto mount_flags = fs_object.get_u32("mount_flags"sv).value_or(0);
163
164 out("{} on {} type {} (", source, mount_point, class_name);
165
166 if (readonly || mount_flags & MS_RDONLY)
167 out("ro");
168 else
169 out("rw");
170
171 if (mount_flags & MS_NODEV)
172 out(",nodev");
173 if (mount_flags & MS_NOREGULAR)
174 out(",noregular");
175 if (mount_flags & MS_NOEXEC)
176 out(",noexec");
177 if (mount_flags & MS_NOSUID)
178 out(",nosuid");
179 if (mount_flags & MS_BIND)
180 out(",bind");
181 if (mount_flags & MS_WXALLOWED)
182 out(",wxallowed");
183 if (mount_flags & MS_AXALLOWED)
184 out(",axallowed");
185
186 outln(")");
187 });
188
189 return {};
190}
191
192ErrorOr<int> serenity_main(Main::Arguments arguments)
193{
194 StringView source;
195 StringView mountpoint;
196 StringView fs_type;
197 StringView options;
198 bool should_mount_all = false;
199
200 Core::ArgsParser args_parser;
201 args_parser.add_positional_argument(source, "Source path", "source", Core::ArgsParser::Required::No);
202 args_parser.add_positional_argument(mountpoint, "Mount point", "mountpoint", Core::ArgsParser::Required::No);
203 args_parser.add_option(fs_type, "File system type", nullptr, 't', "fstype");
204 args_parser.add_option(options, "Mount options", nullptr, 'o', "options");
205 args_parser.add_option(should_mount_all, "Mount all file systems listed in /etc/fstab and /etc/fstab.d/*", nullptr, 'a');
206 args_parser.parse(arguments);
207
208 if (should_mount_all) {
209 TRY(mount_all());
210 return 0;
211 }
212
213 if (source.is_empty() && mountpoint.is_empty()) {
214 TRY(print_mounts());
215 return 0;
216 }
217
218 if (!source.is_empty() && !mountpoint.is_empty()) {
219 if (fs_type.is_empty())
220 fs_type = "ext2"sv;
221 int flags = !options.is_empty() ? parse_options(options) : 0;
222
223 int const fd = TRY(get_source_fd(source));
224
225 TRY(Core::System::mount(fd, mountpoint, fs_type, flags));
226
227 return 0;
228 }
229
230 args_parser.print_usage(stderr, arguments.strings[0]);
231
232 return 1;
233}