chmod Calculator — C++ source
Compute Unix file permissions between octal (e.g. 755), symbolic (rwxr-xr-x), and decimal - including setuid, setgid, and sticky bits. Toggle permissions interactively, fully client-side, with a shareable link.
This is the C++ implementation — the same logic the interactive tool runs, in a shareable, citable form.
// chmod-calculator — POSIX permission mode converter (octal <-> symbolic).
//
// Language: C++ (C++17, standard library only)
// Source: CosmoDev polyglot showcase port of the Chmod Calculator tool,
// ported from src/lib/chmod.ts (the canonical TypeScript lib) and
// held in lock-step with cli/chmod-calculator/chmod-calculator.go.
// License: display source — part of CosmoDev's polyglot tool pages.
//
// Design goals:
// - Pure + deterministic; failable public API returns std::optional (never throws).
// - Functionally equivalent to the TS/Go references: same inputs -> same outputs.
// - Self-contained: std only (no external dependencies).
//
// Converts between 3-4 digit octal ("755" / "4755"), 9-char symbolic
// ("rwxr-xr-x"), and the raw decimal mode, including the setuid / setgid /
// sticky special bits (the s/S and t/T markers in the exec slot).
#include <cassert>
#include <cctype>
#include <cstdio>
#include <iostream>
#include <optional>
#include <stdexcept>
#include <string>
#include <string_view>
#include <utility>
namespace chmod {
/// Which permission class a triplet describes — governs which special-bit
/// marker (s/S for owner+group, t/T for other) is legal in its exec slot.
enum class pos { owner, group, other };
/// Full chmod breakdown — the C++ mirror of the TS `ChmodResult` / Go `Result`.
struct chmod_result {
std::string octal; ///< 4-digit zero-padded octal, e.g. "0755".
std::string symbolic; ///< 9-char rwxrwxrwx with special markers, e.g. "rwsr-xr-x".
int decimal; ///< Raw integer mode (0-4095).
bool setuid;
bool setgid;
bool sticky;
};
// Forward declarations — detail::build_result calls the renderers defined
// further below (kept there as the public API surface).
inline std::string mode_to_octal(int mode);
inline std::string mode_to_symbolic(int mode);
namespace detail {
/// View of `s` with ASCII whitespace trimmed from both ends (mirrors .trim()).
inline std::string_view trim(std::string_view s) {
while (!s.empty() && std::isspace(static_cast<unsigned char>(s.front()))) s.remove_prefix(1);
while (!s.empty() && std::isspace(static_cast<unsigned char>(s.back()))) s.remove_suffix(1);
return s;
}
/// Parse a 3-char rwx triplet at `p`. The exec slot may carry a special-bit
/// marker: s/S (setuid in owner, setgid in group) or t/T (sticky in other).
/// Returns `(digit, special)` on success, or std::nullopt if any char is invalid.
inline std::optional<std::pair<int, int>> parse_triplet(std::string_view tri, pos p) {
if (tri.size() != 3) return std::nullopt;
int digit = 0;
switch (tri[0]) {
case 'r': digit |= 4; break;
case '-': break;
default: return std::nullopt;
}
switch (tri[1]) {
case 'w': digit |= 2; break;
case '-': break;
default: return std::nullopt;
}
int special = 0;
switch (const char c = tri[2]) {
case 'x': digit |= 1; break;
case '-': break;
case 's': case 'S':
if (p != pos::owner && p != pos::group) return std::nullopt;
if (c == 's') digit |= 1;
special = p == pos::owner ? 4 : 2;
break;
case 't': case 'T':
if (p != pos::other) return std::nullopt;
if (c == 't') digit |= 1;
special = 1;
break;
default: return std::nullopt;
}
return std::make_pair(digit, special);
}
/// Render a 0-7 digit + optional special bit as a 3-char triplet. `marker` is
/// 's' (owner/group) or 't' (other); upper-cased when the exec bit is absent —
/// yielding 'S' / 'T'.
inline std::string format_triplet(int digit, bool has_special, char marker) {
std::string out;
out.reserve(3);
out.push_back(digit & 4 ? 'r' : '-');
out.push_back(digit & 2 ? 'w' : '-');
const bool exec = (digit & 1) != 0;
if (has_special && exec) {
out.push_back(marker);
} else if (has_special) {
out.push_back(static_cast<char>(std::toupper(static_cast<unsigned char>(marker))));
} else if (exec) {
out.push_back('x');
} else {
out.push_back('-');
}
return out;
}
inline chmod_result build_result(int mode) {
const int special = (mode >> 9) & 7;
return chmod_result{
mode_to_octal(mode),
mode_to_symbolic(mode),
mode & 07777,
(special & 4) != 0,
(special & 2) != 0,
(special & 1) != 0,
};
}
} // namespace detail
/// Parse symbolic notation ("rwxr-xr-x") into a raw mode integer, or std::nullopt.
inline std::optional<int> symbolic_to_mode(std::string_view sym) {
const std::string_view s = detail::trim(sym);
if (s.size() != 9) return std::nullopt;
const auto o = detail::parse_triplet(s.substr(0, 3), pos::owner);
if (!o) return std::nullopt;
const auto g = detail::parse_triplet(s.substr(3, 3), pos::group);
if (!g) return std::nullopt;
const auto ot = detail::parse_triplet(s.substr(6, 3), pos::other);
if (!ot) return std::nullopt;
const int special = o->second | g->second | ot->second;
return special * 01000 /* 0o1000 */ + (o->first << 6) + (g->first << 3) + ot->first;
}
/// Parse a 3-4 digit octal string ("755" / "4755") into a raw mode, or std::nullopt.
inline std::optional<int> octal_to_mode(std::string_view octal) {
const std::string_view s = detail::trim(octal);
if (s.size() != 3 && s.size() != 4) return std::nullopt;
int value = 0;
for (const char c : s) {
if (c < '0' || c > '7') return std::nullopt;
value = value * 8 + (c - '0');
}
return value;
}
/// Render a raw mode as 9-char symbolic notation.
inline std::string mode_to_symbolic(int mode) {
const int special = (mode >> 9) & 7;
return detail::format_triplet((mode >> 6) & 7, (special & 4) != 0, 's') +
detail::format_triplet((mode >> 3) & 7, (special & 2) != 0, 's') +
detail::format_triplet(mode & 7, (special & 1) != 0, 't');
}
/// Render a raw mode as a 4-digit zero-padded octal string.
inline std::string mode_to_octal(int mode) {
char buf[5];
std::snprintf(buf, sizeof buf, "%04o", mode & 07777);
return buf;
}
/// Build a full result from symbolic notation, or std::nullopt if invalid.
inline std::optional<chmod_result> from_symbolic(std::string_view sym) {
const auto mode = symbolic_to_mode(sym);
if (!mode) return std::nullopt;
return detail::build_result(*mode);
}
/// Build a full result from an octal string, or std::nullopt if invalid.
inline std::optional<chmod_result> from_octal(std::string_view octal) {
const auto mode = octal_to_mode(octal);
if (!mode) return std::nullopt;
return detail::build_result(*mode);
}
} // namespace chmod
// --- showcase assertions (the canonical suite lives in src/lib) ------------------
namespace {
void expect(bool condition, const char *what) {
if (!condition) throw std::runtime_error(std::string("showcase check failed: ") + what);
}
} // namespace
int main() {
using chmod::from_octal;
using chmod::from_symbolic;
using chmod::octal_to_mode;
using chmod::symbolic_to_mode;
const auto r = from_octal("755");
expect(r.has_value(), "755 parses");
expect(r->octal == "0755", "755 octal");
expect(r->symbolic == "rwxr-xr-x", "755 symbolic");
expect(r->decimal == 0755, "755 decimal");
expect(!r->setuid && !r->setgid && !r->sticky, "755 no special bits");
expect(from_symbolic("rwxr-xr-x")->octal == "0755", "symbolic round-trip");
const auto su = from_octal("4755"); // setuid over rwxr-xr-x -> exec slot becomes 's'
expect(su.has_value(), "4755 parses");
expect(su->symbolic == "rwsr-xr-x", "4755 symbolic");
expect(su->decimal == 04755, "4755 decimal");
expect(su->setuid && !su->sticky, "4755 setuid only");
const auto st = from_octal("1644"); // sticky over rw-r--r--, no exec -> marker 'T'
expect(st.has_value(), "1644 parses");
expect(st->symbolic == "rw-r--r-T", "1644 symbolic");
expect(st->decimal == 01644, "1644 decimal");
expect(st->sticky && !st->setuid, "1644 sticky only");
expect(!octal_to_mode("999").has_value(), "'9' is not an octal digit");
expect(!symbolic_to_mode("rwx").has_value(), "wrong length rejected");
expect(from_octal("0000")->symbolic == "---------", "zero round-trip");
std::cout << "All chmod showcase tests passed.\n";
return 0;
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →