chmod Calculator — Go 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 Go implementation — the same logic the interactive tool runs, in a shareable, citable form.
// Package chmod is the Go twin of CosmoDev's src/lib/chmod.ts (dual source: the
// web lib is TypeScript, the CLI lib is Go — kept in lock-step). Pure +
// deterministic, never panics. Table-driven tests share vectors with the TS
// suite so the two implementations are held to the same contract.
package chmod
import (
"fmt"
"strconv"
"strings"
)
// Result is the full chmod breakdown returned by FromSymbolic / FromOctal.
type Result struct {
Octal string // 4-digit zero-padded, e.g. "0755"
Symbolic string // 9-char rwxrwxrwx with special markers, e.g. "rwsr-xr-x"
Decimal int // raw integer mode (0-4095)
Setuid bool
Setgid bool
Sticky bool
}
type pos int
const (
posOwner pos = iota
posGroup
posOther
)
// parseTriplet mirrors parseTriplet in chmod.ts: the exec slot may carry a
// special-bit marker (s/S for setuid-in-owner/setgid-in-group, t/T for sticky).
func parseTriplet(tri string, p pos) (digit, special int, ok bool) {
if len(tri) != 3 {
return 0, 0, false
}
switch tri[0] {
case 'r':
digit |= 4
case '-':
default:
return 0, 0, false
}
switch tri[1] {
case 'w':
digit |= 2
case '-':
default:
return 0, 0, false
}
c := tri[2]
switch {
case c == 'x':
digit |= 1
case c == '-':
case (c == 's' || c == 'S') && (p == posOwner || p == posGroup):
if c == 's' {
digit |= 1
}
if p == posOwner {
special = 4
} else {
special = 2
}
case (c == 't' || c == 'T') && p == posOther:
if c == 't' {
digit |= 1
}
special = 1
default:
return 0, 0, false
}
return digit, special, true
}
// formatTriplet renders a 0-7 digit + optional special bit as a 3-char triplet.
// marker is 's' (owner/group) or 't' (other); upper-cased when exec is absent.
func formatTriplet(digit int, hasSpecial bool, marker byte) string {
t := make([]byte, 3)
if digit&4 != 0 {
t[0] = 'r'
} else {
t[0] = '-'
}
if digit&2 != 0 {
t[1] = 'w'
} else {
t[1] = '-'
}
exec := digit&1 != 0
switch {
case hasSpecial && exec:
t[2] = marker
case hasSpecial:
t[2] = marker - 32 // uppercase: 's'->'S', 't'->'T'
case exec:
t[2] = 'x'
default:
t[2] = '-'
}
return string(t)
}
// SymbolicToMode parses "rwxr-xr-x" into a raw mode, returning ok=false if invalid.
func SymbolicToMode(sym string) (int, bool) {
s := strings.TrimSpace(sym)
if len(s) != 9 {
return 0, false
}
od, os_, oko := parseTriplet(s[0:3], posOwner)
gd, gs, okg := parseTriplet(s[3:6], posGroup)
otd, ots, okot := parseTriplet(s[6:9], posOther)
if !oko || !okg || !okot {
return 0, false
}
special := os_ | gs | ots
return special*0o1000 + (od << 6) + (gd << 3) + otd, true
}
// OctalToMode parses "755" / "4755" into a raw mode, returning ok=false if invalid.
func OctalToMode(octal string) (int, bool) {
s := strings.TrimSpace(octal)
if len(s) != 3 && len(s) != 4 {
return 0, false
}
for _, c := range s {
if c < '0' || c > '7' {
return 0, false
}
}
n, err := strconv.ParseInt(s, 8, 32)
if err != nil {
return 0, false
}
return int(n), true
}
// ModeToSymbolic renders a raw mode as 9-char symbolic notation.
func ModeToSymbolic(mode int) string {
special := (mode >> 9) & 7
return formatTriplet((mode>>6)&7, special&4 != 0, 's') +
formatTriplet((mode>>3)&7, special&2 != 0, 's') +
formatTriplet(mode&7, special&1 != 0, 't')
}
// ModeToOctal renders a raw mode as a 4-digit zero-padded octal string.
func ModeToOctal(mode int) string {
return fmt.Sprintf("%04o", mode&0o7777)
}
func buildResult(mode int) Result {
special := (mode >> 9) & 7
return Result{
Octal: ModeToOctal(mode),
Symbolic: ModeToSymbolic(mode),
Decimal: mode & 0o7777,
Setuid: special&4 != 0,
Setgid: special&2 != 0,
Sticky: special&1 != 0,
}
}
// FromSymbolic builds a Result from symbolic notation.
func FromSymbolic(sym string) (Result, bool) {
mode, ok := SymbolicToMode(sym)
if !ok {
return Result{}, false
}
return buildResult(mode), true
}
// FromOctal builds a Result from an octal string.
func FromOctal(octal string) (Result, bool) {
mode, ok := OctalToMode(octal)
if !ok {
return Result{}, false
}
return buildResult(mode), true
}
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 →