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.
This commit is contained in:
Xavier Defrang 2021-12-24 15:34:30 +01:00 committed by Brian Gianforcaro
parent 0f729cebf4
commit 8b95423b50
Notes: sideshowbarker 2024-07-17 21:49:26 +09:00
5 changed files with 276 additions and 0 deletions

View file

@ -4,6 +4,7 @@ set(TEST_SOURCES
TestLibCoreIODevice.cpp
TestLibCoreDeferredInvoke.cpp
TestLibCoreStream.cpp
TestLibCoreFilePermissionsMask.cpp
)
foreach(source IN LISTS TEST_SOURCES)

View file

@ -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());
}

View file

@ -11,6 +11,7 @@ set(SOURCES
EventLoop.cpp
FileWatcher.cpp
File.cpp
FilePermissionsMask.cpp
GetPassword.cpp
IODevice.cpp
LocalServer.cpp

View file

@ -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;
}
}

View file

@ -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
};
}