mirror of
https://github.com/SerenityOS/serenity
synced 2024-09-06 08:56:40 +00:00
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:
parent
0f729cebf4
commit
8b95423b50
|
@ -4,6 +4,7 @@ set(TEST_SOURCES
|
|||
TestLibCoreIODevice.cpp
|
||||
TestLibCoreDeferredInvoke.cpp
|
||||
TestLibCoreStream.cpp
|
||||
TestLibCoreFilePermissionsMask.cpp
|
||||
)
|
||||
|
||||
foreach(source IN LISTS TEST_SOURCES)
|
||||
|
|
83
Tests/LibCore/TestLibCoreFilePermissionsMask.cpp
Normal file
83
Tests/LibCore/TestLibCoreFilePermissionsMask.cpp
Normal 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());
|
||||
}
|
|
@ -11,6 +11,7 @@ set(SOURCES
|
|||
EventLoop.cpp
|
||||
FileWatcher.cpp
|
||||
File.cpp
|
||||
FilePermissionsMask.cpp
|
||||
GetPassword.cpp
|
||||
IODevice.cpp
|
||||
LocalServer.cpp
|
||||
|
|
151
Userland/Libraries/LibCore/FilePermissionsMask.cpp
Normal file
151
Userland/Libraries/LibCore/FilePermissionsMask.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
40
Userland/Libraries/LibCore/FilePermissionsMask.h
Normal file
40
Userland/Libraries/LibCore/FilePermissionsMask.h
Normal 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
|
||||
};
|
||||
|
||||
}
|
Loading…
Reference in a new issue