Serenity Operating System
1/*
2 * Copyright (c) 2021, Xavier Defrang <xavier.defrang@gmail.com>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include <AK/Assertions.h>
8#include <AK/CharacterTypes.h>
9#include <AK/StringUtils.h>
10
11#include <LibCore/FilePermissionsMask.h>
12
13namespace Core {
14
15enum State {
16 Classes,
17 Mode
18};
19
20enum ClassFlag {
21 Other = 1,
22 Group = 2,
23 User = 4,
24 All = 7
25};
26
27enum Operation {
28 Add,
29 Remove,
30 Assign,
31};
32
33ErrorOr<FilePermissionsMask> FilePermissionsMask::parse(StringView string)
34{
35 return (!string.is_empty() && is_ascii_digit(string[0]))
36 ? from_numeric_notation(string)
37 : from_symbolic_notation(string);
38}
39
40ErrorOr<FilePermissionsMask> FilePermissionsMask::from_numeric_notation(StringView string)
41{
42 string = string.trim_whitespace();
43 mode_t mode = AK::StringUtils::convert_to_uint_from_octal<u16>(string, TrimWhitespace::No).value_or(010000);
44 if (mode > 07777)
45 return Error::from_string_literal("invalid octal representation");
46
47 FilePermissionsMask mask;
48 mask.assign_permissions(mode);
49
50 // For compatibility purposes, just clear the special mode bits if we explicitly passed a 4-character mode.
51 if (string.length() >= 4)
52 mask.remove_permissions(07000);
53
54 return mask;
55}
56
57ErrorOr<FilePermissionsMask> FilePermissionsMask::from_symbolic_notation(StringView string)
58{
59 auto mask = FilePermissionsMask();
60
61 u8 state = State::Classes;
62 u8 classes = 0;
63 u8 operation = 0;
64
65 for (auto ch : string) {
66 switch (state) {
67 case State::Classes: {
68 // zero or more [ugoa] terminated by one operator [+-=]
69 if (ch == 'u')
70 classes |= ClassFlag::User;
71 else if (ch == 'g')
72 classes |= ClassFlag::Group;
73 else if (ch == 'o')
74 classes |= ClassFlag::Other;
75 else if (ch == 'a')
76 classes = ClassFlag::All;
77 else {
78 if (ch == '+')
79 operation = Operation::Add;
80 else if (ch == '-')
81 operation = Operation::Remove;
82 else if (ch == '=')
83 operation = Operation::Assign;
84 else if (classes == 0)
85 return Error::from_string_literal("invalid class: expected 'u', 'g', 'o' or 'a'");
86 else
87 return Error::from_string_literal("invalid operation: expected '+', '-' or '='");
88
89 // if an operation was specified without a class, assume all
90 if (classes == 0)
91 classes = ClassFlag::All;
92
93 state = State::Mode;
94 }
95
96 break;
97 }
98
99 case State::Mode: {
100 // one or more [rwx] terminated by a comma
101
102 // End of mode part, expect class next
103 if (ch == ',') {
104 state = State::Classes;
105 classes = operation = 0;
106 continue;
107 }
108
109 mode_t write_bits = 0;
110 bool apply_to_directories_and_executables_only = false;
111
112 switch (ch) {
113 case 'r':
114 write_bits = 4;
115 break;
116 case 'w':
117 write_bits = 2;
118 break;
119 case 'x':
120 write_bits = 1;
121 break;
122 case 'X':
123 write_bits = 1;
124 apply_to_directories_and_executables_only = true;
125 break;
126 default:
127 return Error::from_string_literal("invalid symbolic permission: expected 'r', 'w' or 'x'");
128 }
129
130 mode_t clear_bits = operation == Operation::Assign ? 7 : write_bits;
131
132 FilePermissionsMask& edit_mask = apply_to_directories_and_executables_only ? mask.directory_or_executable_mask() : mask;
133
134 // Update masks one class at a time in other, group, user order
135 for (auto cls = classes; cls != 0; cls >>= 1) {
136 if (cls & 1) {
137 if (operation == Operation::Add || operation == Operation::Assign)
138 edit_mask.add_permissions(write_bits);
139 if (operation == Operation::Remove || operation == Operation::Assign)
140 edit_mask.remove_permissions(clear_bits);
141 }
142 write_bits <<= 3;
143 clear_bits <<= 3;
144 }
145
146 break;
147 }
148
149 default:
150 VERIFY_NOT_REACHED();
151 }
152 }
153
154 return mask;
155}
156
157FilePermissionsMask& FilePermissionsMask::assign_permissions(mode_t mode)
158{
159 m_write_mask = mode;
160 m_clear_mask = 0777;
161 return *this;
162}
163
164FilePermissionsMask& FilePermissionsMask::add_permissions(mode_t mode)
165{
166 m_write_mask |= mode;
167 return *this;
168}
169
170FilePermissionsMask& FilePermissionsMask::remove_permissions(mode_t mode)
171{
172 m_clear_mask |= mode;
173 return *this;
174}
175
176}