webmvs patch 0018: incremental checkpoints. On top of 0001-0008, 0011-0015, 0017. - cckddasd.c/cckd.h: a bitmap per CCKD file of the 4 KB blocks written since the last full checkpoint copy (cckd_write, cut part of cckd_ftruncate); cckd_ckpt_dev() copies (full) or writes the marked blocks into one delta file (shadows.dlt); helpers for sr.c. - sr.c: checkpoint incr=1 base= [maxdelta=%] [maxshadow=%] [verify=1]: a delta = the pages of main storage whose XXH64 differs from the base (SR_SYS_MSBASE + SR_SYS_MSDELTA in machine.srf) + shadows.dlt; full when there is no tracked base or the delta is too large. The read-back crc32 and the fsyncs of the .srf and of the delta file run after the CPUs are started again. resume track=1 adopts the resumed checkpoint's base. sr_ckpt_progress counts ended checkpoints (lock-free wait in the page's pagehide). - msgenu.h: HHC02030I gets the kind (full/delta) at the end. sr.h: the two new keys. QPL 1.0: a modification of SDL Hercules Hyperion, distributed as a separate patch. diff --git a/cckd.h b/cckd.h index efef3ba1..ee8999ea 100644 --- a/cckd.h +++ b/cckd.h @@ -460,7 +460,14 @@ struct CCKD_EXT { /* Ext for compressed ckd */ int writes[CCKD_MAX_SF+1]; /* Nbr track writes */ CCKD_L1ENT *L1tab[CCKD_MAX_SF+1]; /* Level 1 tables */ CCKD_DEVHDR cdevhdr[CCKD_MAX_SF+1]; /* cckd device hdr */ + /* blocks written since the last full checkpoint */ + U32* ckmap[CCKD_MAX_SF+1]; /* bitmap, 4K blocks */ + BYTE ckover[CCKD_MAX_SF+1]; /* not tracked fully */ + BYTE ckbase[CCKD_MAX_SF+1]; /* map has a base */ }; +#define CKMAP_BLK 4096 +#define CKMAP_MAXBYTES ((U64)1 << 30) +#define CKMAP_WORDS ((int)(CKMAP_MAXBYTES / CKMAP_BLK / 32)) #define CCKD_MIN_FREESIZE( free_count ) (CCKD_FREE_MIN_SIZE + \ free_count < CCKD_IFB_ENTS_INCR ? 0 : \ diff --git a/cckddasd.c b/cckddasd.c index 4ce7bc24..19212aa9 100644 --- a/cckddasd.c +++ b/cckddasd.c @@ -456,6 +456,11 @@ int rc, i; /* Return code, Loop index */ destroy_condition( &cckd->cckdiocond ); /* free the cckd extension itself */ + for (i = 0; i <= CCKD_MAX_SF; i++) + { + free( cckd->ckmap[i] ); + cckd->ckmap[i] = NULL; + } dev->cckd_ext = cckd_free (dev, "ext", cckd); if (dev->dasdsfn) free (dev->dasdsfn); @@ -631,6 +636,10 @@ char pathname[MAX_PATH]; /* file path in host format */ cckd->fd[sfx] = HOPEN (pathname, flags, mode); if (sfx == 0) dev->fd = cckd->fd[sfx]; + /* a file opened again after writes: not tracked */ + if (cckd->fd[sfx] >= 0 && sfx <= CCKD_MAX_SF && cckd->ckmap[sfx]) + cckd->ckover[sfx] = 1; + if (cckd->fd[sfx] >= 0) cckd->open[sfx] = flags & O_RDWR ? CCKD_OPEN_RW : cckd->open[sfx] == CCKD_OPEN_RW ? @@ -735,6 +744,7 @@ int rc; /* Return code */ /*-------------------------------------------------------------------*/ /* Write to a cckd file */ /*-------------------------------------------------------------------*/ +static void cckd_ckmark( CCKD_EXT* cckd, int sfx, U64 off, U64 len ); int cckd_write( DEVBLK* dev, int sfx, off_t off, void* buf, unsigned int len ) { CCKD_EXT *cckd; /* -> cckd extension */ @@ -745,6 +755,9 @@ int rc = 0; /* Return code */ CCKD_TRACE( "file[%d] fd[%d] write, off 0x%16.16"PRIx64" len %d", sfx, cckd->fd[ sfx ], off, len ); + /* these blocks changed since the last checkpoint */ + cckd_ckmark( cckd, sfx, (U64) off, (U64) len ); + /* Seek to specified offset */ if (lseek( cckd->fd[ sfx ], off, SEEK_SET ) < 0) { @@ -791,6 +804,16 @@ CCKD_EXT *cckd; /* -> cckd extension */ CCKD_TRACE( "file[%d] fd[%d] ftruncate, off 0x%16.16"PRIx64, sfx, cckd->fd[sfx], off); + /* the cut part counts as written (a file that */ + /* grows again must not show the base copy's old bytes there) */ + { + struct stat st; + if (fstat( cckd->fd[sfx], &st ) == 0 && (U64) st.st_size > (U64) off) + cckd_ckmark( cckd, sfx, (U64) off, (U64) st.st_size - (U64) off ); + else if (cckd->fd[sfx] >= 0 && fstat( cckd->fd[sfx], &st ) != 0) + cckd->ckover[sfx] = 1; + } + /* Truncate the file */ if (ftruncate (cckd->fd[sfx], off) < 0) { @@ -2041,6 +2064,150 @@ DEVBLK *dev = data; /* -> device block */ /* Returns the number of files copied (0 = read-only device, no */ /* shadow), or -1 with a reason in 'err'. */ /*-------------------------------------------------------------------*/ +/*-------------------------------------------------------------------*/ +/* Incremental */ +/* checkpoints. Every write to a CCKD file (cckd_write, and the cut */ +/* part of an ftruncate) marks its 4 KB blocks in a per-file bitmap */ +/* (allocated at the first write: every write since Hercules opened */ +/* the file is in it). A full checkpoint copies the file and clears */ +/* the bitmap (both under the I/O and file locks); a delta */ +/* checkpoint writes only the marked blocks (cumulative since that */ +/* full copy = the "base") into one delta file. Base copy + delta = */ +/* the file at the delta checkpoint, byte for byte. Files larger */ +/* than CKMAP_MAXBYTES, a re-opened file or a failed allocation set */ +/* ckover: the next checkpoint must be full. */ +/*-------------------------------------------------------------------*/ +static void cckd_ckmark( CCKD_EXT* cckd, int sfx, U64 off, U64 len ) +{ + U32* map; + U64 b, e; + + if (sfx < 0 || sfx > CCKD_MAX_SF || len == 0) + return; + if (!(map = cckd->ckmap[ sfx ])) + { + U32* p = calloc( CKMAP_WORDS, sizeof( U32 )); + if (!p) + { + cckd->ckover[ sfx ] = 1; + return; + } + if (!__sync_bool_compare_and_swap( &cckd->ckmap[ sfx ], NULL, p )) + free( p ); + map = cckd->ckmap[ sfx ]; + } + if (off + len > CKMAP_MAXBYTES) + { + cckd->ckover[ sfx ] = 1; + if (off >= CKMAP_MAXBYTES) + return; + len = CKMAP_MAXBYTES - off; + } + b = off / CKMAP_BLK; + e = (off + len - 1) / CKMAP_BLK; + for (; b <= e; b++) + __sync_fetch_and_or( &map[ b >> 5 ], (U32)1 << (b & 31) ); +} + +/* Writer of the delta file: running crc32 and byte count (CKDLT in */ +/* cckddasd.h; sr.c writes the header and the end record) */ +DLL_EXPORT int ckdlt_put( CKDLT* w, const void* buf, size_t len ) +{ + const BYTE* p = buf; + size_t left = len; + while (left) + { + ssize_t n = write( w->fd, p, left ); + if (n <= 0) + return -1; + p += n; left -= (size_t) n; + } + w->crc = crc32( w->crc, buf, (uInt) len ); + w->bytes += len; + return 0; +} +DLL_EXPORT void ckdlt_le( BYTE* p, U64 v, int n ) +{ + int i; + for (i = 0; i < n; i++) { p[i] = (BYTE)(v & 0xff); v >>= 8; } +} + +/* Dirty bytes of a device's shadow files since the last full copy; */ +/* -1 = not tracked completely (the next checkpoint must be full). */ +DLL_EXPORT S64 cckd_ckpt_dirty( DEVBLK* dev ) +{ + CCKD_EXT* cckd = dev->cckd_ext; + S64 n = 0; + int i, w; + if (!cckd || dev->cckd64) + return -1; + for (i = 1; i <= cckd->sfn; i++) + { + U32* map = cckd->ckmap[ i ]; + if (cckd->ckover[ i ] || !cckd->ckbase[ i ]) + return -1; + if (!map) + continue; + for (w = 0; w < CKMAP_WORDS; w++) + if (map[ w ]) + n += (S64) __builtin_popcount( map[ w ] ) * CKMAP_BLK; + } + return n; +} + +/* After 'resume ... track=1': the live shadow files are the base's */ +/* copies (+ the delta of the resumed slot, marked by the caller */ +/* through cckd_ckpt_markrange). Writes since the open are in the */ +/* bitmaps already; from now on deltas against that base are valid. */ +DLL_EXPORT void cckd_ckpt_adopt( DEVBLK* dev ) +{ + CCKD_EXT* cckd = dev->cckd_ext; + int i; + if (!cckd) + return; + for (i = 1; i <= cckd->sfn; i++) + cckd->ckbase[ i ] = 1; +} +/* bytes of the device's shadow files now */ +DLL_EXPORT U64 cckd_ckpt_size( DEVBLK* dev ) +{ + CCKD_EXT* cckd = dev->cckd_ext; + struct stat st; + U64 n = 0; + int i; + if (!cckd) + return 0; + for (i = 1; i <= cckd->sfn; i++) + if (cckd->fd[i] >= 0 && fstat( cckd->fd[i], &st ) == 0) + n += (U64) st.st_size; + return n; +} +/* index (1..sfn) of the shadow file with this base name, or 0 */ +DLL_EXPORT int cckd_ckpt_findname( DEVBLK* dev, const char* name ) +{ + CCKD_EXT* cckd = dev->cckd_ext; + int i; + if (!cckd) + return 0; + for (i = 1; i <= cckd->sfn; i++) + { + const char* sf = cckd_sf_name( dev, i ); + const char* b; + if (!sf) + continue; + b = strrchr( sf, '/' ); + b = b ? b + 1 : sf; + if (!strcmp( b, name )) + return i; + } + return 0; +} +DLL_EXPORT void cckd_ckpt_markrange( DEVBLK* dev, int sfx, U64 off, U64 len ) +{ + if (dev->cckd_ext) + cckd_ckmark( dev->cckd_ext, sfx, off, len ); +} + static int cckd_ckpt_copy_file( int sfd, const char* dst, int dosync, U64* size, U32* crc, char* err, size_t errlen ) { @@ -2103,9 +2270,94 @@ static int cckd_ckpt_copy_file( int sfd, const char* dst, int dosync, return 0; } +/* Delta of one shadow file: record 'F' (name, size, number of runs), */ +/* then each run (offset, length, data). All numbers little endian. */ +static int cckd_ckpt_delta_file( CCKD_EXT* cckd, int sfx, const char* name, + CKDLT* w, U64* size, U64* dbytes, + char* err, size_t errlen ) +{ + struct stat st; + U32* map = cckd->ckmap[ sfx ]; + U64 nblk, b, total; + U32 nruns = 0; + BYTE hdr[ 16 ]; + BYTE* buf; + size_t nl = strlen( name ); + int pass; + + if (fstat( cckd->fd[ sfx ], &st ) < 0) + { + snprintf( err, errlen, "fstat: %s", strerror( errno )); + return -1; + } + total = (U64) st.st_size; + if (total > CKMAP_MAXBYTES) + { + snprintf( err, errlen, "%s: larger than the tracked size", name ); + return -1; + } + nblk = (total + CKMAP_BLK - 1) / CKMAP_BLK; +#define CKBIT( _b ) (map && (map[ (_b) >> 5 ] & ((U32)1 << ((_b) & 31)))) + for (b = 0; b < nblk; b++) + if (CKBIT( b ) && (b == 0 || !CKBIT( b - 1 ))) + nruns++; + if (!(buf = malloc( 256 * 1024 ))) + { + snprintf( err, errlen, "malloc: %s", strerror( errno )); + return -1; + } + hdr[0] = 'F'; ckdlt_le( hdr + 1, nl, 2 ); + if (ckdlt_put( w, hdr, 3 ) < 0 || ckdlt_put( w, name, nl ) < 0) + goto dlt_werr; + ckdlt_le( hdr, total, 8 ); ckdlt_le( hdr + 8, nruns, 4 ); + if (ckdlt_put( w, hdr, 12 ) < 0) + goto dlt_werr; + *dbytes = 0; + for (b = 0, pass = 0; b < nblk; ) + { + U64 s, e, off, len; + if (!CKBIT( b )) { b++; continue; } + s = b; + while (b < nblk && CKBIT( b )) b++; + e = b; + off = s * CKMAP_BLK; + len = MIN( e * CKMAP_BLK, total ) - off; + ckdlt_le( hdr, off, 8 ); ckdlt_le( hdr + 8, len, 4 ); + if (ckdlt_put( w, hdr, 12 ) < 0) + goto dlt_werr; + while (len) + { + size_t want = (size_t) MIN( (U64)(256 * 1024), len ); + ssize_t n = pread( cckd->fd[ sfx ], buf, want, (off_t) off ); + if (n <= 0) + { + snprintf( err, errlen, "%s: read: %s", name, n < 0 ? strerror( errno ) : "short file" ); + free( buf ); + return -1; + } + if (ckdlt_put( w, buf, (size_t) n ) < 0) + goto dlt_werr; + off += (U64) n; len -= (U64) n; *dbytes += (U64) n; + } + pass++; + } +#undef CKBIT + free( buf ); + *size = total; + return (int) pass; +dlt_werr: + snprintf( err, errlen, "write delta: %s", strerror( errno )); + free( buf ); + return -1; +} + +/* mode: CKPT_FULL = copy every shadow file into 'dir' (+ clear the */ +/* bitmaps: this copy is the new base), */ +/* CKPT_DELTA = append the marked blocks to the delta file 'w' */ +/* CKPT_COPY = copy only (verify copies; bitmaps unchanged) */ DLL_EXPORT int cckd_ckpt_dev( DEVBLK* dev, const char* dir, int dosync, int waitms, CCKD_CKPT_FILE* out, int maxout, - char* err, size_t errlen ) + char* err, size_t errlen, int mode, void* dlt ) { CCKD_EXT *cckd = dev->cckd_ext; struct timeval now; @@ -2194,14 +2446,35 @@ int i, n = 0, rc = 0; base = base ? base + 1 : sfname; out[n].devnum = dev->devnum; out[n].sfx = i; + out[n].runs = 0; + out[n].dbytes = 0; STRLCPY( out[n].name, base ); STRLCPY( out[n].src, sfname ); - snprintf( dst, sizeof( dst ), "%s/%s", dir, out[n].name ); - if (cckd_ckpt_copy_file( cckd->fd[i], dst, dosync, - &out[n].size, &out[n].crc, err, errlen ) < 0) + if (mode == CKPT_DELTA) { - rc = -1; - break; + int k = cckd_ckpt_delta_file( cckd, i, out[n].name, (CKDLT*) dlt, + &out[n].size, &out[n].dbytes, err, errlen ); + if (k < 0) { rc = -1; break; } + out[n].runs = k; + out[n].crc = 0; + } + else + { + snprintf( dst, sizeof( dst ), "%s/%s", dir, out[n].name ); + if (cckd_ckpt_copy_file( cckd->fd[i], dst, dosync, + &out[n].size, &out[n].crc, err, errlen ) < 0) + { + rc = -1; + break; + } + if (mode == CKPT_FULL) + { + /* this copy is the new base of the deltas */ + if (cckd->ckmap[i]) + memset( cckd->ckmap[i], 0, CKMAP_WORDS * sizeof( U32 )); + cckd->ckover[i] = out[n].size > CKMAP_MAXBYTES; + cckd->ckbase[i] = 1; + } } n++; } diff --git a/cckddasd.h b/cckddasd.h index 123788a1..f0e7e9f4 100644 --- a/cckddasd.h +++ b/cckddasd.h @@ -243,10 +243,24 @@ typedef struct CCKD_CKPT_FILE { char src[ 1024 ]; /* live file name */ U64 size; /* bytes copied */ U32 crc; /* zlib crc32 of the copy */ + int runs; /* 0018: delta runs */ + U64 dbytes; /* 0018: delta data bytes */ } CCKD_CKPT_FILE; +/* incremental checkpoints (delta file writer, modes) */ +typedef struct CKDLT { int fd; U32 crc; U64 bytes; } CKDLT; +#define CKPT_FULL 1 +#define CKPT_DELTA 2 +#define CKPT_COPY 3 CCKD_DLL_IMPORT int cckd_ckpt_dev( DEVBLK* dev, const char* dir, int dosync, int waitms, CCKD_CKPT_FILE* out, int maxout, - char* err, size_t errlen ); + char* err, size_t errlen, int mode, void* dlt ); +CCKD_DLL_IMPORT int ckdlt_put( CKDLT* w, const void* buf, size_t len ); +CCKD_DLL_IMPORT void ckdlt_le( BYTE* p, U64 v, int n ); +CCKD_DLL_IMPORT S64 cckd_ckpt_dirty( DEVBLK* dev ); +CCKD_DLL_IMPORT void cckd_ckpt_adopt( DEVBLK* dev ); +CCKD_DLL_IMPORT U64 cckd_ckpt_size( DEVBLK* dev ); +CCKD_DLL_IMPORT int cckd_ckpt_findname( DEVBLK* dev, const char* name ); +CCKD_DLL_IMPORT void cckd_ckpt_markrange( DEVBLK* dev, int sfx, U64 off, U64 len ); CCKD_DLL_IMPORT void *cckd_sf_remove(void *data); CCKD_DLL_IMPORT void *cckd_sf_comp(void *data); CCKD_DLL_IMPORT void *cckd_sf_chk(void *data); diff --git a/msgenu.h b/msgenu.h index ea7fdb01..fffbfb4b 100644 --- a/msgenu.h +++ b/msgenu.h @@ -1380,7 +1380,7 @@ LOGM_DLL_IMPORT int panel_command_capture( char* cmd, char** resp, bool quiet ) #define HHC02027 "SR: resume failed after the machine was changed; processors configured again, IPL (clear) needed" #define HHC02028 "SR: file check failed: %s" //efine HHC02029 (available) -#define HHC02030 "CKPT: %s complete: pause %d ms (stop %d, drain %d, cckd %d, srf %d), srf %"PRIu64" bytes, %d shadow files %"PRIu64" bytes" +#define HHC02030 "CKPT: %s complete: pause %d ms (stop %d, drain %d, cckd %d, srf %d), srf %"PRIu64" bytes, %d shadow files %"PRIu64" bytes%s" #define HHC02031 "CKPT: %s failed: %s; CPUs restarted" #define HHC02032 "CKPT: %s" #define HHC02033 "SR: %04X: 3270 screen not saved: %s" diff --git a/sr.c b/sr.c index c48c05f2..c6d6a6c9 100644 --- a/sr.c +++ b/sr.c @@ -222,6 +222,172 @@ static int sr_drain_io( int timeout_ms, DEVBLK** busy ) } } +/*-------------------------------------------------------------------*/ +/* Incremental */ +/* checkpoints. 'checkpoint incr=1 base=' writes a DELTA */ +/* when is the last full checkpoint of this process (or the */ +/* slot that 'resume ... track=1' adopted): only the 4 KB pages of */ +/* main storage whose hash differs from the base's, and only the */ +/* blocks of the shadow files written since the base copy (cckd */ +/* bitmaps, cckddasd.c), into /shadows.dlt. A delta is always */ +/* against the base, never against another delta (restore = base + */ +/* one delta). Without a usable base, or when the delta would be */ +/* larger than maxdelta= percent of the base, it writes a full one. */ +/*-------------------------------------------------------------------*/ +#define CK_PAGE 4096 +/* progress of 'checkpoint' for an embedder that must wait for it */ +/* without a lock (the browser page in pagehide): 4 * (checkpoints */ +/* ended, ok or failed) + phase (0 idle, 1 CPUs stopped, 2 after the */ +/* pause, writing the manifest) */ +DLL_EXPORT volatile U32 sr_ckpt_progress = 0; +#define CK_PHASE( _p ) (sr_ckpt_progress = (sr_ckpt_progress & ~3u) | (_p)) +#define CK_ENDED() (sr_ckpt_progress = (sr_ckpt_progress & ~3u) + 4) +static char ck_base[ MAX_PATH ] = ""; /* dir of the base checkpoint */ +static int ck_valid = 0; /* deltas against ck_base ok */ +static U64* ck_hash = NULL; /* page hashes of the base */ +static U64* ck_hash2 = NULL; /* (next full: new hashes) */ +static BYTE* ck_chg = NULL; /* pages that differ */ +static U64 ck_npages = 0; +static U64 ck_base_shbytes = 0; /* bytes of the base copies */ +static int ck_ndelta = 0; /* deltas since the base */ +static struct { int mode; const char* basesrf; U64 nchg; } ck_ms; /* 0 stock, 1 full+hash, 2 delta */ + +/* XXH64 of one 4 KB page (each round is a bijection of the word, */ +/* so a change of a single 8-byte word always changes the hash) */ +#define CK_P1 0x9E3779B185EBCA87ULL +#define CK_P2 0xC2B2AE3D27D4EB4FULL +#define CK_P3 0x165667B19E3779F9ULL +#define CK_P4 0x85EBCA77C2B2AE63ULL +#define CK_P5 0x27D4EB2F165667C5ULL +#define CK_ROTL( x, r ) (((x) << (r)) | ((x) >> (64 - (r)))) +static INLINE U64 ck_round( U64 acc, U64 in ) { acc += in * CK_P2; acc = CK_ROTL( acc, 31 ); return acc * CK_P1; } +static INLINE U64 ck_merge( U64 h, U64 v ) { h ^= ck_round( 0, v ); return h * CK_P1 + CK_P4; } +static U64 ck_page_hash( const BYTE* p ) +{ + U64 v1 = CK_P1 + CK_P2, v2 = CK_P2, v3 = 0, v4 = 0 - CK_P1, h, w[4]; + int i; + for (i = 0; i < CK_PAGE; i += 32) + { + memcpy( w, p + i, 32 ); + v1 = ck_round( v1, w[0] ); v2 = ck_round( v2, w[1] ); + v3 = ck_round( v3, w[2] ); v4 = ck_round( v4, w[3] ); + } + h = CK_ROTL( v1, 1 ) + CK_ROTL( v2, 7 ) + CK_ROTL( v3, 12 ) + CK_ROTL( v4, 18 ); + h = ck_merge( h, v1 ); h = ck_merge( h, v2 ); h = ck_merge( h, v3 ); h = ck_merge( h, v4 ); + h += CK_PAGE; + h ^= h >> 33; h *= CK_P2; h ^= h >> 29; h *= CK_P3; h ^= h >> 32; + return h; +} + +/* (re)allocate the page arrays for this main storage size */ +static int ck_alloc() +{ + U64 np = sysblk.mainsize / CK_PAGE; + if (np == ck_npages && ck_hash && ck_hash2 && ck_chg) + return 0; + free( ck_hash ); free( ck_hash2 ); free( ck_chg ); + ck_hash = calloc( np ? np : 1, sizeof( U64 )); + ck_hash2 = calloc( np ? np : 1, sizeof( U64 )); + ck_chg = calloc( np ? np : 1, 1 ); + ck_npages = np; + ck_valid = 0; + if (!ck_hash || !ck_hash2 || !ck_chg) + { + free( ck_hash ); free( ck_hash2 ); free( ck_chg ); + ck_hash = ck_hash2 = NULL; ck_chg = NULL; ck_npages = 0; + return -1; + } + return 0; +} + +/* dirname of a path into buf ("." if none) */ +static void ck_dirname( const char* path, char* buf, size_t len ) +{ + const char* s = strrchr( path, '/' ); + if (!s) { snprintf( buf, len, "." ); return; } + if (s == path) { snprintf( buf, len, "/" ); return; } + snprintf( buf, len, "%.*s", (int)(s - path), path ); +} + +static U64 ck_le( const BYTE* p, int n ) +{ + U64 v = 0; + while (n--) v = (v << 8) | p[n]; + return v; +} + +/* Main storage in the .srf: stock (one buffer), full with the page */ +/* hashes of exactly the bytes written (copied first), or a delta: */ +/* SR_SYS_MSBASE (the base's .srf) + SR_SYS_MSDELTA (runs of pages: */ +/* first page, count (4 bytes each, big endian), the pages). */ +static int sr_write_mainstor( SR_FILE file ) +{ + static BYTE tmp[ 64 * 1024 ]; + U64 np = sysblk.mainsize / CK_PAGE, p, q, off, total = 0; + BYTE rh[ 8 ]; + + if (ck_ms.mode == 1) + { + SR_WRITE_HDR( file, SR_SYS_MAINSTOR, sysblk.mainsize ); + for (off = 0; off < sysblk.mainsize; off += sizeof( tmp )) + { + U32 n = (U32) MIN( (U64) sizeof( tmp ), sysblk.mainsize - off ), k; + memcpy( tmp, sysblk.mainstor + off, n ); + for (k = 0; k + CK_PAGE <= n; k += CK_PAGE) + ck_hash2[ (off + k) / CK_PAGE ] = ck_page_hash( tmp + k ); + if ((U32) SR_WRITE( tmp, 1, n, file ) != n) + { + sr_write_error_(); + return -1; + } + } + return 0; + } + if (ck_ms.mode == 2) + { + SR_WRITE_STRING( file, SR_SYS_MSBASE, ck_ms.basesrf ); + for (p = 0; p < np; ) + { + if (!ck_chg[p]) { p++; continue; } + for (q = p; q < np && ck_chg[q]; q++) ; + total += 8 + (q - p) * CK_PAGE; + p = q; + } + if (total > 0xFFFFFFF0ULL) + { + sr_write_error_(); + return -1; + } + SR_WRITE_HDR( file, SR_SYS_MSDELTA, total ); + for (p = 0; p < np; ) + { + if (!ck_chg[p]) { p++; continue; } + for (q = p; q < np && ck_chg[q]; q++) ; + store_fw( rh, (U32) p ); + store_fw( rh + 4, (U32)(q - p) ); + if (SR_WRITE( rh, 1, 8, file ) != 8) + { + sr_write_error_(); + return -1; + } + for (off = p * CK_PAGE; off < q * CK_PAGE; ) + { + U32 n = (U32) MIN( (U64) SR_BUF_CHUNKSIZE, q * CK_PAGE - off ); + if ((U32) SR_WRITE( sysblk.mainstor + off, 1, n, file ) != n) + { + sr_write_error_(); + return -1; + } + off += n; + } + p = q; + } + return 0; + } + SR_WRITE_BUF( file, SR_SYS_MAINSTOR, sysblk.mainstor, sysblk.mainsize ); + return 0; +} + /* Write the whole machine state (the stock suspend file contents). */ /* All CPUs must be stopped and the I/O drained. Returns 0 or -1. */ static int sr_write_state( SR_FILE file, CPU_BITMAP started_mask ) @@ -252,7 +418,8 @@ struct { U16 lcss, devnum; BYTE type; } q[ 256 ]; SR_WRITE_VALUE (file,SR_SYS_STARTED_MASK,(U64)started_mask,sizeof(U64)); SR_WRITE_VALUE (file,SR_SYS_MAINSIZE,sysblk.mainsize,sizeof(sysblk.mainsize)); TRACE("SR: Saving MAINSTOR...\n"); - SR_WRITE_BUF (file,SR_SYS_MAINSTOR,sysblk.mainstor,sysblk.mainsize); + if (sr_write_mainstor( file ) < 0) /* full/delta */ + return -1; SR_WRITE_VALUE (file,SR_SYS_SKEYSIZE,(sysblk.mainsize/_STORKEY_ARRAY_UNITSIZE),sizeof(U32)); TRACE("SR: Saving Storage Keys...\n"); SR_WRITE_BUF (file,SR_SYS_STORKEYS,sysblk.storkeys,sysblk.mainsize/_STORKEY_ARRAY_UNITSIZE); @@ -600,51 +767,79 @@ static int ckpt_file_crc( const char* path, U64* size, U32* crc, int dosync ) return 0; } +/* open /shadows.dlt and write its header */ +static int ckpt_dlt_open( const char* path, CKDLT* w ) +{ + w->fd = HOPEN( path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, S_IRUSR | S_IWUSR | S_IRGRP ); + w->crc = crc32( 0L, Z_NULL, 0 ); + w->bytes = 0; + if (w->fd < 0) + return -1; + return ckdlt_put( w, "MVSCKDL1", 8 ); +} + int checkpoint_cmd( int argc, char* argv[], char* cmdline ) { static CCKD_CKPT_FILE files[ CKPT_MAXFILES ]; -char *dir = NULL; +static CCKD_CKPT_FILE vfiles[ CKPT_MAXFILES ]; +char *dir = NULL, *base = NULL; int level = 1, waitms = 10000, screenms = 2000, dosync = 1; -int i, n, nfiles = 0, started = 0; -int ms_stop = 0, ms_drain = 0, ms_cckd = 0, ms_srf = 0, ms_pause = 0; +int incr = 0, maxdelta = 50, maxshadow = 90, verify = 0; +int i, n, nfiles = 0, nvfiles = 0, started = 0, kind = 0; +int ms_stop = 0, ms_drain = 0, ms_hash = 0, ms_cckd = 0, ms_srf = 0, ms_pause = 0, ms_crc = 0; CPU_BITMAP mask = 0; struct timeval t0, tv; -char srf[ MAX_PATH ], man[ MAX_PATH ], mode[ 8 ], why[ 1024 ], bid[ 512 ]; +char srf[ MAX_PATH ], man[ MAX_PATH ], dlt[ MAX_PATH ], vdir[ MAX_PATH ], basesrf[ MAX_PATH ]; +char mode[ 8 ], why[ 1024 ], bid[ 512 ], kwhy[ 256 ] = ""; char err[ 512 ]; SR_FILE file; DEVBLK *dev, *busy = NULL; -U64 srfsize = 0, shbytes = 0; +U64 srfsize = 0, shbytes = 0, dbytes = 0, p, nchg = 0; U32 srfcrc = 0; +S64 dirty = 0; +U64 cursh = 0; +CKDLT w; FILE *mf; +#define CKPT_USAGE "usage: checkpoint [level=0..9] [wait=ms] [screens=ms] [sync=0|1] [incr=0|1] [base=] [maxdelta=1..100] [maxshadow=1..100] [verify=0|1]" UNREFERENCED( cmdline ); + w.fd = -1; for (i = 1; i < argc; i++) { - if (!strncasecmp( argv[i], "level=", 6 )) level = atoi( argv[i] + 6 ); - else if (!strncasecmp( argv[i], "wait=", 5 )) waitms = atoi( argv[i] + 5 ); - else if (!strncasecmp( argv[i], "screens=", 8 )) screenms = atoi( argv[i] + 8 ); - else if (!strncasecmp( argv[i], "sync=", 5 )) dosync = atoi( argv[i] + 5 ) != 0; - else if (!dir && !strchr( argv[i], '=' )) dir = argv[i]; + if (!strncasecmp( argv[i], "level=", 6 )) level = atoi( argv[i] + 6 ); + else if (!strncasecmp( argv[i], "wait=", 5 )) waitms = atoi( argv[i] + 5 ); + else if (!strncasecmp( argv[i], "screens=", 8 )) screenms = atoi( argv[i] + 8 ); + else if (!strncasecmp( argv[i], "sync=", 5 )) dosync = atoi( argv[i] + 5 ) != 0; + else if (!strncasecmp( argv[i], "incr=", 5 )) incr = atoi( argv[i] + 5 ) != 0; + else if (!strncasecmp( argv[i], "base=", 5 )) base = argv[i] + 5; + else if (!strncasecmp( argv[i], "maxdelta=", 9 )) maxdelta = atoi( argv[i] + 9 ); + else if (!strncasecmp( argv[i], "verify=", 7 )) verify = atoi( argv[i] + 7 ) != 0; + else if (!strncasecmp( argv[i], "maxshadow=",10 )) maxshadow = atoi( argv[i] + 10 ); + else if (!dir && !strchr( argv[i], '=' )) dir = argv[i]; else { - WRMSG( HHC02032, "E", "usage: checkpoint [level=0..9] [wait=ms] [screens=ms] [sync=0|1]" ); + WRMSG( HHC02032, "E", CKPT_USAGE ); return -1; } } - if (!dir || level < 0 || level > 9 || waitms < 0 || screenms < 0) + if (!dir || level < 0 || level > 9 || waitms < 0 || screenms < 0 || maxdelta < 1 || maxdelta > 100 || maxshadow < 1 || maxshadow > 100) { - WRMSG( HHC02032, "E", "usage: checkpoint [level=0..9] [wait=ms] [screens=ms] [sync=0|1]" ); + WRMSG( HHC02032, "E", CKPT_USAGE ); return -1; } - if (strlen( dir ) > MAX_PATH - 64) + if (strlen( dir ) > MAX_PATH - 64 || (base && strlen( base ) > MAX_PATH - 64)) { WRMSG( HHC02032, "E", "checkpoint: directory name too long" ); return -1; } + if (base && !*base) + base = NULL; snprintf( srf, sizeof( srf ), "%s/machine.srf", dir ); snprintf( man, sizeof( man ), "%s/checkpoint.json", dir ); + snprintf( dlt, sizeof( dlt ), "%s/shadows.dlt", dir ); + snprintf( vdir, sizeof( vdir ), "%s/verify", dir ); if (mkdir( dir, S_IRWXU | S_IRGRP | S_IXGRP ) < 0 && errno != EEXIST) { @@ -659,8 +854,11 @@ FILE *mf; WRMSG( HHC02031, "E", dir, why ); return -1; } + if (base && !strcmp( base, dir )) + base = NULL; gettimeofday( &t0, NULL ); + CK_PHASE( 1 ); /* 1. stop the CPUs */ if (sr_stop_cpus( &mask, 10000 ) < 0) @@ -681,7 +879,72 @@ FILE *mf; } ms_drain = sr_ms_since( &t0 ) - ms_stop; - /* 3. CCKD devices: flush, harden, copy the shadow files */ + /* full or delta? (kind 0 = stock full without */ + /* tracking, 1 = full: the new base, 2 = delta against ck_base) */ + if (incr && (sysblk.mainsize % CK_PAGE || ck_alloc() < 0)) + { + snprintf( kwhy, sizeof( kwhy ), "no tracking (main storage size or memory)" ); + incr = 0; + } + if (incr) + { + kind = 1; + if (!base) + snprintf( kwhy, sizeof( kwhy ), "no base requested" ); + else if (!ck_valid) + snprintf( kwhy, sizeof( kwhy ), "no base in this process" ); + else if (strcmp( base, ck_base )) + snprintf( kwhy, sizeof( kwhy ), "base %s is not the tracked base", base ); + else + { + for (p = 0; p < ck_npages; p++) + { + ck_chg[p] = ck_page_hash( sysblk.mainstor + p * CK_PAGE ) != ck_hash[p]; + nchg += ck_chg[p]; + } + for (dev = sysblk.firstdev; dev && dirty >= 0; dev = dev->nextdev) + { + S64 d; + if (!dev->allocated || !dev->cckd_ext) + continue; + d = cckd_ckpt_dirty( dev ); + dirty = d < 0 ? -1 : dirty + d; + cursh += cckd_ckpt_size( dev ); + } + if (dirty < 0) + snprintf( kwhy, sizeof( kwhy ), "shadow writes not tracked completely" ); + else if (nchg * 100 > ck_npages * (U64) maxdelta) + snprintf( kwhy, sizeof( kwhy ), "storage delta %"PRIu64" of %"PRIu64" pages", nchg, ck_npages ); + else if ((U64) dirty * 100 > MAX( cursh, (U64)(1 << 20) ) * (U64) maxshadow) + snprintf( kwhy, sizeof( kwhy ), "shadow delta %"PRId64" of %"PRIu64" bytes", dirty, cursh ); + else + kind = 2; + } + ms_hash = sr_ms_since( &t0 ) - ms_stop - ms_drain; + } + if (kind != 2) + ck_valid = 0; /* the full copy clears the bitmaps */ + if (kind == 2) + snprintf( basesrf, sizeof( basesrf ), "%s/machine.srf", ck_base ); + + /* 3. CCKD devices: flush, harden, copy the shadow files (full) */ + /* or write their changed blocks into shadows.dlt (delta) */ + if (kind == 2) + { + unlink( dlt ); + if (ckpt_dlt_open( dlt, &w ) < 0) + { + snprintf( why, sizeof( why ), "open %s: %s", dlt, strerror( errno )); + goto ckpt_fail; + } + } + else + unlink( dlt ); /* (a full slot has no delta) */ + if (verify && kind == 2 && mkdir( vdir, S_IRWXU | S_IRGRP | S_IXGRP ) < 0 && errno != EEXIST) + { + snprintf( why, sizeof( why ), "mkdir %s: %s", vdir, strerror( errno )); + goto ckpt_fail; + } for (dev = sysblk.firstdev; dev; dev = dev->nextdev) { if (!dev->allocated) @@ -689,15 +952,31 @@ FILE *mf; if (dev->cckd_ext) { n = cckd_ckpt_dev( dev, dir, dosync, waitms, files + nfiles, - CKPT_MAXFILES - nfiles, err, sizeof( err )); + CKPT_MAXFILES - nfiles, err, sizeof( err ), + kind == 2 ? CKPT_DELTA : CKPT_FULL, &w ); if (n < 0) { snprintf( why, sizeof( why ), "device %04X: %s", dev->devnum, err ); goto ckpt_fail; } for (i = nfiles; i < nfiles + n; i++) + { shbytes += files[i].size; + dbytes += files[i].dbytes; + } nfiles += n; + if (verify && kind == 2) + { + /* test option: also a full copy, to compare base + delta */ + n = cckd_ckpt_dev( dev, vdir, 0, waitms, vfiles + nvfiles, + CKPT_MAXFILES - nvfiles, err, sizeof( err ), CKPT_COPY, NULL ); + if (n < 0) + { + snprintf( why, sizeof( why ), "device %04X: verify copy: %s", dev->devnum, err ); + goto ckpt_fail; + } + nvfiles += n; + } } else if (dev->ckdtab && dev->fd >= 0 && !dev->ckdrdonly) { @@ -705,55 +984,118 @@ FILE *mf; goto ckpt_fail; } } - ms_cckd = sr_ms_since( &t0 ) - ms_stop - ms_drain; + if (kind == 2) + { + BYTE tr[ 5 ]; + tr[0] = 'E'; ckdlt_le( tr + 1, (U64) nfiles, 4 ); + n = ckdlt_put( &w, tr, 5 ); + if (close( w.fd ) < 0 || n < 0) + { + w.fd = -1; + snprintf( why, sizeof( why ), "writing %s: %s", dlt, strerror( errno )); + goto ckpt_fail; + } + w.fd = -1; + } + ms_cckd = sr_ms_since( &t0 ) - ms_stop - ms_drain - ms_hash; /* 4. the machine state */ if (level == 0) STRLCPY( mode, "wbT" ); else snprintf( mode, sizeof( mode ), "wb%d", level ); + if (verify && kind == 2) + { + char vsrf[ MAX_PATH ]; + snprintf( vsrf, sizeof( vsrf ), "%s/machine.srf", vdir ); + ck_ms.mode = 0; + if (!(file = SR_OPEN( vsrf, mode ))) + { + snprintf( why, sizeof( why ), "open %s: %s", vsrf, strerror( errno )); + goto ckpt_fail; + } + sysblk.sr_ckpt_screen_ms = -1; + n = sr_write_state( file, mask ); + sysblk.sr_ckpt_screen_ms = 0; + if (SR_CLOSE( file ) != 0 || n < 0) + { + snprintf( why, sizeof( why ), "writing %s failed", vsrf ); + goto ckpt_fail; + } + } if (!(file = SR_OPEN( srf, mode ))) { snprintf( why, sizeof( why ), "open %s: %s", srf, strerror( errno )); goto ckpt_fail; } + ck_ms.mode = kind; + ck_ms.basesrf = basesrf; + ck_ms.nchg = nchg; sysblk.sr_ckpt_screen_ms = screenms > 0 ? screenms : -1; n = sr_write_state( file, mask ); sysblk.sr_ckpt_screen_ms = 0; + ck_ms.mode = 0; if (SR_CLOSE( file ) != 0 || n < 0) { snprintf( why, sizeof( why ), "writing %s failed", srf ); goto ckpt_fail; } + ms_srf = sr_ms_since( &t0 ) - ms_stop - ms_drain - ms_hash - ms_cckd; + + /* 5. run again (0018: the read-back crc and the fsyncs of the */ + /* .srf and the delta file follow after the pause) */ + started = sr_start_cpus( mask ); + ms_pause = sr_ms_since( &t0 ); + CK_PHASE( 2 ); + if (ckpt_file_crc( srf, &srfsize, &srfcrc, dosync ) < 0) { snprintf( why, sizeof( why ), "reading %s back: %s", srf, strerror( errno )); - goto ckpt_fail; + if (kind == 1) ck_valid = 0; + WRMSG( HHC02031, "E", dir, why ); + CK_ENDED(); + return -1; } - ms_srf = sr_ms_since( &t0 ) - ms_stop - ms_drain - ms_cckd; - - /* 5. run again */ - started = sr_start_cpus( mask ); - ms_pause = sr_ms_since( &t0 ); + if (kind == 2 && dosync) + { + int dfd = HOPEN( dlt, O_RDWR | O_BINARY ); + if (dfd >= 0) { fsync( dfd ); close( dfd ); } + } + ms_crc = sr_ms_since( &t0 ) - ms_pause; /* 6. the manifest, last */ if (!(mf = fopen( man, "w" ))) { snprintf( why, sizeof( why ), "open %s: %s", man, strerror( errno )); WRMSG( HHC02031, "E", dir, why ); + CK_ENDED(); return -1; } gettimeofday( &tv, NULL ); - fprintf( mf, "{\"format\":\"hercules-checkpoint\",\"version\":1,\n" ); + fprintf( mf, "{\"format\":\"hercules-checkpoint\",\"version\":%d,\n", kind ? 2 : 1 ); fprintf( mf, " \"build\":\"%s\",\n", sr_build_id( bid, sizeof( bid ))); fprintf( mf, " \"created\":%"PRId64",\n", (S64) tv.tv_sec * 1000 + tv.tv_usec / 1000 ); + fprintf( mf, " \"kind\":\"%s\",\"kindWhy\":\"%s\",", kind == 2 ? "delta" : "full", kwhy ); + if (kind == 2) + fprintf( mf, "\"base\":\"%s\",\"deltaNo\":%d,\"msPages\":%"PRIu64",\"npages\":%"PRIu64",\n" + " \"delta\":{\"file\":\"shadows.dlt\",\"size\":%"PRIu64",\"crc32\":\"%08X\",\"bytes\":%"PRIu64"},\n", + ck_base, ck_ndelta + 1, nchg, ck_npages, w.bytes, w.crc, dbytes ); + else + fprintf( mf, "\n" ); fprintf( mf, " \"srf\":{\"file\":\"machine.srf\",\"size\":%"PRIu64",\"crc32\":\"%08X\",\"level\":%d},\n", srfsize, srfcrc, level ); - fprintf( mf, " \"cpusStarted\":%d,\"pauseMs\":%d,\"stopMs\":%d,\"drainMs\":%d,\"cckdMs\":%d,\"srfMs\":%d,\n", - started, ms_pause, ms_stop, ms_drain, ms_cckd, ms_srf ); + fprintf( mf, " \"cpusStarted\":%d,\"pauseMs\":%d,\"stopMs\":%d,\"drainMs\":%d,\"hashMs\":%d,\"cckdMs\":%d,\"srfMs\":%d,\"crcMs\":%d,\n", + started, ms_pause, ms_stop, ms_drain, ms_hash, ms_cckd, ms_srf, ms_crc ); fprintf( mf, " \"shadows\":[" ); for (i = 0; i < nfiles; i++) - fprintf( mf, "%s\n {\"dev\":\"%04X\",\"sfx\":%d,\"file\":\"%s\",\"src\":\"%s\",\"size\":%"PRIu64",\"crc32\":\"%08X\"}", - i ? "," : "", files[i].devnum, files[i].sfx, files[i].name, files[i].src, - files[i].size, files[i].crc ); + { + if (kind == 2) + fprintf( mf, "%s\n {\"dev\":\"%04X\",\"sfx\":%d,\"file\":\"%s\",\"src\":\"%s\",\"size\":%"PRIu64",\"delta\":true,\"runs\":%d,\"bytes\":%"PRIu64"}", + i ? "," : "", files[i].devnum, files[i].sfx, files[i].name, files[i].src, + files[i].size, files[i].runs, files[i].dbytes ); + else + fprintf( mf, "%s\n {\"dev\":\"%04X\",\"sfx\":%d,\"file\":\"%s\",\"src\":\"%s\",\"size\":%"PRIu64",\"crc32\":\"%08X\"}", + i ? "," : "", files[i].devnum, files[i].sfx, files[i].name, files[i].src, + files[i].size, files[i].crc ); + } fprintf( mf, "],\n \"end\":true}\n" ); fflush( mf ); if (dosync) @@ -762,7 +1104,9 @@ FILE *mf; { snprintf( why, sizeof( why ), "writing %s failed", man ); unlink( man ); + if (kind == 1) ck_valid = 0; WRMSG( HHC02031, "E", dir, why ); + CK_ENDED(); return -1; } if (dosync) @@ -771,16 +1115,46 @@ FILE *mf; if (dfd >= 0) { fsync( dfd ); close( dfd ); } } - // "CKPT: %s complete: pause %d ms (stop %d, drain %d, cckd %d, srf %d), srf %"PRIu64" bytes, %d shadow files %"PRIu64" bytes" + /* a full checkpoint is the new base */ + if (kind == 1) + { + U64* t = ck_hash; ck_hash = ck_hash2; ck_hash2 = t; + snprintf( ck_base, sizeof( ck_base ), "%s", dir ); + ck_base_shbytes = shbytes; + ck_ndelta = 0; + ck_valid = 1; + } + else if (kind == 2) + ck_ndelta++; + + // "CKPT: %s complete: pause %d ms (stop %d, drain %d, cckd %d, srf %d), srf %"PRIu64" bytes, %d shadow files %"PRIu64" bytes%s" + if (kind == 2) + snprintf( kwhy, sizeof( kwhy ), "; delta %d of %s: %"PRIu64" pages, shadow delta %"PRIu64" bytes, hash %d ms, crc %d ms", + ck_ndelta, ck_base, nchg, w.bytes, ms_hash, ms_crc ); + else if (incr) + { + char k2[ 256 ]; + snprintf( k2, sizeof( k2 ), "; full (%s), hash %d ms, crc %d ms", kwhy, ms_hash, ms_crc ); + snprintf( kwhy, sizeof( kwhy ), "%s", k2 ); + } + else + snprintf( kwhy, sizeof( kwhy ), "; crc %d ms", ms_crc ); WRMSG( HHC02030, "I", dir, ms_pause, ms_stop, ms_drain, ms_cckd, ms_srf, - srfsize, nfiles, shbytes ); + srfsize, nfiles, shbytes, kwhy ); + CK_ENDED(); return 0; ckpt_fail: sysblk.sr_ckpt_screen_ms = 0; + ck_ms.mode = 0; + if (w.fd >= 0) + close( w.fd ); + if (kind != 2) + ck_valid = 0; sr_start_cpus( mask ); // "CKPT: %s failed: %s; CPUs restarted" WRMSG( HHC02031, "E", dir, why ); + CK_ENDED(); return -1; } @@ -792,11 +1166,12 @@ ckpt_fail: /* Returns 0, -1 (damaged; reason in why) or -2 (build mismatch, */ /* message already issued). */ /*-------------------------------------------------------------------*/ -static int sr_check_file( SR_FILE file, char* why, size_t whylen ) +static int sr_check_file( SR_FILE file, char* why, size_t whylen, int depth, U64* msbytes ) { U32 key = 0, len = 0; U64 mainsize = 0; int cpu = 0; +U64 mslen = 0; char buf[ SR_MAX_STRING_LENGTH + 1 ]; char bid[ 512 ]; @@ -833,7 +1208,11 @@ char bid[ 512 ]; { SR_READ_HDR( file, key, len ); if (key == SR_EOF) + { + if (msbytes) + *msbytes = mslen; return 0; + } if ((key & SR_KEY_ID_MASK) != SR_KEY_ID) { snprintf( why, whylen, "invalid key %8.8X", key ); @@ -849,6 +1228,68 @@ char bid[ 512 ]; return -1; } } + else if (key == SR_SYS_MAINSTOR) + { + mslen = len; + SR_READ_SKIP( file, len ); + } + else if (key == SR_SYS_MSBASE) + { + /* a delta: its base must be a complete, */ + /* full file of this build with the same storage size */ + SR_FILE bf; + U64 bms = 0; + int brc; + char bwhy[ 256 ]; + if (depth > 0 || len > SR_MAX_STRING_LENGTH) + { + snprintf( why, whylen, "a delta file as the base of a delta" ); + return -1; + } + SR_READ_STRING( file, buf, len ); + if (!(bf = SR_OPEN( buf, "rb" ))) + { + snprintf( why, whylen, "base %s: %s", buf, strerror( errno )); + return -1; + } + brc = sr_check_file( bf, bwhy, sizeof( bwhy ), 1, &bms ); + SR_CLOSE( bf ); + if (brc == -2) + return -2; + if (brc < 0) + { + snprintf( why, whylen, "base %s: %s", buf, bwhy ); + return -1; + } + if (!mainsize || bms != mainsize) + { + snprintf( why, whylen, "base %s: main storage %"PRIu64" bytes, delta %"PRIu64, buf, bms, mainsize ); + return -1; + } + mslen = bms; + } + else if (key == SR_SYS_MSDELTA) + { + U32 left = len, first, cnt; + BYTE rh[ 8 ]; + while (left) + { + if (left < 8 || SR_READ( rh, 1, 8, file ) != 8) + { + snprintf( why, whylen, "storage delta: bad run" ); + return -1; + } + left -= 8; + first = fetch_fw( rh ); cnt = fetch_fw( rh + 4 ); + if (!cnt || ((U64) first + cnt) * CK_PAGE > mainsize || (U64) cnt * CK_PAGE > left) + { + snprintf( why, whylen, "storage delta: run %u+%u out of range", first, cnt ); + return -1; + } + SR_READ_SKIP( file, cnt * CK_PAGE ); + left -= cnt * CK_PAGE; + } + } else if (key == SR_CPU) { SR_READ_VALUE( file, len, &cpu, sizeof( cpu )); @@ -863,6 +1304,74 @@ char bid[ 512 ]; } } +/* after 'resume /machine.srf track=1': make the */ +/* base of that checkpoint the tracked base (ck_hash holds the page */ +/* hashes of the base's main storage, computed while it was read). */ +/* For a delta, mark the blocks of /shadows.dlt as written. */ +static void sr_track_adopt( const char* fn, const char* rbase, int isdelta ) +{ + DEVBLK* dev; + U64 sh = 0; + char d[ MAX_PATH ], dlt[ MAX_PATH ]; + int ok = 1; + + for (dev = sysblk.firstdev; dev; dev = dev->nextdev) + if (dev->allocated && dev->cckd_ext) + { + cckd_ckpt_adopt( dev ); + sh += cckd_ckpt_size( dev ); + } + if (isdelta) + { + BYTE h[ 16 ]; + char name[ 256 ]; + FILE* f; + ck_dirname( fn, d, sizeof( d )); + snprintf( dlt, sizeof( dlt ), "%s/shadows.dlt", d ); + ok = 0; + if ((f = fopen( dlt, "rb" ))) + { + ok = fread( h, 1, 8, f ) == 8 && !memcmp( h, "MVSCKDL1", 8 ); + while (ok) + { + U32 nl, nruns, r; + U64 size; + if (fread( h, 1, 1, f ) != 1) { ok = 0; break; } + if (h[0] == 'E') break; + if (h[0] != 'F' || fread( h, 1, 2, f ) != 2) { ok = 0; break; } + nl = h[0] | (h[1] << 8); + if (nl >= sizeof( name ) || fread( name, 1, nl, f ) != nl || fread( h, 1, 12, f ) != 12) { ok = 0; break; } + name[ nl ] = 0; + size = ck_le( h, 8 ); nruns = (U32) ck_le( h + 8, 4 ); + UNREFERENCED( size ); + for (dev = sysblk.firstdev; dev; dev = dev->nextdev) + if (dev->allocated && dev->cckd_ext && cckd_ckpt_findname( dev, name ) > 0) + break; + if (!dev) { ok = 0; break; } + for (r = 0; r < nruns && ok; r++) + { + U64 off; U32 rl; + if (fread( h, 1, 12, f ) != 12) { ok = 0; break; } + off = ck_le( h, 8 ); rl = (U32) ck_le( h + 8, 4 ); + cckd_ckpt_markrange( dev, cckd_ckpt_findname( dev, name ), off, rl ); + if (fseek( f, rl, SEEK_CUR ) != 0) ok = 0; + } + } + fclose( f ); + } + } + if (!ok) + { + // (a delta whose blocks cannot be marked: no deltas until the next full) + WRMSG( HHC02032, "W", "resume: track=1 ignored: shadows.dlt of the resumed checkpoint not readable" ); + return; + } + snprintf( ck_base, sizeof( ck_base ), "%s", rbase ); + ck_base_shbytes = sh; + ck_ndelta = 0; + ck_valid = 1; +} + #define SR_NULL_REGS_CHECK(_regs) if ((_regs) == NULL) goto sr_null_regs_exit; int resume_cmd(int argc, char *argv[],char *cmdline) @@ -890,9 +1399,21 @@ char buf[SR_MAX_STRING_LENGTH+1]; char zeros[16]; S64 dreg; int numconfdev=0; +int track = 0; /* adopt base */ +int isdelta = 0; +char rbase[ MAX_PATH ] = ""; UNREFERENCED(cmdline); + /* resume [track=1]. track=1: the caller */ + /* restored the shadow files from this checkpoint (its base copies */ + /* + its delta): later 'checkpoint ... incr=1 base=' */ + /* may write deltas against that base */ + if (argc == 3 && !strncasecmp( argv[2], "track=", 6 )) + { + track = atoi( argv[2] + 6 ) != 0; + argc = 2; + } if (argc > 2) { // "SR: too many arguments" @@ -902,6 +1423,9 @@ int numconfdev=0; if (argc == 2) fn = argv[1]; + if (track && (sysblk.mainsize % CK_PAGE || ck_alloc() < 0)) + track = 0; + ck_valid = 0; /* (the old base is gone) */ memset (zeros, 0, sizeof(zeros)); @@ -926,7 +1450,7 @@ int numconfdev=0; /* check the whole file first; on any problem the */ /* machine is not changed (the caller can IPL instead) */ - rc = sr_check_file( file, why, sizeof( why )); + rc = sr_check_file( file, why, sizeof( why ), 0, NULL ); SR_CLOSE( file ); if (rc < 0) { @@ -1049,8 +1573,65 @@ int numconfdev=0; case SR_SYS_MAINSTOR: TRACE("SR: Restoring MAINSTOR...\n"); SR_READ_BUF(file, sysblk.mainstor, mainsize); + if (track) /* base hashes */ + { + U64 p; + for (p = 0; p < ck_npages; p++) + ck_hash[p] = ck_page_hash( sysblk.mainstor + p * CK_PAGE ); + ck_dirname( fn, rbase, sizeof( rbase )); + } break; + case SR_SYS_MSBASE: /* a delta */ + { + SR_FILE bf; + U32 bkey = 0, blen = 0; + SR_READ_STRING(file, buf, len); + isdelta = 1; + if (!(bf = SR_OPEN( buf, "rb" ))) + { + WRMSG(HHC02001, "E", "open() base", strerror(errno)); + goto sr_error_exit; + } + while (bkey != SR_SYS_MAINSTOR) + { + if (sr_read_hdr( (FILE*) bf, &bkey, &blen ) != 0 || bkey == SR_EOF) + { SR_CLOSE( bf ); goto sr_error_exit; } + if (bkey != SR_SYS_MAINSTOR && sr_read_skip( (FILE*) bf, blen ) != 0) + { SR_CLOSE( bf ); goto sr_error_exit; } + } + if (blen != mainsize || sr_read_buf( (FILE*) bf, sysblk.mainstor, mainsize ) != 0) + { SR_CLOSE( bf ); goto sr_error_exit; } + SR_CLOSE( bf ); + if (track) + { + U64 p; + for (p = 0; p < ck_npages; p++) + ck_hash[p] = ck_page_hash( sysblk.mainstor + p * CK_PAGE ); + ck_dirname( buf, rbase, sizeof( rbase )); + } + break; + } + + case SR_SYS_MSDELTA: /* its pages */ + { + U32 left = len, first, cnt; + BYTE rh[ 8 ]; + while (left) + { + if (left < 8 || SR_READ( rh, 1, 8, file ) != 8) + goto sr_error_exit; + left -= 8; + first = fetch_fw( rh ); cnt = fetch_fw( rh + 4 ); + if (!cnt || ((U64) first + cnt) * CK_PAGE > mainsize || (U64) cnt * CK_PAGE > left) + goto sr_error_exit; + if (sr_read_buf( (FILE*) file, sysblk.mainstor + (U64) first * CK_PAGE, (U64) cnt * CK_PAGE ) != 0) + goto sr_error_exit; + left -= cnt * CK_PAGE; + } + break; + } + case SR_SYS_SKEYSIZE: SR_READ_VALUE(file, len, &len, sizeof(len)); if (len > (U32)(sysblk.mainsize/_STORKEY_ARRAY_UNITSIZE)) @@ -1926,6 +2507,12 @@ int numconfdev=0; #endif machine_check_crwpend(); + /* track=1: this checkpoint's base is the base of */ + /* the next deltas; the blocks of the resumed delta count as */ + /* written since the base (they are in the live files now) */ + if (track && rbase[0]) + sr_track_adopt( fn, rbase, isdelta ); + /* Start the CPUs */ TRACE("SR: Resuming CPUs...\n"); OBTAIN_INTLOCK(NULL); diff --git a/sr.h b/sr.h index 25d05a1d..288b4b4f 100644 --- a/sr.h +++ b/sr.h @@ -197,6 +197,8 @@ #define SR_SYS_LPARNUM 0xace10052 #define SR_SYS_CPUIDFMT 0xace10053 #define SR_SYS_OPERATION_MODE 0xace10054 +#define SR_SYS_MSBASE 0xace10060 /* delta: base .srf */ +#define SR_SYS_MSDELTA 0xace10061 /* changed pages */ #define SR_SYS_SERVC 0xace11000