summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXavier Defrang <xavier.defrang@gmail.com>2021-12-24 15:34:30 +0100
committerBrian Gianforcaro <b.gianfo@gmail.com>2022-01-01 17:33:43 +0000
commit8b95423b5027e3363a3d7fc5e796bc4bf8a0ea15 (patch)
tree0d6b95182dcebd9216c7d98b5a23768211e37dc0
parent0f729cebf4ff08b8d625c300a00be32241dbe27a (diff)
downloadserenity-8b95423b5027e3363a3d7fc5e796bc4bf8a0ea15.zip
LibCore: Add FilePermissionsMask
This class parses UNIX file permissions definitions in numeric (octal) or symbolic (ugoa+rwx) format and can apply them on a given file mode.
-rw-r--r--Tests/LibCore/CMakeLists.txt1
-rw-r--r--Tests/LibCore/TestLibCoreFilePermissionsMask.cpp83
-rw-r--r--Userland/Libraries/LibCore/CMakeLists.txt1
-rw-r--r--Userland/Libraries/LibCore/FilePermissionsMask.cpp151
-rw-r--r--Userland/Libraries/LibCore/FilePermissionsMask.h40
5 files changed, 276 insertions, 0 deletions
diff --git a/Tests/LibCore/CMakeLists.txt b/Tests/LibCore/CMakeLists.txt
index c83c229265..922547c80f 100644
--- a/Tests/LibCore/CMakeLists.txt
+++ b/Tests/LibCore/CMakeLists.txt
@@ -4,6 +4,7 @@ set(TEST_SOURCES
TestLibCoreIODevice.cpp
TestLibCoreDeferredInvoke.cpp
TestLibCoreStream.cpp
+ TestLibCoreFilePermissionsMask.cpp
)
foreach(source IN LISTS TEST_SOURCES)
diff --git a/Tests/LibCore/TestLibCoreFilePermissionsMask.cpp b/Tests/LibCore/TestLibCoreFilePermissionsMask.cpp
new file mode 100644
index 0000000000..544bd750a0
--- /dev/null
+++ b/Tests/LibCore/TestLibCoreFilePermissionsMask.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2021, Xavier Defrang <xavier.defrang@gmail.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibCore/FilePermissionsMask.h>
+#include <LibTest/TestCase.h>
+
+TEST_CASE(file_permission_mask_from_symbolic_notation)
+{
+ auto mask = Core::FilePermissionsMask::from_symbolic_notation(""sv);
+ EXPECT(!mask.is_error());
+ EXPECT_EQ(mask.value().clear_mask(), 0);
+ EXPECT_EQ(mask.value().write_mask(), 0);
+ EXPECT_EQ(mask.value().apply(0), 0);
+ EXPECT_EQ(mask.value().apply(0664), 0664);
+
+ mask = Core::FilePermissionsMask::from_symbolic_notation("u+rwx"sv);
+ EXPECT(!mask.is_error());
+ EXPECT_EQ(mask.value().clear_mask(), 0);
+ EXPECT_EQ(mask.value().write_mask(), 0700);
+ EXPECT_EQ(mask.value().apply(0), 0700);
+ EXPECT_EQ(mask.value().apply(0664), 0764);
+
+ mask = Core::FilePermissionsMask::from_symbolic_notation("g+rwx"sv);
+ EXPECT(!mask.is_error());
+ EXPECT_EQ(mask.value().clear_mask(), 0);
+ EXPECT_EQ(mask.value().write_mask(), 0070);
+ EXPECT_EQ(mask.value().apply(0), 0070);
+ EXPECT_EQ(mask.value().apply(0664), 0674);
+
+ mask = Core::FilePermissionsMask::from_symbolic_notation("o+rwx"sv);
+ EXPECT(!mask.is_error());
+ EXPECT_EQ(mask.value().clear_mask(), 0);
+ EXPECT_EQ(mask.value().write_mask(), 0007);
+ EXPECT_EQ(mask.value().apply(0), 0007);
+ EXPECT_EQ(mask.value().apply(0664), 0667);
+
+ mask = Core::FilePermissionsMask::from_symbolic_notation("a=rx"sv);
+ EXPECT(!mask.is_error());
+ EXPECT_EQ(mask.value().clear_mask(), 0777);
+ EXPECT_EQ(mask.value().write_mask(), 0555);
+ EXPECT_EQ(mask.value().apply(0), 0555);
+ EXPECT_EQ(mask.value().apply(0664), 0555);
+
+ mask = Core::FilePermissionsMask::from_symbolic_notation("u+rw,g=rx,o-rwx"sv);
+ EXPECT(!mask.is_error());
+ EXPECT_EQ(mask.value().clear_mask(), 0077);
+ EXPECT_EQ(mask.value().write_mask(), 0650);
+ EXPECT_EQ(mask.value().apply(0), 0650);
+ EXPECT_EQ(mask.value().apply(0177), 0750);
+
+ mask = Core::FilePermissionsMask::from_symbolic_notation("z+rw"sv);
+ EXPECT(mask.is_error());
+
+ mask = Core::FilePermissionsMask::from_symbolic_notation("u*rw"sv);
+ EXPECT(mask.is_error());
+
+ mask = Core::FilePermissionsMask::from_symbolic_notation("u+rz"sv);
+ EXPECT(mask.is_error());
+
+ mask = Core::FilePermissionsMask::from_symbolic_notation("u+rw;g+rw"sv);
+ EXPECT(mask.is_error());
+}
+
+TEST_CASE(file_permission_mask_parse)
+{
+ auto numeric_mask = Core::FilePermissionsMask::parse("750"sv);
+ auto symbolic_mask = Core::FilePermissionsMask::parse("u=rwx,g=rx,o-rwx"sv);
+
+ EXPECT_EQ(numeric_mask.value().apply(0), 0750);
+ EXPECT_EQ(symbolic_mask.value().apply(0), 0750);
+
+ EXPECT_EQ(numeric_mask.value().clear_mask(), symbolic_mask.value().clear_mask());
+ EXPECT_EQ(numeric_mask.value().write_mask(), symbolic_mask.value().write_mask());
+
+ auto mask = Core::FilePermissionsMask::parse("888");
+ EXPECT(mask.is_error());
+
+ mask = Core::FilePermissionsMask::parse("z+rw");
+ EXPECT(mask.is_error());
+}
diff --git a/Userland/Libraries/LibCore/CMakeLists.txt b/Userland/Libraries/LibCore/CMakeLists.txt
index db069de046..5981e880fe 100644
--- a/Userland/Libraries/LibCore/CMakeLists.txt
+++ b/Userland/Libraries/LibCore/CMakeLists.txt
@@ -11,6 +11,7 @@ set(SOURCES
EventLoop.cpp
FileWatcher.cpp
File.cpp
+ FilePermissionsMask.cpp
GetPassword.cpp
IODevice.cpp
LocalServer.cpp
diff --git a/Userland/Libraries/LibCore/FilePermissionsMask.cpp b/Userland/Libraries/LibCore/FilePermissionsMask.cpp
new file mode 100644
index 0000000000..1b6cdfc164
--- /dev/null
+++ b/Userland/Libraries/LibCore/FilePermissionsMask.cpp
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2021, Xavier Defrang <xavier.defrang@gmail.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <AK/Assertions.h>
+#include <AK/CharacterTypes.h>
+#include <AK/StringUtils.h>
+
+#include <LibCore/FilePermissionsMask.h>
+
+namespace Core {
+
+enum State {
+ Reference,
+ Mode
+};
+
+enum ClassFlag {
+ Other = 1,
+ Group = 2,
+ User = 4
+};
+
+enum Operation {
+ Add,
+ Remove,
+ Assign,
+};
+
+ErrorOr<FilePermissionsMask> FilePermissionsMask::parse(StringView string)
+{
+ return (!string.is_empty() && is_ascii_digit(string[0]))
+ ? from_numeric_notation(string)
+ : from_symbolic_notation(string);
+}
+
+ErrorOr<FilePermissionsMask> FilePermissionsMask::from_numeric_notation(StringView string)
+{
+ mode_t mode = AK::StringUtils::convert_to_uint_from_octal<u16>(string).value_or(01000);
+ if (mode > 0777)
+ return Error::from_string_literal("invalid octal representation"sv);
+ return FilePermissionsMask().assign_permissions(mode);
+}
+
+ErrorOr<FilePermissionsMask> FilePermissionsMask::from_symbolic_notation(StringView string)
+{
+ auto mask = FilePermissionsMask();
+
+ u8 state = State::Reference;
+ u8 classes = 0;
+ u8 operation = 0;
+
+ for (auto ch : string) {
+ switch (state) {
+ case State::Reference: {
+ // one 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::User | ClassFlag::Group | ClassFlag::Other;
+ else {
+ if (classes == 0)
+ return Error::from_string_literal("invalid access class: expected 'u', 'g', 'o' or 'a' "sv);
+
+ if (ch == '+')
+ operation = Operation::Add;
+ else if (ch == '-')
+ operation = Operation::Remove;
+ else if (ch == '=')
+ operation = Operation::Assign;
+ else
+ return Error::from_string_literal("invalid operation: expected '+', '-' or '='"sv);
+
+ state = State::Mode;
+ }
+
+ break;
+ }
+
+ case State::Mode: {
+ // one or more [rwx] terminated by a comma
+
+ // End of mode part, expect reference next
+ if (ch == ',') {
+ state = State::Reference;
+ classes = operation = 0;
+ continue;
+ }
+
+ mode_t write_bits = 0;
+
+ if (ch == 'r')
+ write_bits = 4;
+ else if (ch == 'w')
+ write_bits = 2;
+ else if (ch == 'x')
+ write_bits = 1;
+ else
+ return Error::from_string_literal("invalid symbolic permission"sv);
+
+ mode_t clear_bits = operation == Operation::Assign ? 7 : write_bits;
+
+ // 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)
+ mask.add_permissions(write_bits);
+ if (operation == Operation::Remove || operation == Operation::Assign)
+ 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;
+}
+
+}
diff --git a/Userland/Libraries/LibCore/FilePermissionsMask.h b/Userland/Libraries/LibCore/FilePermissionsMask.h
new file mode 100644
index 0000000000..c238344d91
--- /dev/null
+++ b/Userland/Libraries/LibCore/FilePermissionsMask.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2021, Xavier Defrang <xavier.defrang@gmail.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/Error.h>
+#include <AK/String.h>
+#include <sys/stat.h>
+
+namespace Core {
+
+class FilePermissionsMask {
+public:
+ static ErrorOr<FilePermissionsMask> parse(StringView string);
+ static ErrorOr<FilePermissionsMask> from_numeric_notation(StringView string);
+ static ErrorOr<FilePermissionsMask> from_symbolic_notation(StringView string);
+
+ FilePermissionsMask()
+ : m_clear_mask(0)
+ , m_write_mask(0)
+ {
+ }
+
+ FilePermissionsMask& assign_permissions(mode_t mode);
+ FilePermissionsMask& add_permissions(mode_t mode);
+ FilePermissionsMask& remove_permissions(mode_t mode);
+
+ mode_t apply(mode_t mode) const { return m_write_mask | (mode & ~m_clear_mask); }
+ mode_t clear_mask() const { return m_clear_mask; }
+ mode_t write_mask() const { return m_write_mask; }
+
+private:
+ mode_t m_clear_mask; // the bits that will be cleared
+ mode_t m_write_mask; // the bits that will be set
+};
+
+}