/* * Copyright (c) 2021, Xavier Defrang * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include namespace Core { enum State { Classes, Mode }; enum ClassFlag { Other = 1, Group = 2, User = 4, All = 7 }; enum Operation { Add, Remove, Assign, }; ErrorOr FilePermissionsMask::parse(StringView string) { return (!string.is_empty() && is_ascii_digit(string[0])) ? from_numeric_notation(string) : from_symbolic_notation(string); } ErrorOr FilePermissionsMask::from_numeric_notation(StringView string) { string = string.trim_whitespace(); mode_t mode = AK::StringUtils::convert_to_uint_from_octal(string, TrimWhitespace::No).value_or(010000); if (mode > 07777) return Error::from_string_literal("invalid octal representation"); FilePermissionsMask mask; mask.assign_permissions(mode); // For compatibility purposes, just clear the special mode bits if we explicitly passed a 4-character mode. if (string.length() >= 4) mask.remove_permissions(07000); return mask; } ErrorOr FilePermissionsMask::from_symbolic_notation(StringView string) { auto mask = FilePermissionsMask(); u8 state = State::Classes; u8 classes = 0; u8 operation = 0; for (auto ch : string) { switch (state) { case State::Classes: { // zero or more [ugoa] terminated by one operator [+-=] if (ch == 'u') classes |= ClassFlag::User; else if (ch == 'g') classes |= ClassFlag::Group; else if (ch == 'o') classes |= ClassFlag::Other; else if (ch == 'a') classes = ClassFlag::All; else { if (ch == '+') operation = Operation::Add; else if (ch == '-') operation = Operation::Remove; else if (ch == '=') operation = Operation::Assign; else if (classes == 0) return Error::from_string_literal("invalid class: expected 'u', 'g', 'o' or 'a'"); else return Error::from_string_literal("invalid operation: expected '+', '-' or '='"); // if an operation was specified without a class, assume all if (classes == 0) classes = ClassFlag::All; state = State::Mode; } break; } case State::Mode: { // one or more [rwx] terminated by a comma // End of mode part, expect class next if (ch == ',') { state = State::Classes; classes = operation = 0; continue; } mode_t write_bits = 0; bool apply_to_directories_and_executables_only = false; switch (ch) { case 'r': write_bits = 4; break; case 'w': write_bits = 2; break; case 'x': write_bits = 1; break; case 'X': write_bits = 1; apply_to_directories_and_executables_only = true; break; default: return Error::from_string_literal("invalid symbolic permission: expected 'r', 'w' or 'x'"); } mode_t clear_bits = operation == Operation::Assign ? 7 : write_bits; FilePermissionsMask& edit_mask = apply_to_directories_and_executables_only ? mask.directory_or_executable_mask() : mask; // Update masks one class at a time in other, group, user order for (auto cls = classes; cls != 0; cls >>= 1) { if (cls & 1) { if (operation == Operation::Add || operation == Operation::Assign) edit_mask.add_permissions(write_bits); if (operation == Operation::Remove || operation == Operation::Assign) edit_mask.remove_permissions(clear_bits); } write_bits <<= 3; clear_bits <<= 3; } break; } default: VERIFY_NOT_REACHED(); } } return mask; } FilePermissionsMask& FilePermissionsMask::assign_permissions(mode_t mode) { m_write_mask = mode; m_clear_mask = 0777; return *this; } FilePermissionsMask& FilePermissionsMask::add_permissions(mode_t mode) { m_write_mask |= mode; return *this; } FilePermissionsMask& FilePermissionsMask::remove_permissions(mode_t mode) { m_clear_mask |= mode; return *this; } }