Skip to content

chmod Calculator — TypeScript 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 TypeScript implementation — the same logic the interactive tool runs, in a shareable, citable form.

// Pure, deterministic Unix chmod calculator. Zero deps. Converts between
// symbolic notation ("rwxr-xr-x"), 3-4 digit octal ("755" / "4755"), and the
// raw decimal mode, including the setuid / setgid / sticky special bits - the
// unit-test surface for the chmod Calculator tool.

export interface ChmodResult {
  octal: string; // 4-digit zero-padded, e.g. "0755"
  symbolic: string; // 9-char rwxrwxrwx with special markers, e.g. "rwsr-xr-x"
  decimal: number; // raw integer mode (0-4095)
  setuid: boolean;
  setgid: boolean;
  sticky: boolean;
}

type Pos = 'owner' | 'group' | 'other';

// Parse a 3-char rwx triplet at `pos`. The exec slot may carry a special-bit
// marker: s/S (setuid in owner, setgid in group) or t/T (sticky in other).
// Returns the 0-7 digit plus the special-bit contribution (4/2/1/0), or null.
function parseTriplet(tri: string, pos: Pos): { digit: number; special: number } | null {
  // Caller (symbolicToMode) always passes a 3-char slice, so index directly.
  let digit = 0;
  if (tri[0] === 'r') digit |= 4;
  else if (tri[0] !== '-') return null;

  if (tri[1] === 'w') digit |= 2;
  else if (tri[1] !== '-') return null;

  let special = 0;
  const c = tri[2];
  if (c === 'x') digit |= 1;
  else if (c === '-') {
    /* none */
  } else if ((c === 's' || c === 'S') && (pos === 'owner' || pos === 'group')) {
    if (c === 's') digit |= 1;
    special = pos === 'owner' ? 4 : 2;
  } else if ((c === 't' || c === 'T') && pos === 'other') {
    if (c === 't') digit |= 1;
    special = 1;
  } else return null;

  return { 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.
function formatTriplet(digit: number, hasSpecial: boolean, marker: string): string {
  let t = '';
  t += digit & 4 ? 'r' : '-';
  t += digit & 2 ? 'w' : '-';
  const exec = (digit & 1) !== 0;
  if (hasSpecial) t += exec ? marker : marker.toUpperCase();
  else t += exec ? 'x' : '-';
  return t;
}

/** Parse symbolic notation ("rwxr-xr-x") into a raw mode integer, or null. */
export function symbolicToMode(sym: string): number | null {
  const s = sym.trim();
  if (s.length !== 9) return null;
  const o = parseTriplet(s.slice(0, 3), 'owner');
  const g = parseTriplet(s.slice(3, 6), 'group');
  const ot = parseTriplet(s.slice(6, 9), 'other');
  if (!o || !g || !ot) return null;
  return (o.special | g.special | ot.special) * 0o1000 + (o.digit << 6) + (g.digit << 3) + ot.digit;
}

/** Parse a 3-4 digit octal string ("755" / "4755") into a raw mode, or null. */
export function octalToMode(octal: string): number | null {
  if (!/^[0-7]{3,4}$/.test(octal.trim())) return null;
  return parseInt(octal.trim(), 8);
}

/** Render a raw mode as 9-char symbolic notation. */
export function modeToSymbolic(mode: number): string {
  const special = (mode >> 9) & 7;
  const od = (mode >> 6) & 7;
  const gd = (mode >> 3) & 7;
  const otd = mode & 7;
  return (
    formatTriplet(od, (special & 4) !== 0, 's') +
    formatTriplet(gd, (special & 2) !== 0, 's') +
    formatTriplet(otd, (special & 1) !== 0, 't')
  );
}

/** Render a raw mode as a 4-digit zero-padded octal string. */
export function modeToOctal(mode: number): string {
  return (mode & 0o7777).toString(8).padStart(4, '0');
}

function result(mode: number): ChmodResult {
  const special = (mode >> 9) & 7;
  return {
    octal: modeToOctal(mode),
    symbolic: modeToSymbolic(mode),
    decimal: mode & 0o7777,
    setuid: (special & 4) !== 0,
    setgid: (special & 2) !== 0,
    sticky: (special & 1) !== 0,
  };
}

/** Build a full result from symbolic notation, or null if invalid. */
export function fromSymbolic(sym: string): ChmodResult | null {
  const mode = symbolicToMode(sym);
  return mode === null ? null : result(mode);
}

/** Build a full result from an octal string, or null if invalid. */
export function fromOctal(octal: string): ChmodResult | null {
  const mode = octalToMode(octal);
  return mode === null ? null : result(mode);
}

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 →