mirror of
https://github.com/hsoft/collapseos.git
synced 2024-11-02 10:20:55 +11:00
Compare commits
5 Commits
d0545d555f
...
d4cdb659b4
Author | SHA1 | Date | |
---|---|---|---|
|
d4cdb659b4 | ||
|
7292d486dc | ||
|
7d184b9e70 | ||
|
d919a10265 | ||
|
b536d3bfd6 |
@ -41,11 +41,12 @@ path to giving Collapse OS a try.
|
||||
from this folder.
|
||||
* `recipes`: collection of recipes that assemble parts together on a specific
|
||||
machine.
|
||||
* `blk`: Collapse OS filesystem's content. See `000` for intro.
|
||||
* `doc`: User guide for when you've successfully installed Collapse OS.
|
||||
* `tools`: Tools for working with Collapse OS from "modern" environments. For
|
||||
example, tools for facilitating data upload to a Collapse OS machine
|
||||
through a serial port.
|
||||
* `emul`: Emulated applications, such as zasm and the shell.
|
||||
* `emul`: Emulated applications.
|
||||
* `tests`: Automated test suite for the whole project.
|
||||
|
||||
## Status
|
||||
|
15
blk/000
Normal file
15
blk/000
Normal file
@ -0,0 +1,15 @@
|
||||
Collapse OS file system
|
||||
|
||||
This is a Forth-style filesystems which is very simple. It is a
|
||||
list of 1024 bytes block, organised in 16 lines of 64 columns
|
||||
each. You refer to blocks by numbers. You show them with LIST.
|
||||
You interpret them with LOAD.
|
||||
|
||||
Conventions: When you see "(cont.)" at the bottom right of a
|
||||
block, it means that the next block continues the same kind of
|
||||
contents. This of course only work for informational text.
|
||||
|
||||
Block numbers are abbreviated with prefix "B". "BX" means
|
||||
"block X".
|
||||
|
||||
The master index of this filesystem is at B1.
|
@ -2,9 +2,9 @@
|
||||
operation while doing the right thing. Checks data integrity
|
||||
and ABORT on mismatch.
|
||||
)
|
||||
( a n -- )
|
||||
( n a -- )
|
||||
: AT28!
|
||||
2DUP C! SWAP
|
||||
2DUP C!
|
||||
( as long as writing operation is running, IO/6 will toggle at each
|
||||
read attempt. We know that write is finished when we read the same
|
||||
value twice. )
|
||||
|
2
emul/.gitignore
vendored
2
emul/.gitignore
vendored
@ -1,8 +1,6 @@
|
||||
/forth/stage1
|
||||
/forth/stage1dbg
|
||||
/forth/stage2
|
||||
/forth/stage2dbg
|
||||
/forth/forth
|
||||
/runbin/runbin
|
||||
/*/*-bin.h
|
||||
/*/*.bin
|
||||
|
@ -1,4 +1,4 @@
|
||||
TARGETS = runbin/runbin forth/forth
|
||||
TARGETS = forth/forth
|
||||
BIN2C = ../tools/bin2c
|
||||
# Those Forth source files are in a particular order
|
||||
BOOTSRCS = ./forth/conf.fs \
|
||||
@ -58,9 +58,6 @@ forth/stage2: forth/stage.c $(OBJS) forth/forth1-bin.h
|
||||
forth/forth: forth/forth.c $(OBJS) forth/forth1-bin.h
|
||||
$(CC) forth/forth.c $(OBJS) -o $@
|
||||
|
||||
runbin/runbin: runbin/runbin.c $(OBJS)
|
||||
$(CC) runbin/runbin.c $(OBJS) -o $@
|
||||
|
||||
libz80/libz80.o: libz80/z80.c
|
||||
$(MAKE) -C libz80/codegen opcodes
|
||||
$(CC) -Wall -ansi -g -c -o libz80/libz80.o libz80/z80.c
|
||||
|
@ -49,14 +49,6 @@ the power of a full Forth intepreter, including an assembler, to assemble
|
||||
Normally, running this step should yield the exact same `boot.bin` and
|
||||
`z80c.bin` as before, unless of course you've changed the source.
|
||||
|
||||
## runbin
|
||||
|
||||
This is a very simple tool that reads binary z80 code from stdin, loads it in
|
||||
memory starting at address 0 and then run the code until it halts. The exit
|
||||
code of the program is the value of `A` when the program halts.
|
||||
|
||||
This is used for unit tests.
|
||||
|
||||
## Problems?
|
||||
|
||||
If the libz80-wrapped zasm executable works badly (hangs, spew garbage, etc.),
|
||||
|
@ -1,33 +0,0 @@
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include "../emul.h"
|
||||
|
||||
/* runbin loads binary from stdin directly in memory address 0 then runs it
|
||||
* until it halts. The return code is the value of the register A at halt time.
|
||||
*/
|
||||
|
||||
static void iowr_stderr(uint8_t val)
|
||||
{
|
||||
fputc(val, stderr);
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
Machine *m = emul_init();
|
||||
m->iowr[0] = iowr_stderr;
|
||||
// read stdin in mem
|
||||
int i = 0;
|
||||
int c = getchar();
|
||||
while (c != EOF) {
|
||||
m->mem[i] = c & 0xff;
|
||||
i++;
|
||||
c = getchar();
|
||||
}
|
||||
if (!i) {
|
||||
fprintf(stderr, "No input, aborting\n");
|
||||
return 1;
|
||||
}
|
||||
emul_loop();
|
||||
return m->cpu.R1.br.A;
|
||||
}
|
||||
|
@ -159,7 +159,7 @@ PC ORG @ 4 + ! ( find )
|
||||
adjust. Because the compare loop pre-decrements, instead
|
||||
of DECing HL twice, we DEC it once. )
|
||||
HL DECss,
|
||||
L3 BSET ( inner )
|
||||
BEGIN, ( inner )
|
||||
( DE is a wordref, first step, do our len correspond? )
|
||||
HL PUSHqq, ( --> lvl 1 )
|
||||
DE PUSHqq, ( --> lvl 2 )
|
||||
@ -172,15 +172,15 @@ L3 BSET ( inner )
|
||||
DE DECss, ( Skip prev field. One less because we )
|
||||
DE DECss, ( pre-decrement )
|
||||
B C LDrr, ( loop C times )
|
||||
L5 BSET ( loop )
|
||||
BEGIN, ( loop )
|
||||
( pre-decrement for easier Z matching )
|
||||
DE DECss,
|
||||
HL DECss,
|
||||
LDA(DE),
|
||||
(HL) CPr,
|
||||
JRNZ, L6 FWR ( loopend )
|
||||
DJNZ, L5 BWR ( loop )
|
||||
L4 FSET L6 FSET ( loopend )
|
||||
JRNZ, L3 FWR ( loopend )
|
||||
DJNZ, AGAIN, ( loop )
|
||||
L4 FSET L3 FSET ( loopend )
|
||||
( At this point, Z is set if we have a match. In all cases,
|
||||
we want to pop HL and DE )
|
||||
DE POPqq, ( <-- lvl 2 )
|
||||
@ -201,14 +201,14 @@ L4 FSET L6 FSET ( loopend )
|
||||
( HL is prev field's addr. Is offset zero? )
|
||||
A D LDrr,
|
||||
E ORr,
|
||||
JRZ, L6 FWR ( noprev )
|
||||
IFZ, ( noprev )
|
||||
( get absolute addr from offset )
|
||||
( carry cleared from "or e" )
|
||||
DE SBCHLss,
|
||||
EXDEHL, ( result in DE )
|
||||
L6 FSET ( noprev )
|
||||
THEN, ( noprev )
|
||||
HL POPqq, ( <-- lvl 1 )
|
||||
JRNZ, L3 BWR ( inner, try to match again )
|
||||
JRNZ, AGAIN, ( inner, try to match again )
|
||||
( Z set? end of dict, unset Z )
|
||||
L1 FSET ( fail )
|
||||
A XORr,
|
||||
@ -260,8 +260,7 @@ PC ORG @ 0x1e + ! ( chkPS )
|
||||
HL DECss,
|
||||
HL DECss,
|
||||
HL DECss,
|
||||
A ORr, ( clear carry )
|
||||
SP SBCHLss,
|
||||
SP SUBHLss,
|
||||
HL POPqq,
|
||||
CNC RETcc, ( INITIAL_SP >= SP? good )
|
||||
JR, L1 BWR ( abortUnderflow )
|
||||
@ -269,8 +268,7 @@ PC ORG @ 0x1e + ! ( chkPS )
|
||||
L3 BSET ( chkRS )
|
||||
IX PUSHqq, HL POPqq,
|
||||
DE RS_ADDR LDddnn,
|
||||
A ORr, ( clear carry )
|
||||
DE SBCHLss,
|
||||
DE SUBHLss,
|
||||
CNC RETcc, ( IX >= RS_ADDR? good )
|
||||
JR, L1 BWR ( abortUnderflow )
|
||||
|
||||
|
@ -16,18 +16,16 @@
|
||||
|
||||
To avoid using dict memory in compilation targets, we
|
||||
pre-declare label variables here, which means we have a
|
||||
limited number of it. For now, 6 ought to be enough. )
|
||||
limited number of it. For now, 4 ought to be enough. )
|
||||
: L1 2 Z80AMEM+ ;
|
||||
: L2 4 Z80AMEM+ ;
|
||||
: L3 6 Z80AMEM+ ;
|
||||
: L4 8 Z80AMEM+ ;
|
||||
: L5 10 Z80AMEM+ ;
|
||||
: L6 12 Z80AMEM+ ;
|
||||
|
||||
: Z80A$
|
||||
( 59 == z80a's memory )
|
||||
H@ 0x59 RAM+ !
|
||||
14 ALLOT
|
||||
10 ALLOT
|
||||
;
|
||||
|
||||
( Splits word into msb/lsb, lsb being on TOS )
|
||||
@ -43,42 +41,6 @@
|
||||
: A, C, ;
|
||||
: A,, SPLITB A, A, ;
|
||||
|
||||
( There are 2 label types: backward and forward. For each
|
||||
type, there are two actions: set and write. Setting a label
|
||||
is declaring where it is. It has to be performed at the
|
||||
label's destination. Writing a label is writing its offset
|
||||
difference to the binary result. It has to be done right
|
||||
after a relative jump operation. Yes, labels are only for
|
||||
relative jumps.
|
||||
|
||||
For backward labels, set happens before write. For forward
|
||||
labels, write happen before set. The write operation writes
|
||||
a dummy placeholder, and then the set operation writes the
|
||||
offset at that placeholder's address.
|
||||
|
||||
Variable actions are expected to be called with labels in
|
||||
front of them. Example, "L2 FSET"
|
||||
|
||||
About that "1 -": z80 relative jumps record "e-2", that is,
|
||||
the offset that *counts the 2 bytes of the jump itself*.
|
||||
Because we set the label *after* the jump OP1 itself, that's
|
||||
1 byte that is taken care of. We still need to adjust by
|
||||
another byte before writing the offset.
|
||||
)
|
||||
|
||||
: BSET PC SWAP ! ;
|
||||
: BWR @ PC - 1 - A, ;
|
||||
( same as BSET, but we need to write a placeholder )
|
||||
: FWR BSET 0 A, ;
|
||||
: FSET
|
||||
@ DUP PC ( l l pc )
|
||||
-^ 1 - ( l off )
|
||||
( warning: l is a PC offset, not a mem addr! )
|
||||
SWAP ORG @ + ( off addr )
|
||||
C!
|
||||
;
|
||||
|
||||
|
||||
( "r" register constants )
|
||||
7 CONSTANT A
|
||||
0 CONSTANT B
|
||||
@ -375,3 +337,52 @@
|
||||
( Routines )
|
||||
( 29 == chkPS )
|
||||
: chkPS, 29 CALLnn, ;
|
||||
|
||||
( Flow
|
||||
|
||||
There are 2 label types: backward and forward. For each
|
||||
type, there are two actions: set and write. Setting a label
|
||||
is declaring where it is. It has to be performed at the
|
||||
label's destination. Writing a label is writing its offset
|
||||
difference to the binary result. It has to be done right
|
||||
after a relative jump operation. Yes, labels are only for
|
||||
relative jumps.
|
||||
|
||||
For backward labels, set happens before write. For forward
|
||||
labels, write happen before set. The write operation writes
|
||||
a dummy placeholder, and then the set operation writes the
|
||||
offset at that placeholder's address.
|
||||
|
||||
Variable actions are expected to be called with labels in
|
||||
front of them. Example, "L2 FSET"
|
||||
|
||||
About that "1 -": z80 relative jumps record "e-2", that is,
|
||||
the offset that *counts the 2 bytes of the jump itself*.
|
||||
Because we set the label *after* the jump OP1 itself, that's
|
||||
1 byte that is taken care of. We still need to adjust by
|
||||
another byte before writing the offset.
|
||||
)
|
||||
|
||||
( Place BEGIN, where you want to jump back and AGAIN after
|
||||
a relative jump operator. Just like BSET and BWR. )
|
||||
: BEGIN, PC ;
|
||||
: AGAIN, PC - 1 - A, ;
|
||||
|
||||
: BSET PC SWAP ! ;
|
||||
: BWR @ AGAIN, ;
|
||||
( same as BSET, but we need to write a placeholder )
|
||||
: FJR, PC 0 A, ;
|
||||
: IFZ, JRZ, FJR, ;
|
||||
: IFNZ, JRNZ, FJR, ;
|
||||
: IFC, JRC, FJR, ;
|
||||
: IFNC, JRNC, FJR, ;
|
||||
: THEN,
|
||||
DUP PC ( l l pc )
|
||||
-^ 1 - ( l off )
|
||||
( warning: l is a PC offset, not a mem addr! )
|
||||
SWAP ORG @ + ( off addr )
|
||||
C!
|
||||
;
|
||||
: FWR BSET 0 A, ;
|
||||
: FSET @ THEN, ;
|
||||
|
||||
|
@ -149,10 +149,10 @@ CODE NOT
|
||||
A L LDrr,
|
||||
H ORr,
|
||||
HL 0 LDddnn,
|
||||
JRNZ, L1 FWR ( skip )
|
||||
IFNZ, ( skip )
|
||||
( false, make 1 )
|
||||
HL INCss,
|
||||
L1 FSET ( skip )
|
||||
THEN, ( skip )
|
||||
HL PUSHqq,
|
||||
;CODE
|
||||
|
||||
@ -168,8 +168,7 @@ CODE -
|
||||
DE POPqq,
|
||||
HL POPqq,
|
||||
chkPS,
|
||||
A ORr,
|
||||
DE SBCHLss,
|
||||
DE SUBHLss,
|
||||
HL PUSHqq,
|
||||
;CODE
|
||||
|
||||
@ -204,17 +203,17 @@ CODE /MOD
|
||||
A B LDrr,
|
||||
B 16 LDrn,
|
||||
HL 0 LDddnn,
|
||||
L1 BSET ( loop )
|
||||
BEGIN, ( loop )
|
||||
SCF,
|
||||
C RLr,
|
||||
RLA,
|
||||
HL ADCHLss,
|
||||
DE SBCHLss,
|
||||
JRNC, L2 FWR ( skip )
|
||||
IFNC, ( skip )
|
||||
DE ADDHLss,
|
||||
C DECr,
|
||||
L2 FSET ( skip )
|
||||
DJNZ, L1 BWR ( loop )
|
||||
THEN, ( skip )
|
||||
DJNZ, AGAIN, ( loop )
|
||||
B A LDrr,
|
||||
HL PUSHqq,
|
||||
BC PUSHqq,
|
||||
@ -316,16 +315,16 @@ CODE SCMP
|
||||
DE POPqq,
|
||||
HL POPqq,
|
||||
chkPS,
|
||||
L1 BSET ( loop )
|
||||
BEGIN, ( loop )
|
||||
LDA(DE),
|
||||
(HL) CPr,
|
||||
JRNZ, L2 FWR ( not equal? break early to "end".
|
||||
JRNZ, L1 FWR ( not equal? break early to "end".
|
||||
NZ is set. )
|
||||
A ORr, ( if our char is null, stop )
|
||||
HL INCss,
|
||||
DE INCss,
|
||||
JRNZ, L1 BWR ( loop )
|
||||
L2 FSET ( end )
|
||||
JRNZ, AGAIN, ( loop )
|
||||
L1 FSET ( end )
|
||||
( 40 == flagsToBC )
|
||||
40 CALLnn,
|
||||
BC PUSHqq,
|
||||
@ -335,8 +334,7 @@ CODE CMP
|
||||
HL POPqq,
|
||||
DE POPqq,
|
||||
chkPS,
|
||||
A ORr, ( clear carry )
|
||||
DE SBCHLss,
|
||||
DE SUBHLss,
|
||||
( 40 == flagsToBC )
|
||||
40 CALLnn,
|
||||
BC PUSHqq,
|
||||
@ -349,13 +347,13 @@ CODE _find
|
||||
chkPS,
|
||||
( 3 == find )
|
||||
3 CALLnn,
|
||||
JRZ, L1 FWR ( found )
|
||||
IFZ, ( found )
|
||||
( not found )
|
||||
HL PUSHqq,
|
||||
DE 0 LDddnn,
|
||||
DE PUSHqq,
|
||||
JPNEXT,
|
||||
L1 FSET ( found )
|
||||
THEN, ( found )
|
||||
DE PUSHqq,
|
||||
DE 1 LDddnn,
|
||||
DE PUSHqq,
|
||||
|
@ -7,7 +7,8 @@ itself.
|
||||
|
||||
## Gathering parts
|
||||
|
||||
* A RC2014 Classic that could install the base recipe
|
||||
* A RC2014 Classic
|
||||
* `stage3.bin` from the base recipe
|
||||
* An extra AT28C64B
|
||||
* 1x 40106 inverter gates
|
||||
* Proto board, RC2014 header pins, wires, IC sockets, etc.
|
||||
@ -32,52 +33,33 @@ in write protection mode, but I preferred building my own module.
|
||||
|
||||
I don't think you need a schematic. It's really simple.
|
||||
|
||||
## Building the kernel
|
||||
## Using the at28 driver
|
||||
|
||||
For this recipe to work, we need a block device for the `at28w` program to read
|
||||
from. The easiest way to go around would be to use a SD card, but maybe you
|
||||
haven't built a SPI relay yet and it's quite a challenge to do so.
|
||||
The AT28 driver is at `drv/at28.fs` and is a pure forth source file so it's
|
||||
rather easy to set up from the base Stage 3 binary:
|
||||
|
||||
Therefore, for this recipe, we'll have `at28w` read from a memory map and we'll
|
||||
upload contents to write to memory through our serial link.
|
||||
|
||||
`at28w` is designed to be ran as a "user application", but in this case, because
|
||||
we run from a kernel without a filesystem and that `pgm` can't run without it,
|
||||
we'll integrate `at28w` directly in our kernel and expose it as an extra shell
|
||||
command (renaming it to `a28w` to fit the 4 chars limit).
|
||||
|
||||
For all this to work, you'll need [glue code that looks like this](glue.asm).
|
||||
Running `make` in this directory will produce a `os.bin` with that glue code
|
||||
that you can install in the same way you did with the basic RC2014 recipe.
|
||||
|
||||
If your range is different than `0x2000-0x3fff`, you'll have to modify
|
||||
`AT28W_MEMSTART` before you build.
|
||||
cat ../stage3.bin ../pre.fs ../../../drv/at28.fs ../run.fs > os.bin
|
||||
../../../emul/hw/rc2014/classic os.bin
|
||||
|
||||
## Writing contents to the AT28
|
||||
|
||||
The memory map is configured to start at `0xd000`. The first step is to upload
|
||||
contents at that address as documented in ["Load code in RAM and run it"][load].
|
||||
The driver provides `AT28!` which can be plugged in adev's `A!*`.
|
||||
|
||||
You have to know the size of the contents you've loaded because you'll pass it
|
||||
as at argument to `a28w`. You can run:
|
||||
It's not in the Stage 3 binary, but because it's a small piece of Forth code,
|
||||
let's just run its definition code:
|
||||
|
||||
Collapse OS
|
||||
> bsel 0
|
||||
> seek 00 0000
|
||||
> a28w <size-of-contents>
|
||||
cat ../../../drv/at28.fs | ./stripfc | ./exec <tty device>
|
||||
|
||||
It takes a little while to write. About 1 second per 0x100 bytes (soon, I'll
|
||||
implement page writing which should make it much faster).
|
||||
Then, upload your binary to some place in memory, for example `a000`. To do so,
|
||||
run this from your modern computer:
|
||||
|
||||
If the program doesn't report an error, you're all good! The program takes care
|
||||
of verifying each byte, so everything should be in place. You can verify
|
||||
yourself by `peek`-ing around the `0x2000-0x3fff` range.
|
||||
./upload <tty device> a000 <filename>
|
||||
|
||||
Note that to write a single byte to the AT28 eeprom, you don't need a special
|
||||
program. You can, while you're in the `0x2000-0x3fff` range, run `poke 1` and
|
||||
send an arbitrary char. It will work. The problem is with writing multiple
|
||||
bytes: you have to wait until the eeprom is finished writing before writing to
|
||||
a new address, something a regular `poke` doesn't do but `at28w` does.
|
||||
|
||||
[load]: ../../../doc/load-run-code.md
|
||||
Then, activate `AT28!` with `' AT28! A!* !` and then run
|
||||
`0xa000 0x2000 <size-of-bin> AMOVE`. `AT28!` checks every myte for integrity,
|
||||
so it there's no error, you should be fine. Your content is now on the EEPROM!
|
||||
|
||||
Why not upload content directly to `0x2000` after having activated `AT28!`?
|
||||
Technically, you could. It was my first idea too. However, at the time of this
|
||||
writing, I always get weird mismatch errors about halfway through. Maybe that
|
||||
the ACIA interrupt does something wrong...
|
||||
|
1
tools/.gitignore
vendored
1
tools/.gitignore
vendored
@ -8,3 +8,4 @@
|
||||
/stripfc
|
||||
/bin2c
|
||||
/exec
|
||||
/blkpack
|
||||
|
@ -8,9 +8,10 @@ SLATEST_TGT = slatest
|
||||
STRIPFC_TGT = stripfc
|
||||
BIN2C_TGT = bin2c
|
||||
EXEC_TGT = exec
|
||||
BLKPACK_TGT = blkpack
|
||||
TARGETS = $(MEMDUMP_TGT) $(BLKDUMP_TGT) $(UPLOAD_TGT) $(FONTCOMPILE_TGT) \
|
||||
$(TTYSAFE_TGT) $(PINGPONG_TGT) $(SLATEST_TGT) $(STRIPFC_TGT) \
|
||||
$(BIN2C_TGT) $(EXEC_TGT)
|
||||
$(BIN2C_TGT) $(EXEC_TGT) $(BLKPACK_TGT)
|
||||
OBJS = common.o
|
||||
|
||||
all: $(TARGETS)
|
||||
@ -29,6 +30,7 @@ $(SLATEST_TGT): $(SLATEST_TGT).c
|
||||
$(STRIPFC_TGT): $(STRIPFC_TGT).c
|
||||
$(BIN2C_TGT): $(BIN2C_TGT).c
|
||||
$(EXEC_TGT): $(EXEC_TGT).c
|
||||
$(BLKPACK_TGT): $(BLKPACK_TGT).c
|
||||
$(TARGETS): $(OBJS)
|
||||
$(CC) $(CFLAGS) $@.c $(OBJS) -o $@
|
||||
|
||||
|
64
tools/blkpack.c
Normal file
64
tools/blkpack.c
Normal file
@ -0,0 +1,64 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <dirent.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
void usage()
|
||||
{
|
||||
fprintf(stderr, "Usage: blkpack dirname\n");
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
DIR *dp;
|
||||
struct dirent *ep;
|
||||
char *buf = NULL;
|
||||
int blkcnt = 0;
|
||||
if (argc != 2) {
|
||||
usage();
|
||||
return 1;
|
||||
}
|
||||
dp = opendir(argv[1]);
|
||||
if (dp == NULL) {
|
||||
fprintf(stderr, "Couldn't open directory.\n");
|
||||
return 1;
|
||||
}
|
||||
while ((ep = readdir(dp))) {
|
||||
if ((strcmp(ep->d_name, ".") == 0) || strcmp(ep->d_name, "..") == 0) {
|
||||
continue;
|
||||
}
|
||||
if (ep->d_type != DT_REG) {
|
||||
continue;
|
||||
}
|
||||
int blkid = atoi(ep->d_name);
|
||||
if (blkid >= blkcnt) {
|
||||
int newcnt = blkid+1;
|
||||
buf = realloc(buf, newcnt*1024);
|
||||
bzero(buf+(blkcnt*1024), (newcnt-blkcnt)*1024);
|
||||
blkcnt = newcnt;
|
||||
}
|
||||
char fullpath[0x200];
|
||||
strcpy(fullpath, argv[1]);
|
||||
strcat(fullpath, "/");
|
||||
strcat(fullpath, ep->d_name);
|
||||
FILE *fp = fopen(fullpath, "r");
|
||||
char *line = NULL;
|
||||
size_t n = 0;
|
||||
for (int i=0; i<16; i++) {
|
||||
ssize_t cnt = getline(&line, &n, fp);
|
||||
if (cnt < 0) break;
|
||||
if (cnt > 65) {
|
||||
fprintf(stderr, "Line %d too long in blk %s\n", i+1, ep->d_name);
|
||||
}
|
||||
strncpy(buf+(blkid*1024)+(i*64), line, cnt-1);
|
||||
}
|
||||
free(line);
|
||||
}
|
||||
fwrite(buf, 1024, blkcnt, stdout);
|
||||
free(buf);
|
||||
closedir(dp);
|
||||
return 0;
|
||||
}
|
||||
|
@ -8,7 +8,9 @@
|
||||
void mread(int fd, char *s, int count)
|
||||
{
|
||||
while (count) {
|
||||
while (read(fd, s, 1) == 0);
|
||||
while (read(fd, s, 1) == 0) {
|
||||
usleep(1000);
|
||||
}
|
||||
s++;
|
||||
count--;
|
||||
}
|
||||
|
@ -65,6 +65,7 @@ int main(int argc, char **argv)
|
||||
// we don't exit now because we need to "consume" our whole program.
|
||||
returncode = 1;
|
||||
}
|
||||
usleep(1000); // let it breathe
|
||||
}
|
||||
mread(fd, s, 2); // "> " prompt
|
||||
sendcmdp(fd, "FORGET _");
|
||||
|
Loading…
Reference in New Issue
Block a user