Mirror the source tree onto the system disk
A list of files in a makefile goes stale the moment somebody adds a program and forgets to name it, and what they forgot is invisible until they go looking for it on the machine. So SplitDisk gained a mirror command and the disk rule is one line: putting a file where the others live is now the whole of putting it on the disk. EVERY FILE GOES THROUGH put AND EVERY DIRECTORY THROUGH mkdir. That is the point of it - mirror adds a walk and no filesystem code at all, so anything the format refuses here it refuses everywhere, in the same words. What is new is the walk, and the walk is what the six checks in Tests/disk.sh are about: that it goes all the way down, that it leaves dotfiles and named directories behind, and that a name too long stops it. REFUSED RATHER THAN SKIPPED, because a disk quietly missing a file is the exact failure a mirror exists to prevent. Which meant four sources had to be renamed - a directory entry holds 22 characters and they were 23, 23, 24 and 29: 16bitSegmentedSieve.asm -> 16bitSieve.asm 16bitSegmentedSieveModern.asm -> 16bitSieveModern.asm consoleInterruptTest.asm -> consoleInterrupt.asm controllerWriteTest.asm -> controllerWrite.asm The test names in the manifest are unchanged, since those are identifiers and every recorded result is filed under them. Only where the source lives has moved. The entries are sorted before anything is written. readdir hands them back in whatever order the host filesystem feels like, and a disk image that comes out different from one run to the next is an image no test could compare against another. The disk grew from one megabyte to four and from 192 directory entries to 1,024. The sources are 2,850 blocks and the mirror filled the old directory on its first run, which is a thing that should not need thinking about again. The Tests fixture disk is deliberately NOT mirrored. It is a controlled fixture with known contents, and the shipped disk is the one meant to be useful; they want different things. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
co-authored by
Claude Opus 5
parent
ff4b025058
commit
0852666e73
@@ -1,7 +1,7 @@
|
|||||||
; The 16-bit segmented sieve rewritten for SplitBit's four-Data-Pointer ISA.
|
; The 16-bit segmented sieve rewritten for SplitBit's four-Data-Pointer ISA.
|
||||||
;
|
;
|
||||||
; This deliberately implements the same algorithm and emits the same text as
|
; This deliberately implements the same algorithm and emits the same text as
|
||||||
; 16bitSegmentedSieve.asm, making the two versions useful as a direct comparison.
|
; 16bitSieve.asm, making the two versions useful as a direct comparison.
|
||||||
; DP0 walks PrimeStates, DP1 holds Page, DP2 walks Segment, and volatile DP3
|
; DP0 walks PrimeStates, DP1 holds Page, DP2 walks Segment, and volatile DP3
|
||||||
; marks multiples. CALL preserves the first three pointers automatically.
|
; marks multiples. CALL preserves the first three pointers automatically.
|
||||||
|
|
||||||
|
|||||||
@@ -93,6 +93,31 @@ The generated files are kept under `Programs/build/`:
|
|||||||
- `CosmOS/Apps/*.sbx` are loadable application images.
|
- `CosmOS/Apps/*.sbx` are loadable application images.
|
||||||
- `cosmos.img` is the SBFS disk containing those applications.
|
- `cosmos.img` is the SBFS disk containing those applications.
|
||||||
|
|
||||||
|
**The disk carries every source in `Programs/`, mirrored.** Not a list kept in the makefile -
|
||||||
|
a list goes stale the moment somebody adds a program and forgets to name it, and what they
|
||||||
|
forgot is invisible until they go looking for it on the machine. Putting a file where the
|
||||||
|
others live is the whole of putting it on the disk.
|
||||||
|
|
||||||
|
That matters most for the things nobody thought worth shipping a binary of. A demo that is
|
||||||
|
not interesting enough to build by default is still worth having the source of, because the
|
||||||
|
machine can build it:
|
||||||
|
|
||||||
|
```
|
||||||
|
> cd /Source/Examples
|
||||||
|
/Source/Examples> Asm colours.asm
|
||||||
|
wrote colours.bin: program 114, data 86, labels 8
|
||||||
|
```
|
||||||
|
|
||||||
|
Two things are left behind. `build`, because what a project builds is not what it wrote. And
|
||||||
|
anything whose name is longer than a directory entry holds, which is **refused rather than
|
||||||
|
skipped**: a disk quietly missing a file is exactly the failure a mirror exists to prevent,
|
||||||
|
so the build stops and says which name to shorten.
|
||||||
|
|
||||||
|
`/Lib` still holds the library sources separately, because that is where an `#Include` looks
|
||||||
|
after looking beside the file that asked. The same files therefore appear twice - once as
|
||||||
|
what a program includes, once as part of the source tree - and that is the difference between
|
||||||
|
an installed library and a copy of the source.
|
||||||
|
|
||||||
The disk is rebuilt from scratch when its applications change, so its contents describe
|
The disk is rebuilt from scratch when its applications change, so its contents describe
|
||||||
the current source tree rather than accumulating files left by older builds.
|
the current source tree rather than accumulating files left by older builds.
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
; The 16-bit segmented sieve rewritten for SplitBit's four-Data-Pointer ISA.
|
; The 16-bit segmented sieve rewritten for SplitBit's four-Data-Pointer ISA.
|
||||||
;
|
;
|
||||||
; This deliberately implements the same algorithm and emits the same text as
|
; This deliberately implements the same algorithm and emits the same text as
|
||||||
; 16bitSegmentedSieve.asm, making the two versions useful as a direct comparison.
|
; 16bitSieve.asm, making the two versions useful as a direct comparison.
|
||||||
; DP0 walks PrimeStates, DP1 holds Page, DP2 walks Segment, and volatile DP3
|
; DP0 walks PrimeStates, DP1 holds Page, DP2 walks Segment, and volatile DP3
|
||||||
; marks multiples. CALL preserves the first three pointers automatically.
|
; marks multiples. CALL preserves the first three pointers automatically.
|
||||||
|
|
||||||
+23
-28
@@ -138,7 +138,15 @@ $(COSMOS_DISK): $(APPS) $(NATIVE_ASM) $(COSMOS) $(STAGE2) testPrograms/stringKey
|
|||||||
@# one it has to be handed. Forty blocks a slot and two slots: stage two is about
|
@# one it has to be handed. Forty blocks a slot and two slots: stage two is about
|
||||||
@# eight thousand bytes, and the second slot is what makes replacing it survivable,
|
@# eight thousand bytes, and the second slot is what makes replacing it survivable,
|
||||||
@# since raw blocks have no name and so nothing to rename.
|
@# since raw blocks have no name and so nothing to rename.
|
||||||
$(DISKTOOL) format $@ 4096 24 40
|
@# ---- Room for the whole source tree ----
|
||||||
|
@#
|
||||||
|
@# Sixteen thousand blocks is four megabytes, which is absurd for a machine with 128K
|
||||||
|
@# of memory and exactly right for a disk: the sources alone are 2,850 blocks and the
|
||||||
|
@# point of mirroring them is that nobody has to think about it again when they add
|
||||||
|
@# one. A hundred and twenty-eight directory blocks is 1,024 entries against the 149
|
||||||
|
@# the tree has now, for the same reason - it was 24, which is 192, and the mirror
|
||||||
|
@# filled it on its first run.
|
||||||
|
$(DISKTOOL) format $@ 16384 128 40
|
||||||
$(DISKTOOL) boot $@ $(STAGE2) 0
|
$(DISKTOOL) boot $@ $(STAGE2) 0
|
||||||
@# THREE DIRECTORIES, WHICH IS WHAT A CLEAN INSTALL LOOKS LIKE: what you run, what you
|
@# THREE DIRECTORIES, WHICH IS WHAT A CLEAN INSTALL LOOKS LIKE: what you run, what you
|
||||||
@# assemble, and what those include. It was thirty nine files in one list with
|
@# assemble, and what those include. It was thirty nine files in one list with
|
||||||
@@ -164,37 +172,24 @@ $(COSMOS_DISK): $(APPS) $(NATIVE_ASM) $(COSMOS) $(STAGE2) testPrograms/stringKey
|
|||||||
@for app in $(APPS); do \
|
@for app in $(APPS); do \
|
||||||
$(DISKTOOL) put $@ $$app /Apps/`basename $$app` >/dev/null || exit 1; done
|
$(DISKTOOL) put $@ $$app /Apps/`basename $$app` >/dev/null || exit 1; done
|
||||||
$(DISKTOOL) put $@ $(NATIVE_ASM) /Apps/Asm.sbx
|
$(DISKTOOL) put $@ $(NATIVE_ASM) /Apps/Asm.sbx
|
||||||
@# What you assemble. All of it, because an assembler with nothing to assemble is a
|
@# ---- What you assemble: all of it ----
|
||||||
@# demonstration of nothing:
|
|
||||||
@#
|
@#
|
||||||
@# > cd /Source
|
@# MIRRORED RATHER THAN LISTED. A list in a makefile goes stale the moment somebody
|
||||||
@# /Source> Asm cosmos.asm the system it is running on
|
@# adds a program and forgets to name it here, and what they forgot is invisible until
|
||||||
@# /Source> Asm Asm.asm and the thing that built it
|
@# they go looking for it on the machine. Now putting a file where the others live is
|
||||||
|
@# the whole of putting it on the disk.
|
||||||
@#
|
@#
|
||||||
@# Both come out byte for byte what the host tool makes from the same source.
|
@# That matters most for the things nobody thought worth building an .sbx of. A demo
|
||||||
|
@# that is not interesting enough to ship as a binary is still worth having the source
|
||||||
|
@# of, because somebody curious can assemble it on the machine itself:
|
||||||
@#
|
@#
|
||||||
@# Keys.asm brings a vector of its own, so assembling it exercises the version two
|
@# > cd /Source/Examples
|
||||||
@# header and the Vector Segment: the loader installs its handler, the console
|
@# /Source/Examples> Asm colours.asm
|
||||||
@# interrupts into it, and the shell takes the vector back at exit.
|
|
||||||
@#
|
@#
|
||||||
@# strings.asm is the odd one out on purpose. It has no #Include and no #Base, so it
|
@# build is left behind, because what a project builds is not what it wrote. Anything
|
||||||
@# comes out as a boot image rather than a loadable program, and the difference between
|
@# with a name longer than a directory entry holds is refused rather than skipped: a
|
||||||
@# the two is visible on one disk.
|
@# disk quietly missing a file is the failure a mirror exists to prevent.
|
||||||
$(DISKTOOL) put $@ CosmOS/Source/cosmos.asm /Source/cosmos.asm
|
$(DISKTOOL) mirror $@ . /Source build
|
||||||
$(DISKTOOL) put $@ CosmOS/Assembler/Asm.asm /Source/Asm.asm
|
|
||||||
$(DISKTOOL) put $@ CosmOS/Assembler/readTest.asm /Source/readTest.asm
|
|
||||||
$(DISKTOOL) put $@ CosmOS/Assembler/tokenTest.asm /Source/tokenTest.asm
|
|
||||||
$(DISKTOOL) put $@ CosmOS/Apps/hello.asm /Source/hello.asm
|
|
||||||
$(DISKTOOL) put $@ CosmOS/Apps/Say.asm /Source/Say.asm
|
|
||||||
$(DISKTOOL) put $@ CosmOS/Apps/Keys.asm /Source/Keys.asm
|
|
||||||
$(DISKTOOL) put $@ testPrograms/stringKeyword.asm /Source/strings.asm
|
|
||||||
@# And the loader, so the machine can rebuild what starts it. Assembling stage2.asm on
|
|
||||||
@# the machine and writing the result into the other boot slot is the whole of a
|
|
||||||
@# self-hosted boot chain, and everything it includes is already in /Lib.
|
|
||||||
$(DISKTOOL) put $@ Boot/stage1.asm /Source/stage1.asm
|
|
||||||
$(DISKTOOL) put $@ Boot/stage2.asm /Source/stage2.asm
|
|
||||||
@# And what those include. Everything here is named by an #Include somewhere and by
|
|
||||||
@# nothing else, which is exactly what makes it a library rather than a source.
|
|
||||||
@for f in CosmOS/Source/console.asm CosmOS/Source/fileStream.asm \
|
@for f in CosmOS/Source/console.asm CosmOS/Source/fileStream.asm \
|
||||||
CosmOS/Source/sbfs.asm CosmOS/Source/services.asm CosmOS/Source/text.asm \
|
CosmOS/Source/sbfs.asm CosmOS/Source/services.asm CosmOS/Source/text.asm \
|
||||||
CosmOS/Source/config.asm \
|
CosmOS/Source/config.asm \
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
;
|
;
|
||||||
; There is a fourth bit, for whether the console interrupts on input, and nothing here
|
; There is a fourth bit, for whether the console interrupts on input, and nothing here
|
||||||
; sets it. Polling and interrupting are the two ways to get a byte and this is the one
|
; sets it. Polling and interrupting are the two ways to get a byte and this is the one
|
||||||
; about polling; consoleInterruptTest.asm is the other.
|
; about polling; consoleInterrupt.asm is the other.
|
||||||
;
|
;
|
||||||
; This runs with input from a file rather than a terminal, so key mode has no terminal to
|
; This runs with input from a file rather than a terminal, so key mode has no terminal to
|
||||||
; put into another state and the mode bit is the only thing that changes. That is on
|
; put into another state and the mode bit is the only thing that changes. That is on
|
||||||
|
|||||||
@@ -104,6 +104,9 @@ make run-cosmos
|
|||||||
|
|
||||||
Then `dir` to see what is there, `load Snake.sbx` and `run` to play something, or `load Asm.sbx` and `run cosmos.asm` to watch the machine build itself.
|
Then `dir` to see what is there, `load Snake.sbx` and `run` to play something, or `load Asm.sbx` and `run cosmos.asm` to watch the machine build itself.
|
||||||
|
|
||||||
|
Every source in `Programs/` is on that disk, under `/Source`, so anything not shipped as a
|
||||||
|
binary can still be assembled on the machine: `cd /Source/Examples` and `Asm colours.asm`.
|
||||||
|
|
||||||
`make run-voyager` boots the same disk on the machine with a screen instead of a terminal.
|
`make run-voyager` boots the same disk on the machine with a screen instead of a terminal.
|
||||||
Both targets depend on the disk, so a disk built before a change to the machine is rebuilt
|
Both targets depend on the disk, so a disk built before a change to the machine is rebuilt
|
||||||
rather than booted as it stands: **what is on a disk is whatever was built when the disk was
|
rather than booted as it stands: **what is on a disk is whatever was built when the disk was
|
||||||
|
|||||||
@@ -12,6 +12,10 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
// For mirroring a host directory onto a disk: walking one, and telling a directory from a
|
||||||
|
// file. POSIX rather than C, which is why the build asks for POSIX.1-2008 by name.
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
// Numbers on a SplitBit disk are most significant byte first, the same as everywhere
|
// Numbers on a SplitBit disk are most significant byte first, the same as everywhere
|
||||||
// else on the machine.
|
// else on the machine.
|
||||||
@@ -1108,6 +1112,137 @@ done:
|
|||||||
// disk - so the children of a deleted directory would reappear inside whatever took its
|
// disk - so the children of a deleted directory would reappear inside whatever took its
|
||||||
// place. Emptying it first is the only safe order, and making the caller do that is the
|
// place. Emptying it first is the only safe order, and making the caller do that is the
|
||||||
// smallest way to guarantee it.
|
// smallest way to guarantee it.
|
||||||
|
// ---- Mirroring a host directory onto a disk ----
|
||||||
|
//
|
||||||
|
// So that putting a new program where the others live is all it takes to have it on the
|
||||||
|
// machine. A list of files in a makefile is a list that goes stale the moment somebody adds
|
||||||
|
// something and forgets, and the thing they forgot is invisible until they look for it.
|
||||||
|
//
|
||||||
|
// EVERY FILE GOES THROUGH put AND EVERY DIRECTORY THROUGH mkdir, which is the point: this
|
||||||
|
// adds a walk and no filesystem code at all, so anything the format refuses here it refuses
|
||||||
|
// everywhere, in exactly the same words.
|
||||||
|
|
||||||
|
static int compareEntries(const void *left, const void *right) {
|
||||||
|
return strcmp(*(const char *const *)left, *(const char *const *)right);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mirrorDirectory(const char *path, const char *hostDir, const char *diskDir,
|
||||||
|
int skipCount, char *const skips[]) {
|
||||||
|
DIR *open = opendir(hostDir);
|
||||||
|
if (open == NULL) {
|
||||||
|
fprintf(stderr, "Error: Couldn't read the directory \"%s\".\n", hostDir);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Read the names first, and sort them ----
|
||||||
|
//
|
||||||
|
// readdir hands them back in whatever order the host filesystem feels like, and a disk
|
||||||
|
// image that comes out different from one run to the next is an image no test can
|
||||||
|
// compare against another. Sorted, the same tree always makes the same disk.
|
||||||
|
char **names = NULL;
|
||||||
|
size_t count = 0, room = 0;
|
||||||
|
const struct dirent *entry;
|
||||||
|
while ((entry = readdir(open)) != NULL) {
|
||||||
|
// Nothing beginning with a dot: that is . and .. and every editor's leavings, and
|
||||||
|
// none of it is source anybody wants on the machine.
|
||||||
|
if (entry->d_name[0] == '.') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int skipped = 0;
|
||||||
|
for (int i = 0; i < skipCount; i++) {
|
||||||
|
if (strcmp(entry->d_name, skips[i]) == 0) {
|
||||||
|
skipped = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (skipped) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (count == room) {
|
||||||
|
room = room ? room * 2 : 32;
|
||||||
|
char **grown = realloc(names, room * sizeof(*names));
|
||||||
|
if (grown == NULL) {
|
||||||
|
fprintf(stderr, "Error: Out of memory reading \"%s\".\n", hostDir);
|
||||||
|
closedir(open);
|
||||||
|
for (size_t i = 0; i < count; i++) free(names[i]);
|
||||||
|
free(names);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
names = grown;
|
||||||
|
}
|
||||||
|
names[count] = strdup(entry->d_name);
|
||||||
|
if (names[count] == NULL) {
|
||||||
|
fprintf(stderr, "Error: Out of memory reading \"%s\".\n", hostDir);
|
||||||
|
closedir(open);
|
||||||
|
for (size_t i = 0; i < count; i++) free(names[i]);
|
||||||
|
free(names);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
closedir(open);
|
||||||
|
qsort(names, count, sizeof(*names), compareEntries);
|
||||||
|
|
||||||
|
int failed = 0;
|
||||||
|
for (size_t i = 0; i < count; i++) {
|
||||||
|
char hostChild[1024];
|
||||||
|
char diskChild[1024];
|
||||||
|
if (snprintf(hostChild, sizeof(hostChild), "%s/%s", hostDir, names[i])
|
||||||
|
>= (int)sizeof(hostChild)
|
||||||
|
|| snprintf(diskChild, sizeof(diskChild), "%s/%s", diskDir, names[i])
|
||||||
|
>= (int)sizeof(diskChild)) {
|
||||||
|
fprintf(stderr, "Error: \"%s/%s\" makes a path too long to follow.\n",
|
||||||
|
hostDir, names[i]);
|
||||||
|
failed = 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A NAME TOO LONG IS AN ERROR RATHER THAN A SKIP. Leaving it off would mean a build
|
||||||
|
// that looks like it worked and a disk quietly missing a program, which is the exact
|
||||||
|
// failure a mirror exists to prevent. Twenty-two bytes is what a directory entry
|
||||||
|
// holds, and the fix is to call the file something shorter.
|
||||||
|
if (strlen(names[i]) > SBFS_NAME_BYTES) {
|
||||||
|
fprintf(stderr, "Error: \"%s\" is %zu characters, and a name holds %d.\n",
|
||||||
|
names[i], strlen(names[i]), SBFS_NAME_BYTES);
|
||||||
|
failed = 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct stat about;
|
||||||
|
if (stat(hostChild, &about) != 0) {
|
||||||
|
fprintf(stderr, "Error: Couldn't look at \"%s\".\n", hostChild);
|
||||||
|
failed = 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (S_ISDIR(about.st_mode)) {
|
||||||
|
if (commandMakeDirectory(path, diskChild)
|
||||||
|
|| mirrorDirectory(path, hostChild, diskChild, skipCount, skips)) {
|
||||||
|
failed = 1;
|
||||||
|
}
|
||||||
|
} else if (S_ISREG(about.st_mode)) {
|
||||||
|
if (commandPut(path, hostChild, diskChild)) {
|
||||||
|
failed = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Anything else - a socket, a device, whatever a host has - is not a thing this
|
||||||
|
// filesystem has a way to be, so it is passed over without comment.
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < count; i++) {
|
||||||
|
free(names[i]);
|
||||||
|
}
|
||||||
|
free(names);
|
||||||
|
return failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int commandMirror(const char *path, const char *hostDir, const char *diskDir,
|
||||||
|
int skipCount, char *const skips[]) {
|
||||||
|
struct stat about;
|
||||||
|
if (stat(hostDir, &about) != 0 || !S_ISDIR(about.st_mode)) {
|
||||||
|
fprintf(stderr, "Error: \"%s\" is not a directory to mirror.\n", hostDir);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return mirrorDirectory(path, hostDir, diskDir, skipCount, skips);
|
||||||
|
}
|
||||||
|
|
||||||
static int commandRemoveDirectory(const char *path, const char *name) {
|
static int commandRemoveDirectory(const char *path, const char *name) {
|
||||||
FILE *image = openImage(path, "r+b");
|
FILE *image = openImage(path, "r+b");
|
||||||
if (image == NULL) {
|
if (image == NULL) {
|
||||||
@@ -1162,6 +1297,8 @@ static void printUsage(const char *program) {
|
|||||||
printf(" delete <image> <path> Remove a file.\n");
|
printf(" delete <image> <path> Remove a file.\n");
|
||||||
printf(" mkdir <image> <path> Make a directory.\n");
|
printf(" mkdir <image> <path> Make a directory.\n");
|
||||||
printf(" rmdir <image> <path> Remove an empty one.\n");
|
printf(" rmdir <image> <path> Remove an empty one.\n");
|
||||||
|
printf(" mirror <image> <dir> <path> [skip...] Copy a whole host directory onto it,\n");
|
||||||
|
printf(" leaving behind anything named in skip.\n");
|
||||||
printf("\n");
|
printf("\n");
|
||||||
printf("Blocks are %d bytes. A name may be %d characters, and a path is names with\n",
|
printf("Blocks are %d bytes. A name may be %d characters, and a path is names with\n",
|
||||||
SBFS_BLOCK_BYTES, SBFS_NAME_BYTES);
|
SBFS_BLOCK_BYTES, SBFS_NAME_BYTES);
|
||||||
@@ -1256,6 +1393,16 @@ int main(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
return commandGet(path, argv[3], (argc > 4) ? argv[4] : argv[3]);
|
return commandGet(path, argv[3], (argc > 4) ? argv[4] : argv[3]);
|
||||||
}
|
}
|
||||||
|
if (strcmp(command, "mirror") == 0) {
|
||||||
|
if (argc < 5) {
|
||||||
|
fprintf(stderr, "Error: mirror needs a directory to copy and somewhere to put"
|
||||||
|
" it.\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
// Anything after those is a name to leave behind, which is how a project keeps what
|
||||||
|
// it builds out of what it wrote.
|
||||||
|
return commandMirror(path, argv[3], argv[4], argc - 5, &argv[5]);
|
||||||
|
}
|
||||||
if (strcmp(command, "delete") == 0) {
|
if (strcmp(command, "delete") == 0) {
|
||||||
if (argc < 4) {
|
if (argc < 4) {
|
||||||
fprintf(stderr, "Error: delete needs the name of a file on the disk.\n");
|
fprintf(stderr, "Error: delete needs the name of a file on the disk.\n");
|
||||||
|
|||||||
+41
-1
@@ -76,7 +76,7 @@ for f in empty.bin one.bin exact.bin part.bin whole.bin; do
|
|||||||
check "$f comes back byte for byte" roundTrip "$f"
|
check "$f comes back byte for byte" roundTrip "$f"
|
||||||
done
|
done
|
||||||
|
|
||||||
refuses "refuse a name of 29 characters" "$TOOL" put work.img part.bin 16bitSegmentedSieveModern.asm
|
refuses "refuse a name of 29 characters" "$TOOL" put work.img part.bin twentyNineCharactersLong.asm
|
||||||
refuses "refuse a duplicate name" "$TOOL" put work.img one.bin
|
refuses "refuse a duplicate name" "$TOOL" put work.img one.bin
|
||||||
refuses "refuse a file that is not there" "$TOOL" get work.img nosuch.bin out.bin
|
refuses "refuse a file that is not there" "$TOOL" get work.img nosuch.bin out.bin
|
||||||
check "delete" "$TOOL" delete work.img one.bin
|
check "delete" "$TOOL" delete work.img one.bin
|
||||||
@@ -228,6 +228,46 @@ check "a fresh disk is settled" python3 -c "
|
|||||||
import sys
|
import sys
|
||||||
sys.exit(0 if open('plain.img','rb').read()[17] == 0 else 1)"
|
sys.exit(0 if open('plain.img','rb').read()[17] == 0 else 1)"
|
||||||
|
|
||||||
|
# ---- Mirroring a host directory ----
|
||||||
|
#
|
||||||
|
# What the system disk is built with. Every file goes through put and every directory
|
||||||
|
# through mkdir, so this is a walk over machinery already checked above - what wants
|
||||||
|
# checking is the walk: that it goes all the way down, that it leaves behind what it was
|
||||||
|
# told to, and that it REFUSES a name the format cannot hold rather than skipping it, since
|
||||||
|
# a disk quietly missing a file is the failure a mirror exists to prevent.
|
||||||
|
mkdir -p tree/inner/deeper tree/leave
|
||||||
|
printf 'top' > tree/top.txt
|
||||||
|
printf 'inner' > tree/inner/middle.txt
|
||||||
|
printf 'deep' > tree/inner/deeper/bottom.txt
|
||||||
|
printf 'not this' > tree/leave/ignored.txt
|
||||||
|
: > tree/.hidden
|
||||||
|
|
||||||
|
"$TOOL" format mirror.img 256 8 >/dev/null
|
||||||
|
check "mirror a directory tree" "$TOOL" mirror mirror.img tree /
|
||||||
|
|
||||||
|
"$TOOL" list mirror.img > mirrored.txt 2>&1
|
||||||
|
grep -q '/inner/deeper/bottom.txt' mirrored.txt \
|
||||||
|
&& { PASS=$((PASS + 1)); printf " [%sok %s] %s\n" "$GREEN" "$RESET" "it goes all the way down"; } \
|
||||||
|
|| { FAIL=$((FAIL + 1)); FAILED_NAMES+=("depth"); printf " [%sFAIL%s] %s\n" "$RED" "$RESET" "it goes all the way down"; }
|
||||||
|
grep -q 'hidden' mirrored.txt \
|
||||||
|
&& { FAIL=$((FAIL + 1)); FAILED_NAMES+=("hidden"); printf " [%sFAIL%s] %s\n" "$RED" "$RESET" "and leaves dotfiles behind"; } \
|
||||||
|
|| { PASS=$((PASS + 1)); printf " [%sok %s] %s\n" "$GREEN" "$RESET" "and leaves dotfiles behind"; }
|
||||||
|
|
||||||
|
# Named on the command line, which is how a project keeps what it builds out of what it
|
||||||
|
# wrote.
|
||||||
|
"$TOOL" format skipped.img 256 8 >/dev/null
|
||||||
|
check "mirror with something left out" "$TOOL" mirror skipped.img tree / leave
|
||||||
|
"$TOOL" list skipped.img > skipped.txt 2>&1
|
||||||
|
grep -q 'ignored.txt' skipped.txt \
|
||||||
|
&& { FAIL=$((FAIL + 1)); FAILED_NAMES+=("skip"); printf " [%sFAIL%s] %s\n" "$RED" "$RESET" "and the skipped one is not there"; } \
|
||||||
|
|| { PASS=$((PASS + 1)); printf " [%sok %s] %s\n" "$GREEN" "$RESET" "and the skipped one is not there"; }
|
||||||
|
|
||||||
|
# Twenty-three characters, one more than a directory entry holds.
|
||||||
|
printf 'too long' > tree/aNameOfTwentyThreeChars
|
||||||
|
"$TOOL" format refused.img 256 8 >/dev/null
|
||||||
|
refuses "a name too long stops the mirror" "$TOOL" mirror refused.img tree /
|
||||||
|
rm -f tree/aNameOfTwentyThreeChars
|
||||||
|
|
||||||
echo
|
echo
|
||||||
if [ "$FAIL" -eq 0 ]; then
|
if [ "$FAIL" -eq 0 ]; then
|
||||||
echo "All $PASS disk tool checks passed."
|
echo "All $PASS disk tool checks passed."
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ Programs/CosmOS/Source/text.asm redundant-setd 2
|
|||||||
Programs/Loader/loader.asm redundant-assignment 1
|
Programs/Loader/loader.asm redundant-assignment 1
|
||||||
Programs/testPrograms/branchTest.asm redundant-assignment 1
|
Programs/testPrograms/branchTest.asm redundant-assignment 1
|
||||||
Programs/testPrograms/branchTest.asm redundant-ccf 1
|
Programs/testPrograms/branchTest.asm redundant-ccf 1
|
||||||
Programs/testPrograms/controllerWriteTest.asm branch-to-next 1
|
Programs/testPrograms/controllerWrite.asm branch-to-next 1
|
||||||
Programs/testPrograms/diagnostics/duplicateLabel.asm branch-to-next 1
|
Programs/testPrograms/diagnostics/duplicateLabel.asm branch-to-next 1
|
||||||
Programs/testPrograms/diskTest.asm redundant-assignment 1
|
Programs/testPrograms/diskTest.asm redundant-assignment 1
|
||||||
Programs/testPrograms/dispatchTest.asm branch-to-next 1
|
Programs/testPrograms/dispatchTest.asm branch-to-next 1
|
||||||
|
|||||||
+4
-4
@@ -46,10 +46,10 @@ printHello | Examples/printHello.asm | run | -
|
|||||||
32bitFibonacci | Examples/Fibonacci/32bitFibonacci.asm | run | - | -
|
32bitFibonacci | Examples/Fibonacci/32bitFibonacci.asm | run | - | -
|
||||||
colours | Examples/colours.asm | run | - | -
|
colours | Examples/colours.asm | run | - | -
|
||||||
8bitSieve | Examples/primeSieve/8bitSieve.asm | run | - | -
|
8bitSieve | Examples/primeSieve/8bitSieve.asm | run | - | -
|
||||||
16bitSegmentedSieve | Examples/primeSieve/16bitSegmentedSieve.asm | run | - | -
|
16bitSegmentedSieve | Examples/primeSieve/16bitSieve.asm | run | - | -
|
||||||
# The four pointer rewrite. It emits exactly the same primes as the line above, which
|
# The four pointer rewrite. It emits exactly the same primes as the line above, which
|
||||||
# is the whole point of keeping both: the pair is a direct before and after.
|
# is the whole point of keeping both: the pair is a direct before and after.
|
||||||
16bitSegmentedSieveModern | Examples/primeSieve/16bitSegmentedSieveModern.asm | run | - | -
|
16bitSegmentedSieveModern | Examples/primeSieve/16bitSieveModern.asm | run | - | -
|
||||||
mathTest | testPrograms/mathTest.asm | run | - | -
|
mathTest | testPrograms/mathTest.asm | run | - | -
|
||||||
printTest | testPrograms/printTest.asm | run | - | -
|
printTest | testPrograms/printTest.asm | run | - | -
|
||||||
int16print | Libraries/int16print.asm | run | - | -
|
int16print | Libraries/int16print.asm | run | - | -
|
||||||
@@ -73,7 +73,7 @@ moveQTest | testPrograms/moveQTest.asm | run | -
|
|||||||
# Reading and writing Program Memory, which the instruction set deliberately cannot do,
|
# Reading and writing Program Memory, which the instruction set deliberately cannot do,
|
||||||
# and the bank table that says what is reachable.
|
# and the bank table that says what is reachable.
|
||||||
controllerReadTest | testPrograms/controllerReadTest.asm | run | - | -
|
controllerReadTest | testPrograms/controllerReadTest.asm | run | - | -
|
||||||
controllerWriteTest | testPrograms/controllerWriteTest.asm | run | - | -
|
controllerWriteTest | testPrograms/controllerWrite.asm | run | - | -
|
||||||
# Block transfers: between banks, within one, overlapping, and one that is refused.
|
# Block transfers: between banks, within one, overlapping, and one that is refused.
|
||||||
blitTest | testPrograms/blitTest.asm | run | - | -
|
blitTest | testPrograms/blitTest.asm | run | - | -
|
||||||
# Reading a filesystem that the host tool wrote. The two are separate implementations of
|
# Reading a filesystem that the host tool wrote. The two are separate implementations of
|
||||||
@@ -196,7 +196,7 @@ consoleModeTest | testPrograms/consoleModeTest.asm | run | consoleMo
|
|||||||
# down the two things that make the feature usable rather than merely present: the end of
|
# down the two things that make the feature usable rather than merely present: the end of
|
||||||
# input raises the line once, so an interrupt-driven program is told when to stop, and a
|
# input raises the line once, so an interrupt-driven program is told when to stop, and a
|
||||||
# handler that does not read the byte is not called again, so nothing storms.
|
# handler that does not read the byte is not called again, so nothing storms.
|
||||||
consoleInterruptTest | testPrograms/consoleInterruptTest.asm | run | consoleInterruptTest.in | -
|
consoleInterruptTest | testPrograms/consoleInterrupt.asm | run | consoleInterruptTest.in | -
|
||||||
# Picking a typed line apart, which is how the shell understands anything. Includes a
|
# Picking a typed line apart, which is how the shell understands anything. Includes a
|
||||||
# string beginning with a zero: the assembler strips the quotes before deciding what a
|
# string beginning with a zero: the assembler strips the quotes before deciding what a
|
||||||
# token is, so such a string looked like a malformed literal and was refused.
|
# token is, so such a string looked like a malformed literal and was refused.
|
||||||
|
|||||||
Reference in New Issue
Block a user