From: Thomas Walker Lynch Date: Fri, 6 Nov 2026 06:39:56 +0000 (+0000) Subject: . X-Git-Url: https://git.reasoningtechnology.com/%27%20%20%20full_path%20%20%20%27?a=commitdiff_plain;h=e3dee0dbaefe8c284fc342382a5f0df3b398bc7b;p=Man_In_Grey . --- diff --git a/developer/authored/Man_In_Grey.copy/FileLanguage.py b/developer/authored/Man_In_Grey.copy/FileLanguage.py index 3f102cf..3802c92 100644 --- a/developer/authored/Man_In_Grey.copy/FileLanguage.py +++ b/developer/authored/Man_In_Grey.copy/FileLanguage.py @@ -1,6 +1,6 @@ # -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*- """ -FileLanguage - a small command language for writing files, Man_In_Grey's +FileLanguage - a small instruction language for writing files, Man_In_Grey's test language. It is a constrained form of the POSIX file interface. open(directory ,name ,mode ,owner ,permission) produces a symbol for the file @@ -14,11 +14,11 @@ The modes of open: replace_keep an existing file is kept as .YYYYMMDDTHHMMSSZ There is no close. Each opened file is written to a temporary file in its -directory, and after the journal's last command every opened file is put in +directory, and after the journal's last instruction every opened file is put in place, each in one step. The module has two halves. The composer half is JournalInterface, through -which a composer places commands; it checks every argument when the call is +which a composer places instructions; it checks every argument when the call is made, and raises JournalError on a wrong one. The interpreter half holds the checks the guard makes of a journal received as data, and the execution the interpreter carries out. @@ -88,8 +88,8 @@ def permission_value(value): class JournalInterface: """ The journal interface of the file language. A composer is given one, and - places commands with its three functions. open returns a Symbol: the file - the open command will produce when the journal is executed. + places instructions with its three functions. open returns a Symbol: the file + the open instruction will produce when the journal is executed. """ def __init__(self ,journal ,working_dpath): @@ -123,7 +123,7 @@ class JournalInterface: }) def write(self ,file ,content): - if not isinstance(file ,Symbol) or self._journal.command_list[file.index].command != "open": + if not isinstance(file ,Symbol) or self._journal.instruction_list[file.index].name != "open": raise JournalError("write: file is to be the symbol an open returned") if isinstance(content ,str): content = content.encode("utf-8") @@ -143,10 +143,10 @@ class JournalInterface: def check_wellformed(data): problem_list = [] - for index ,command in enumerate(data["command_list"]): - name ,argument = command["command"] ,command["argument"] + for index ,instruction in enumerate(data["instruction_list"]): + name ,argument = instruction["name"] ,instruction["argument"] if name not in ARGUMENT: - problem_list.append(f"@{index}: unknown command {clean(repr(name))}"); continue + problem_list.append(f"@{index}: unknown instruction {clean(repr(name))}"); continue if set(argument) != set(ARGUMENT[name]): problem_list.append(f"@{index} {name}: arguments {clean(sorted(argument))}, expected {sorted(ARGUMENT[name])}"); continue if name in ("open" ,"unlink"): @@ -161,7 +161,7 @@ def check_wellformed(data): problem_list.append(f"@{index} open: permission {clean(repr(argument['permission']))} is not valid") if name == "write": file = argument["file"] - if not Symbol.is_data(file) or data["command_list"][file["symbol"]]["command"] != "open": + if not Symbol.is_data(file) or data["instruction_list"][file["symbol"]]["name"] != "open": problem_list.append(f"@{index} write: file is not the symbol of an open") if not isinstance(argument["content"] ,(bytes ,bytearray)): problem_list.append(f"@{index} write: content is not bytes") @@ -174,8 +174,8 @@ def check_constraint(data ,constraint ,gate_invoking_account): allowed = {} for entry in constraint.get("file" ,()): allowed[(entry["directory"] ,entry["name"])] = entry - for index ,command in enumerate(data["command_list"]): - name ,argument = command["command"] ,command["argument"] + for index ,instruction in enumerate(data["instruction_list"]): + name ,argument = instruction["name"] ,instruction["argument"] if name not in ("open" ,"unlink"): continue where = f"{argument['directory']}/{argument['name']}" @@ -196,9 +196,9 @@ def check_constraint(data ,constraint ,gate_invoking_account): def check_validity(data): problem_list = [] - for index ,command in enumerate(data["command_list"]): - if command["command"] == "open": - owner = command["argument"]["owner"] + for index ,instruction in enumerate(data["instruction_list"]): + if instruction["name"] == "open": + owner = instruction["argument"]["owner"] try: pwd.getpwnam(owner) except KeyError: @@ -218,14 +218,14 @@ def _regular_or_absent(dirfd ,name): def check_filesystem(data): problem_list = [] seen = set() - for index ,command in enumerate(data["command_list"]): - name ,argument = command["command"] ,command["argument"] + for index ,instruction in enumerate(data["instruction_list"]): + name ,argument = instruction["name"] ,instruction["argument"] if name not in ("open" ,"unlink"): continue directory ,file_name = argument["directory"] ,argument["name"] where = f"{directory}/{file_name}" if (directory ,file_name) in seen: - problem_list.append(f"@{index} {name}: {where} is named by more than one command"); continue + problem_list.append(f"@{index} {name}: {where} is named by more than one instruction"); continue seen.add((directory ,file_name)) if os.path.realpath(directory) != directory: problem_list.append(f"@{index} {name}: {directory} passes through a symbolic link"); continue @@ -266,15 +266,15 @@ def _backup_name(dirfd ,name): def execute(data ,log): """ - Execute the commands in order, then put each opened file in place. Stops + Execute the instructions in order, then put each opened file in place. Stops at the first error, removing every temporary file not yet put in place. log is given a tag and a message for each thing done. Returns 0, or 1 on an error. """ opened = {} # symbol index -> {dirfd ,fd ,tmp ,argument} try: - for index ,command in enumerate(data["command_list"]): - name ,argument = command["command"] ,command["argument"] + for index ,instruction in enumerate(data["instruction_list"]): + name ,argument = instruction["name"] ,instruction["argument"] if name == "open": dirfd = os.open(argument["directory"] ,os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW) tmp = f".{argument['name']}.Man_In_Grey.{os.getpid()}.{index}" diff --git a/developer/authored/Man_In_Grey.copy/Guard.py b/developer/authored/Man_In_Grey.copy/Guard.py index 7d89de8..c980660 100644 --- a/developer/authored/Man_In_Grey.copy/Guard.py +++ b/developer/authored/Man_In_Grey.copy/Guard.py @@ -2,13 +2,13 @@ """ Guard - the general guard of Man_In_Grey. -The guard decides whether a journal can be executed. A command language +The guard decides whether a journal can be executed. An instruction language supplies its checks, in order, as CHECK; check() decodes the journal, runs the checks, and stops at the first that finds problems. Every finding is given to report as a tag and a message: _broken for each problem, check_passed for each check that found none. -This module is a library. A command language's guard program calls check() +This module is a library. An instruction language's guard program calls check() and writes the verdict; see guard_file.py. """ @@ -17,7 +17,7 @@ import Journal def check(journal_bytes ,language ,constraint ,gate_invoking_account ,report): """ - Given the encoded journal, the command language module, the constraints, + Given the encoded journal, the instruction language module, the constraints, the gate invoking account's name, and report(tag ,message). Returns 0 when every check passed, 1 when a check found problems, 2 when the journal could not be read. @@ -27,10 +27,10 @@ def check(journal_bytes ,language ,constraint ,gate_invoking_account ,report): except Journal.JournalError as error: report("journal_unreadable" ,str(error)) return 2 - if data["command_language"] != language.NAME: - report("wrong_language" ,f"the journal is in {data['command_language']!r}; this guard checks {language.NAME!r}") + if data["instruction_language"] != language.NAME: + report("wrong_language" ,f"the journal is in {data['instruction_language']!r}; this guard checks {language.NAME!r}") return 1 - report("journal" ,f"{len(data['command_list'])} commands") + report("journal" ,f"{len(data['instruction_list'])} instructions") for name ,function in language.CHECK: problem_list = function(data ,constraint ,gate_invoking_account) for problem in problem_list: diff --git a/developer/authored/Man_In_Grey.copy/Interpreter.py b/developer/authored/Man_In_Grey.copy/Interpreter.py index 2cc9250..c76e11a 100644 --- a/developer/authored/Man_In_Grey.copy/Interpreter.py +++ b/developer/authored/Man_In_Grey.copy/Interpreter.py @@ -3,11 +3,11 @@ Interpreter - the general interpreter of Man_In_Grey. The interpreter executes a journal the guard has passed. It carries out -commands and does not judge them, with one exception: it checks again that +instructions and does not judge them, with one exception: it checks again that the journal is well formed, since it decodes the journal anyway, so that a malformed journal never reaches execution. -This module is a library. A command language's interpreter program calls +This module is a library. An instruction language's interpreter program calls interpret(); see interpreter_file.py. """ @@ -16,7 +16,7 @@ import Journal def interpret(journal_bytes ,language ,log): """ - Given the encoded journal, the command language module, and log(tag + Given the encoded journal, the instruction language module, and log(tag ,message). Returns 0 when the journal was executed, 1 when execution failed, 2 when the journal could not be read or is not well formed. """ @@ -25,8 +25,8 @@ def interpret(journal_bytes ,language ,log): except Journal.JournalError as error: log("journal_unreadable" ,str(error)) return 2 - if data["command_language"] != language.NAME: - log("wrong_language" ,f"the journal is in {data['command_language']!r}; this interpreter executes {language.NAME!r}") + if data["instruction_language"] != language.NAME: + log("wrong_language" ,f"the journal is in {data['instruction_language']!r}; this interpreter executes {language.NAME!r}") return 2 problem_list = language.check_wellformed(data) for problem in problem_list: diff --git a/developer/authored/Man_In_Grey.copy/Journal.py b/developer/authored/Man_In_Grey.copy/Journal.py index de83e7f..348441c 100644 --- a/developer/authored/Man_In_Grey.copy/Journal.py +++ b/developer/authored/Man_In_Grey.copy/Journal.py @@ -1,25 +1,25 @@ # -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*- """ -Journal - the journal of Man_In_Grey, independent of any command language. +Journal - the journal of Man_In_Grey, independent of any instruction language. -A composer places commands into a journal. A command is data: the name of -the command, its arguments, and the composer that placed it. A command whose +A composer places instructions into a journal. An instruction is data: the name of +the instruction, its arguments, and the composer that placed it. An instruction whose execution produces a value, such as an open file, is given a symbol when it -is placed. A later command names that symbol where it needs the value. The -value does not exist until the journal interpreter executes the command, so +is placed. A later instruction names that symbol where it needs the value. The +value does not exist until the journal interpreter executes the instruction, so the composer holds the symbol and nothing more. The journal travels to the journal interpreter encoded in CBOR. CBOR carries strings and byte strings with their lengths, so content is never scanned for marks, and decoding it makes maps, lists, strings and bytes, never code. -A command language is defined elsewhere, as a module with a composer half, +An instruction language is defined elsewhere, as a module with a composer half, its journal interface, and an interpreter half, its guard and executor. """ import cbor2 -JOURNAL_VERSION = 2 +JOURNAL_VERSION = 3 def is_control(character): @@ -41,8 +41,8 @@ class JournalError(Exception): class Symbol: """ - The value a command will produce when it is executed. A symbol is the - index of the command that produces it, counted from 0. It is encoded in the + The value an instruction will produce when it is executed. A symbol is the + index of the instruction that produces it, counted from 0. It is encoded in the journal as {"symbol": index}. """ __slots__ = ("index" ,) @@ -61,17 +61,17 @@ class Symbol: return isinstance(value ,dict) and set(value) == {"symbol"} and isinstance(value["symbol"] ,int) -class Command: - __slots__ = ("command" ,"argument" ,"composer") +class Instruction: + __slots__ = ("name" ,"argument" ,"composer") - def __init__(self ,command ,argument ,composer): - self.command = command + def __init__(self ,name ,argument ,composer): + self.name = name self.argument = argument self.composer = composer def as_data(self): return { - "command": self.command + "name": self.name ,"argument": {k: (v.as_data() if isinstance(v ,Symbol) else v) for k ,v in self.argument.items()} ,"composer": self.composer } @@ -79,26 +79,26 @@ class Command: class Journal: """ - The commands of one run, in order, for one command language. + The instructions of one run, in order, for one instruction language. """ - def __init__(self ,command_language): - self.command_language = command_language + def __init__(self ,instruction_language): + self.instruction_language = instruction_language self.meta = {} - self.command_list = [] + self.instruction_list = [] self.composer = None - def place(self ,command ,argument): - """Place a command; returns its symbol.""" - self.command_list.append(Command(command ,dict(argument) ,self.composer)) - return Symbol(len(self.command_list) - 1) + def place(self ,name ,argument): + """Place an instruction; returns its symbol.""" + self.instruction_list.append(Instruction(name ,dict(argument) ,self.composer)) + return Symbol(len(self.instruction_list) - 1) def as_data(self): data = { "version": JOURNAL_VERSION - ,"command_language": self.command_language + ,"instruction_language": self.instruction_language ,"meta": dict(self.meta) - ,"command_list": [c.as_data() for c in self.command_list] + ,"instruction_list": [c.as_data() for c in self.instruction_list] } return data @@ -108,16 +108,16 @@ class Journal: def print(self ,file=None): import sys file = file or sys.stdout - if not self.command_list: + if not self.instruction_list: print("(the journal is empty)" ,file=file) - for index ,command in enumerate(self.command_list): - print(command_line(index ,command.as_data()) ,file=file) + for index ,instruction in enumerate(self.instruction_list): + print(instruction_line(index ,instruction.as_data()) ,file=file) def decode(journal_bytes): """ Decode a journal from CBOR into its data, checking only the shape common to - every command language. Raises JournalError. + every instruction language. Raises JournalError. """ try: data = cbor2.loads(journal_bytes) @@ -126,27 +126,27 @@ def decode(journal_bytes): if not isinstance(data ,dict): raise JournalError("the journal is not a map") if data.get("version") != JOURNAL_VERSION: - raise JournalError(f"journal version {data.get('version')!r}; this interpreter reads version {JOURNAL_VERSION}") - if not isinstance(data.get("command_language") ,str): - raise JournalError("the journal names no command language") - command_list = data.get("command_list") - if not isinstance(command_list ,list): - raise JournalError("the journal has no command list") - for index ,command in enumerate(command_list): - if not (isinstance(command ,dict) and isinstance(command.get("command") ,str) and isinstance(command.get("argument") ,dict)): - raise JournalError(f"command {index} is not a command") - for name ,value in command["argument"].items(): + raise JournalError(f"journal version {data.get('version')!r}; this Man_In_Grey reads version {JOURNAL_VERSION}") + if not isinstance(data.get("instruction_language") ,str): + raise JournalError("the journal names no instruction language") + instruction_list = data.get("instruction_list") + if not isinstance(instruction_list ,list): + raise JournalError("the journal has no instruction list") + for index ,instruction in enumerate(instruction_list): + if not (isinstance(instruction ,dict) and isinstance(instruction.get("name") ,str) and isinstance(instruction.get("argument") ,dict)): + raise JournalError(f"instruction {index} is not an instruction") + for name ,value in instruction["argument"].items(): if isinstance(value ,dict) and not Symbol.is_data(value): - raise JournalError(f"command {index}: argument {name} is neither a value nor a symbol") + raise JournalError(f"instruction {index}: argument {name} is neither a value nor a symbol") if Symbol.is_data(value) and not (0 <= value["symbol"] < index): - raise JournalError(f"command {index}: argument {name} names symbol {value['symbol']}, which no earlier command produces") + raise JournalError(f"instruction {index}: argument {name} names symbol {value['symbol']}, which no earlier instruction produces") return data -def command_line(index ,command): - """One line describing a command, for printing a journal.""" +def instruction_line(index ,instruction): + """One line describing an instruction, for printing a journal.""" part = [] - for name ,value in command["argument"].items(): + for name ,value in instruction["argument"].items(): if Symbol.is_data(value): part.append(f"{name}=@{value['symbol']}") elif isinstance(value ,(bytes ,bytearray)): @@ -155,5 +155,5 @@ def command_line(index ,command): part.append(f"{name}={value:04o}") else: part.append(f"{name}={value!r}") - composer = command.get("composer") - return f"@{index:<3} {command['command']:<8} {' '.join(part)}" + (f" ({composer})" if composer else "") + composer = instruction.get("composer") + return f"@{index:<3} {instruction['name']:<8} {' '.join(part)}" + (f" ({composer})" if composer else "") diff --git a/developer/authored/Man_In_Grey.copy/Man_In_Grey.py b/developer/authored/Man_In_Grey.copy/Man_In_Grey.py index 85fce20..86453ba 100755 --- a/developer/authored/Man_In_Grey.copy/Man_In_Grey.py +++ b/developer/authored/Man_In_Grey.copy/Man_In_Grey.py @@ -3,14 +3,14 @@ """ Man_In_Grey -The orchestrator. It runs composers, which place commands into one journal, +The orchestrator. It runs composers, which place instructions into one journal, and invokes a gate with the journal on its standard input. The gate starts the guard, which checks the journal against its constraints, and, when the guard passes the journal, the interpreter, which executes it. A composer is a Python file defining compose(prov ,journal_iface). prov -describes where the composer is; journal_iface places commands of the file -command language: open, write and unlink. +describes where the composer is; journal_iface places instructions of the file +instruction language: open, write and unlink. Usage: Man_In_Grey [option ...] help | -h print this message (also printed when given no arguments) @@ -32,7 +32,7 @@ gate invoking account or by root, and writable by no other account. Symbolic links in a composer directory are refused. Exit status: 0 when the run did what it was asked; 1 when the guard refused -the journal or a command failed to execute; 2 when the run could not start, +the journal or an instruction failed to execute; 2 when the run could not start, a composer failed, or the gate, guard or interpreter could not run. """ diff --git a/developer/authored/Man_In_Grey.copy/guard_file.py b/developer/authored/Man_In_Grey.copy/guard_file.py index a40096a..fd42913 100644 --- a/developer/authored/Man_In_Grey.copy/guard_file.py +++ b/developer/authored/Man_In_Grey.copy/guard_file.py @@ -1,10 +1,10 @@ #!/usr/bin/env -S python3 -B # -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*- """ -guard_file - the guard of the file command language. +guard_file - the guard of the file instruction language. -Started by the gate Man_In_Grey_file_gate, under the interpreter account, -with the journal on standard input. It checks the journal against the +Started by the gate Man_In_Grey_file_gate, with the gate's real and +effective uid, and with the journal on standard input. It checks the journal against the constraints, and says what it found on standard error, one line per finding, as : ; the first two lines are name: and version:. diff --git a/developer/authored/Man_In_Grey.copy/interpreter_file.py b/developer/authored/Man_In_Grey.copy/interpreter_file.py index b6586f4..bf5ea95 100644 --- a/developer/authored/Man_In_Grey.copy/interpreter_file.py +++ b/developer/authored/Man_In_Grey.copy/interpreter_file.py @@ -1,11 +1,12 @@ #!/usr/bin/env -S python3 -B # -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*- """ -interpreter_file - the interpreter of the file command language. +interpreter_file - the interpreter of the file instruction language. -Started by the gate Man_In_Grey_file_gate, under the interpreter account, -with the journal on standard input, once the guard has passed the journal. -It executes the journal's commands and writes one line to standard output +Started by the gate Man_In_Grey_file_gate, with the gate's real and +effective uid, and with the journal on standard input, once the guard has +passed the journal. +It executes the journal's instructions and writes one line to standard output for each thing done, as : . It checks again that the journal is well formed before executing it. It diff --git a/developer/authored/Man_In_Grey_gate.make/Gate.lib.c b/developer/authored/Man_In_Grey_gate.make/Gate.lib.c index 7a47fbb..6d30c38 100644 --- a/developer/authored/Man_In_Grey_gate.make/Gate.lib.c +++ b/developer/authored/Man_In_Grey_gate.make/Gate.lib.c @@ -1,5 +1,5 @@ /* - Man_In_Grey_gate·Gate - the gate of a Man_In_Grey command language. + Man_In_Grey_gate·Gate - the gate of a Man_In_Grey instruction language. A gate is a program of its own, made from this module by a three-line *.CLI.c whose main calls Man_In_Grey_gate·Gate·run with three things fixed @@ -7,10 +7,10 @@ path, the two paths relative to the gate itself. The gate invoking account can name neither program; who invokes a gate cannot choose what it runs. - Setuid and owned by the interpreter account, account B, a gate runs the - guard and the interpreter under account B while the real uid still names - the gate invoking account, account A. Not setuid, it runs them under - account A. + Setuid and owned by account B, a gate runs with the effective uid of + account B and the real uid of account A, the gate invoking account. Not + setuid, it keeps both uids of the program that invoked it. The guard and + the interpreter keep the gate's two uids. admit reads the journal on standard input, then: diff --git a/developer/authored/Man_In_Grey_gate.make/Man_In_Grey_file_gate.CLI.c b/developer/authored/Man_In_Grey_gate.make/Man_In_Grey_file_gate.CLI.c index 31e62c6..282f940 100644 --- a/developer/authored/Man_In_Grey_gate.make/Man_In_Grey_file_gate.CLI.c +++ b/developer/authored/Man_In_Grey_gate.make/Man_In_Grey_file_gate.CLI.c @@ -1,5 +1,5 @@ /* - Man_In_Grey_file_gate - the gate of the file command language: the general + Man_In_Grey_file_gate - the gate of the file instruction language: the general gate with its guard's name and the guard's and interpreter's paths fixed. */ #include "Gate.lib.c" diff --git a/tester/authored/write_file/run b/tester/authored/write_file/run index 4cc7c4a..41a44e3 100755 --- a/tester/authored/write_file/run +++ b/tester/authored/write_file/run @@ -3,7 +3,7 @@ """ run -The write_file test of Man_In_Grey's file command language. It runs the +The write_file test of Man_In_Grey's file instruction language. It runs the promoted Man_In_Grey, from $REPO_HOME/consumer, through a gate that is not setuid, with constraints it writes for the files it expects, and checks: @@ -18,7 +18,7 @@ setuid, with constraints it writes for the files it expects, and checks: refused orchestrate composer_refused/: a journal opening one allowed file and one the constraints do not allow is refused whole by the guard's constraint check argument orchestrate composer_argument/: a composer giving a wrong mode fails when it - places the command, and no journal reaches the gate + places the instruction, and no journal reaches the gate writable orchestrate a composer directory holding a composer other accounts can write: the orchestrator refuses to run it check_only orchestrate composer/ with check_only: the guard approves the journal, and the