Skip to content

Commit 8428285

Browse files
author
mancausoft
committed
Phase 4: -o textmode RMS record decoder
src/recfmt.c implements the on-the-fly text view of an ODS-2 file: STMLF/STM pass-through STMCR CR -> LF substitution VAR strip the 16-bit length prefix, walk the body, append LF when FAB$M_CR is set, pad to even-byte boundaries VFC like VAR but skip the fat$b_vfcsize-byte fixed header FIX emit fixed-size records, append LF when FAB$M_CR is set unknown fall back to a raw byte pass-through FAB$M_BLK (records do not span block boundaries) and the 0xffff end-of-block padding marker are honoured, mirroring how a VMS reader would treat the file. The decoder produces the entire decoded stream into a malloc'd buffer the first time it's asked, keyed by FID in a small linked-list cache. ops_getattr returns the cached length as st_size; ops_read serves slices out of the cached buffer. recfmt_clear_cache() runs on dismount so a transient mount doesn't leak memory. Bump actions/checkout and actions/upload-artifact to v5 to drop the Node.js 20 deprecation warning.
1 parent 3e9c649 commit 8428285

7 files changed

Lines changed: 430 additions & 13 deletions

File tree

.github/workflows/build.yml

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,6 @@ on:
77
branches: [ main ]
88
workflow_dispatch:
99

10-
env:
11-
# Opt JS-based actions into Node.js 24 ahead of the default flip
12-
# (Node 20 is being deprecated on GitHub-hosted runners).
13-
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
14-
1510
jobs:
1611
debian:
1712
runs-on: ubuntu-latest
@@ -24,7 +19,7 @@ jobs:
2419
apt-get install -y --no-install-recommends \
2520
build-essential pkg-config libfuse3-dev fuse3 git ca-certificates
2621
27-
- uses: actions/checkout@v4
22+
- uses: actions/checkout@v5
2823

2924
- name: Show toolchain
3025
run: |
@@ -46,7 +41,7 @@ jobs:
4641
run: ./fuse-ods2 -h || true
4742

4843
- name: Upload binary
49-
uses: actions/upload-artifact@v4
44+
uses: actions/upload-artifact@v5
5045
with:
5146
name: fuse-ods2-debian
5247
path: fuse-ods2

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ LIB_OBJS := $(LIB_SRCS:%.c=$(BUILD)/%.o)
5151
NO_FUSE_SRCS := \
5252
src/compat_glue.c \
5353
src/phyfuse.c \
54-
src/lookup.c
54+
src/lookup.c \
55+
src/recfmt.c
5556

5657
FUSE_SRCS := \
5758
src/fuse_ods2.c \

TODO.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,12 @@ brew install macfuse pkg-config
6565
- [ ] Honour highwater (return zeros past `fcb->highwater`) *(deferred: most images are dense)*
6666
- [ ] Test: `cmp` STREAM_LF and FIX files against an ods2 extraction *(deferred to Linux validation)*
6767

68-
## Phase 4 - text-mode (`-o textmode`)
69-
- [ ] `src/recfmt.c`: streaming reader (FCB, byte_offset, len) -> "decoded" bytes
70-
- [ ] Per-record-format strategies (FIX/VAR/VFC/STM/STMLF/STMCR)
71-
- [ ] Recompute `st_size` in `getattr` when textmode (cache the logical length)
72-
- [ ] Test on `.LIS` / `.TXT` files in VAR format
68+
## Phase 4 - text-mode (`-o textmode`) [CODE DONE, RUNTIME TBD]
69+
- [x] `src/recfmt.c`: full-file decoder with per-FID buffer cache
70+
- [x] Per-record-format strategies (FIX/VAR/VFC/STM/STMLF/STMCR + UDF passthrough)
71+
- [x] Recompute `st_size` in `getattr` when textmode (cache the logical length)
72+
- [x] Cache cleared on dismount
73+
- [ ] Test on `.LIS` / `.TXT` files in VAR format *(deferred to phase 7 e2e)*
7374

7475
## Phase 5 - offset + volume set
7576
- [ ] `-o offset=N`: implemented in phyfuse, exposed on the CLI

src/fuse_ods2.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include <unistd.h>
1717

1818
#include "ods2_ops.h"
19+
#include "recfmt.h"
1920

2021
#include "access.h"
2122
#include "f11def.h"
@@ -216,6 +217,7 @@ static int do_mount( void ) {
216217
}
217218

218219
static void do_dismount( void ) {
220+
recfmt_clear_cache();
219221
if( ods2_vcb != NULL ) {
220222
dismount( ods2_vcb, 0 );
221223
ods2_vcb = NULL;

src/ops.c

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include <unistd.h>
1717

1818
#include "ods2_ops.h"
19+
#include "recfmt.h"
1920

2021
#include "access.h"
2122
#include "f11def.h"
@@ -74,6 +75,24 @@ int ods2_getattr( const char *path, struct stat *st,
7475
if( !ods2_rt.force_gid && st->st_gid == 0 )
7576
st->st_gid = getgid();
7677

78+
/* In textmode the on-disk size is wrong (record headers, padding,
79+
* implicit LFs). Open the file briefly to compute the decoded
80+
* length; the result is cached, so subsequent stat/read are cheap.
81+
*/
82+
if( ods2_rt.textmode && S_ISREG( st->st_mode )
83+
&& recfmt_textmode_eligible( head ) ) {
84+
deaccesshead( vioc, NULL, 0 );
85+
86+
struct FCB *fcb = NULL;
87+
if( $SUCCESSFUL( accessfile( ods2_vcb, &fid, &fcb, 0 ) ) ) {
88+
ssize_t lsize = recfmt_logical_size( fcb );
89+
if( lsize >= 0 )
90+
st->st_size = (off_t)lsize;
91+
deaccessfile( fcb );
92+
}
93+
return 0;
94+
}
95+
7796
deaccesshead( vioc, NULL, 0 );
7897
return 0;
7998
}
@@ -225,6 +244,15 @@ int ods2_read( const char *path, char *out, size_t size, off_t offset,
225244
if( fcb == NULL )
226245
return -EBADF;
227246

247+
/* In textmode redirect through the record-format decoder; it has
248+
* its own size accounting and cached buffer. */
249+
if( ods2_rt.textmode && fcb->head != NULL
250+
&& recfmt_textmode_eligible( fcb->head ) ) {
251+
ssize_t n = recfmt_read( fcb, offset, out, size );
252+
if( n < 0 ) return -EIO;
253+
return (int)n;
254+
}
255+
228256
/* Logical end of file derived from the RECATTR. EOF block is the
229257
* first block past the last block holding data; ffb is the count
230258
* of bytes used in the last block. When efblk == 0 the file has

0 commit comments

Comments
 (0)