BareGit

Use typed YAML integers for socket permissions

Reuse the common integer parser for socket permissions, validate the mode range, and update examples, tests, and the Arch package checksum for the explicit YAML octal form.
Author: MetroWind <chris.corsair@gmail.com>
Date: Sat Sep 19 12:15:18 2026 -0700
Commit: ef042e741f08030c7a00b1e750c1fffaba6b59d7

Changes

diff --git a/config.example.yaml b/config.example.yaml
index 60d5f16..c2a4fa7 100644
--- a/config.example.yaml
+++ b/config.example.yaml
@@ -4,7 +4,7 @@
 worker_count: 4
 database_path: status-tracker.sqlite
 # unix_socket: /run/probius/probius.sock
-# socket_permission: 0660
+# socket_permission: 0o660
 
 # Groups are a sequence. Service IDs are mapping keys and must be unique.
 groups:
diff --git a/packages/arch/PKGBUILD b/packages/arch/PKGBUILD
index 9b2e85c..442a94b 100644
--- a/packages/arch/PKGBUILD
+++ b/packages/arch/PKGBUILD
@@ -24,7 +24,7 @@ sha256sums=(
     'SKIP'
     'c16a0f6151f0c64681339ac131e07bde8707ab994ba5707d541c6fb1501cca0c'
     '0f57b093e9e0412f2bfaced764c27929a581e6cdaa2842c904701337959712ff'
-    'c486ce4ddedb590db2ed7406e112b70d1ad55628f9c6b049286e2ab144661d72'
+    '9ec35ec50287a513c1d9af35b48d7a0647ccdf13016f6f97829abe6c7444c777'
 )
 
 pkgver()
diff --git a/packages/arch/probius.yaml b/packages/arch/probius.yaml
index 9aa9f3e..e900330 100644
--- a/packages/arch/probius.yaml
+++ b/packages/arch/probius.yaml
@@ -1,5 +1,5 @@
 worker_count: 4
 database_path: /var/lib/probius/probius.sqlite
 # unix_socket: /run/probius/probius.sock
-# socket_permission: 0660
+# socket_permission: 0o660
 groups: []
diff --git a/src/configuration.cpp b/src/configuration.cpp
index 00a5c71..0744914 100644
--- a/src/configuration.cpp
+++ b/src/configuration.cpp
@@ -1,7 +1,6 @@
 #include "configuration.h"
 
 #include <algorithm>
-#include <charconv>
 #include <cctype>
 #include <exception>
 #include <fstream>
@@ -92,10 +91,8 @@ mw::E<Integer> requiredInteger(Node node, std::string_view key,
         return std::unexpected(configError(
             context, std::string("missing integer field ") + std::string(key)));
     }
-    const auto text = value.val();
     Integer result{};
-    const auto parsed = std::from_chars(text.begin(), text.end(), result);
-    if(parsed.ec != std::errc{} || parsed.ptr != text.end())
+    if(!value.deserialize(&result))
     {
         return std::unexpected(configError(
             context, std::string("invalid integer field ") + std::string(key)));
@@ -117,10 +114,8 @@ mw::E<std::optional<Integer>> optionalInteger(
         return std::unexpected(configError(
             context, std::string("invalid integer field ") + std::string(key)));
     }
-    const auto text = value.val();
     Integer result{};
-    const auto parsed = std::from_chars(text.begin(), text.end(), result);
-    if(parsed.ec != std::errc{} || parsed.ptr != text.end())
+    if(!value.deserialize(&result))
     {
         return std::unexpected(configError(
             context, std::string("invalid integer field ") + std::string(key)));
@@ -128,38 +123,6 @@ mw::E<std::optional<Integer>> optionalInteger(
     return std::optional<Integer>{result};
 }
 
-mw::E<std::optional<unsigned int>> optionalSocketPermission(
-    Node node, std::string_view key, std::string_view context)
-{
-    auto value = child(node, key);
-    if(!value.readable())
-    {
-        return std::optional<unsigned int>{};
-    }
-    if(!value.has_val())
-    {
-        return std::unexpected(configError(
-            context, std::string("invalid socket permission ") +
-                         std::string(key)));
-    }
-    std::string_view text(value.val().begin(), value.val().size());
-    if(text.starts_with("0o") || text.starts_with("0O"))
-    {
-        text.remove_prefix(2);
-    }
-    unsigned int result = 0;
-    const auto parsed = std::from_chars(
-        text.begin(), text.end(), result, 8);
-    if(text.empty() || parsed.ec != std::errc{} ||
-       parsed.ptr != text.end() || result > 0777)
-    {
-        return std::unexpected(configError(
-            context, std::string(key) +
-                         " must be an octal mode from 000 through 777"));
-    }
-    return std::optional<unsigned int>{result};
-}
-
 std::string lower(std::string value)
 {
     std::transform(value.begin(), value.end(), value.begin(),
@@ -358,9 +321,17 @@ mw::E<Configuration> parseTree(ryml::Tree& tree)
     {
         result.unix_socket = std::move(*unix_socket);
     }
-    ASSIGN_OR_RETURN(result.socket_permission,
-                     optionalSocketPermission(
+    ASSIGN_OR_RETURN(auto socket_permission,
+                     optionalInteger<unsigned int>(
                          root, "socket_permission", "configuration"));
+    if(socket_permission && *socket_permission > 0777)
+    {
+        return std::unexpected(configError(
+            "configuration",
+            "socket_permission must be an octal mode from 0o000 through "
+            "0o777"));
+    }
+    result.socket_permission = std::move(socket_permission);
     auto groups = child(root, "groups");
     if(!groups.readable() || !groups.is_seq())
     {
diff --git a/tests/configuration_test.cpp b/tests/configuration_test.cpp
index f2f8b84..1c51ddd 100644
--- a/tests/configuration_test.cpp
+++ b/tests/configuration_test.cpp
@@ -37,7 +37,7 @@ TEST(Configuration, LoadsGroupedProtocolSettings)
         "worker_count: 4\n"
         "database_path: tracker.sqlite\n"
         "unix_socket: /run/probius.sock\n"
-        "socket_permission: 0660\n"
+        "socket_permission: 0o660\n"
         "groups:\n"
         "  - name: Public services\n"
         "    services:\n"
@@ -141,12 +141,12 @@ TEST(Configuration, RejectsInvalidValues)
         "worker_count: 1\n"
         "database_path: db\n"
         "unix_socket: /run/probius.sock\n"
-        "socket_permission: 0668\n"
+        "socket_permission: 0o668\n"
         "groups: []\n",
         "worker_count: 1\n"
         "database_path: db\n"
         "unix_socket: /run/probius.sock\n"
-        "socket_permission: 01000\n"
+        "socket_permission: 0o1000\n"
         "groups: []\n"
     };
     for(const auto& content : contents)