Categorie
Uncategorized

Timestamp

Tra le note IBM sul formato *DTS (menzionate in un precedente post) troviamo:

When converting an input date from a *DTS timestamp format to an output date of any format without time zone conversion, the supported date range is from August 23, 1928, 12:03:06.314752 (.315 for milliseconds) to May 10, 2071, 11:56:53.685240 (.685 for milliseconds).

e poco dopo:

When converting a character date and time value to *DTS and back to character format using microseconds precision, there is a rounding error of minus 1 to minus 7 microseconds.
If you specify a precision of microseconds, it is recommended that you use a microsecond value that is evenly divisible by 8.

Si potrebbe ipotizzare che X'0000000000000001‘ valga 8 microsecondi per spiegare questo errore di arrotondamento. Ma facendo qualche conto si verifica che non può essere.

Sia x il valore utilizzato per rappresentare 8 microsecondi. Conseguentemente:

  • Il valore 125.000 * x rappresenterebbe un milione di microsecondi, ossia 1 secondo.
  • Il valore 450.000.000 * x rappresenterebbe un ora (3600 secondi).
  • Il valore 10.800.000.000 * x rappresenterebbe un giorno (24 ore).

Tra il 23 Agosto 1928 e il 10 Maggio 2071 intercorrono 52.126 giorni.

Volendo cavillare ed avere un valore esatto dell’intervallo temporale dobbiamo sottrarre la differenza tra gli orari:
12:03:06.314752 – 11:56:53.685240 = 00:06:12.629512

Da cui:

(52126 * 10.800.000.000 - (125.000 * 372 + 78689) ) * x =
(562.960.800.000.000 - 46.578.689) * x =
 562.960.753.421.311 * x

Ora:

2^64 / 562.960.753.421.311 = 32.767,371

poiché 32767 = 0x7FFF il valore più plausibile per x è X'0000000000008000'

Ogni 8 microsecondi avremmo un incremento di X'8000'.

Quindi il sistema di conversione date da (e per) il formato *DTS utilizzerebbe a rigore solo 6 * 8 + 1 = 49 bit dei 64 a disposizione per trattare i timestamp (prima della versione V4R3 del sistema operativo i bit utilizzati erano 41).

Tuttavia i valori destinati ai timestamp nel savefile sembrano utilizzare tutti gli otto byte a disposizione.

La cosa più ovvia è ipotizzare che con i progressi dell’hardware si sia giunti (a livello di LIC) a gestire una granularità maggiore. Un’altra ipotesi è che la materializzazione del clock, essendo una operazione software oltre che hardware, sia strutturata proprio per garantire che due chiamate successive non possano restituire uno stesso valore (gestendo una rotazione dei valori associati ai bit meno significativi ossia quelli non determinabili dal clock vero e proprio).


Comunque sia, se venissero gestiti completamente i microsecondi sfrutteremmo a pieno altri 3 bit: 52 bit dei 64 a disposizione

X'000000000001000' = 1 microsecondo.

Curiosando nella documentazione IBM troviamo la risposta. A livello di sistema operativo IBM i 7.4 i bit utilizzati sono proprio 52.

Troviamo anche una spiegazione circa i bit apparentemente inutilizzati:

The uniqueness bits field may contain any combination of binary 1s and 0s. These bits do not provide additional granularity for a time value; they merely allow unique 64-bit values to be returned, such as when the value of the time-of-day (TOD) clock is materialized. When the uniqueness bits all contain binary 0s, then the 64-bit value returned is not unique. Unless explicitly stated otherwise, MI instructions which materialize the TOD clock return a unique 64-bit value.

Aggiungendo altri 10 bit potremmo gestire 1024 valori diversi per ogni microsecondo: con 62 bit avremo allora superato la granularità necessaria per gestire effettivamente i nanosecondi.

Numericamente avremmo comunque un dubbio su come siano gestiti i bit meno significativi.

Con un poco di impegno possiamo scrivere una routine in C che decodifichi i timestamp estratti dai savefile una volta che gli stessi siano stati trasferiti su altra piattaforma.

Quello effettivamente mostrato nel DSPSAVF (tasto funzione F16) è presente nella parte fissa dell’associated space:

000000000 02 49 c5 d4 d7 e3 e8 40 40 40 40 40 40 40 40 40
000000010 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40
000000020 04 01 a6 d7 9a 5b 0d 5a e0 01 00 00 00 01 00 00
000000030 00 00 01 40 40 ** ** ** ** ** ** ** 00 00 5c e2 
000000040 e8 e2 e5 c1 d3 40 40 40 00 00 00 00 d5 00 01 00 000000050 5c e2 e8 e2 c2 c1 e2 40 40 40 00 5c d5 d6 d5 c5 000000060 40 40 40 40 40 ** ** ** ** ** ** ** ** 00 00 00

Una ultima curiosità. Tra il valore X'0000000000000001' e il valore X'FFFFFFFFFFFFFFFF' esiste il valore X'8000000000000000'. A che data corrisponde?

In altri termini: quale è la data intermedia tra il 23 Agosto 1928 e il 10 Maggio 2071 nel formato *DTS?

Categorie
Uncategorized

Hands on #2

Nell’esempio di savefile descritto nel precedente post abbiamo analizzato solo la prima pagina contenente la intestazione del savefile. Seguono 3 pagine (da 4KB ciascuna) per cui la struttura complessiva è la seguente:

-------------------------------------------------
| Header                               | PAGE 1 |
-------------------------------------------------
| Load/dump object descriptor (0x19db) | PAGE 2 |
-------------------------------------------------
| System Object Structure     (0x19db) | PAGE 3 |
| Associated space (IBM i work area)   | PAGE 4 |
-------------------------------------------------

Come dicevamo in precedenza la documentazione fornita da Frank Soltis ci è utile per comprendere la pagina 3 (System Object Structure).

I primi 32 byte della stessa contengono la Segment header e forniscono informazioni descrittive del segmento di memoria di cui è stato effettuato il dump. Le due pagine 3 e 4 sono esattamente la copia del contenuto di due pagine presenti in memoria (si tratta di un dump) anche se non si tratta di un oggetto utente ma di una entità di servizio ad uso del sistema operativo.

Questa è la segment header:

    00 01 00 10 03 c0 00 90 3a aa 40 cc fa 00 00 00
    00 01 00 00 00 00 00 00 3a aa 40 cc fa 00 10 00

Il valore 0x3aaa40ccfa (ossia i primi 40 bit dei due indirizzi nelle posizioni 9-16 e 25-32) identificano univocamente un segmento di memoria IBM i. Il primo indirizzo è complessivamente un puntatore alla posizione iniziale del segmento (offset 0x000000), il secondo è un puntatore alla pagina successiva (offset 0x001000).


Un segmento si compone sempre di un multiplo intero di pagine di memoria (4KB) logicamente contigue ( ma non necessariamente contigue anche nella memoria fisica ).

Il secondo indirizzo ci dice che a partire dalla posizione 0x001000 (fatto 0 l’inizio della header) inizia il cosiddetto Associated Space che altro non è se non l’area di lavoro utilizzata dal sistema operativo IBM i. Il sistema operativo IBM i (ex OS/400 -e ancor prima ex CPF-) esiste ad un livello di architettura superiore al License Internal Code : “all users of the MI, including OS/400 itself, are not allowed below the MI” (op. cit. Soltis, page 129) .

Le informazioni che ci interessano, ossia quelle che descrivono gli oggetti come visti/gestiti dal sistema operativo, si trovano dunque all’inizio della pagina 4.

Sapendo che all’interno del segmento di memoria 0x3aaa40ccfa la sola pagina gestita dal sistema operativo IBM i (e di nostro interesse) inizia all’indirizzo 0x1000, decidiamo di far precedere al dump esadecimale un offset relativo partendo da 0x00000000 anziché dall’offset relativo al segmento:

000000000 02 49 c5 d4 d7 e3 e8 40 40 40 40 40 40 40 40 40
000000010 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40
000000020 04 01 a6 d7 9a 5b 0d 5a e0 01 00 00 00 01 00 00
000000030 00 00 01 40 40 ** ** ** ** ** ** ** 00 00 5c e2 
000000040 e8 e2 e5 c1 d3 40 40 40 00 00 00 00 d5 00 01 00 000000050 5c e2 e8 e2 c2 c1 e2 40 40 40 00 5c d5 d6 d5 c5 000000060 40 40 40 40 40 ** ** ** ** ** ** ** ** 00 00 00

La prima sezione (byte 1-112) ha dimensione fissa è descrive l’operazione di salvataggio (operazione, versione sistema operativo, nome libreria, tipo, sottotipo, timestamp, ecc.).

000000070 c5 d4 d7 e3 e8 40 40 40 40 40 40 40 40 40 40 40
000000080 40 40 40 40 40 40 40 40 40 40 40 40 40 40 04 01
000000090 c1 d5 c4 d9 c5 c1 d9 c9 c2 40 40 40 40 40 40 40
0000000a0 40 40 40 40 40 40 40 40 40 40 40 40 40 40 00 00
0000000b0 00 00 80 00 01 c0 00 00 00 01 10 00 00 01 60 00
0000000c0 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000000d0 00 00 00 00 00 00 00
0000000d7                      00 00 00 00 00 00 00 00 00

La seconda è costituita da una struttura di elementi che si ripete: una struct di lunghezza 151 byte (0xd7) per ogni oggetto salvato (nel nostro caso ne è presente una sola).
All’interno di questa struttura risiedono due riferimenti a posizioni successive che contengono dati addizionali relative all’elemento salvato.

I valori 0x00000110 e 0x00000160 fungono da puntatori locali all’interno della pagina.

000000110 00 00 00 00 00 00 00 40 00 00 00 00 02 e2 00 00
000000120 ff 1c 3f 10 60 00 00 00 40 00 00 00 00 00 00 00
000000130 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*

Alla posizione 0x00000110 (in questo esempio) si trovano informazioni generali valide per ogni tipo di oggetto.

Mentre alla posizione 0x00000160 (sempre in questo esempio) troviamo una serie di strutture precedute dalla loro
lunghezza e che hanno un significato posizionale.

Espandiamo la sezione di dump seguente per facilitarne la comprensione:

000000160 00 00 00 00 00 00 00 a8 40 40 40 40 40 40 40 40
000000170 40 40 40 40 40 40 40 40 40 40 40 40 40 f1 40 40
000000180 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40
000000190 40 40 40 e5 f7 d9 f4 d4 f0 f1 f2 f1 f0 f8 f2 f6
0000001a0 f1 f8 f5 f3 f0 f9 40 40 40 40 40 40 40 40 40 40
0000001b0 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40
*
0000001e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
000000200 40 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00
000000210 00 00 00 1a c1 d5 c4 d9 c5 c1 d9 c9 c2 40 ** **
000000220 ** ** ** ** ** ** 00 00 00 00 00 00 00 00 00 00
000000230 00 00 00 00 00 0b d7 d9 d6 c4 40 40 40 40 40 40
000000240 f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
000000250 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

interpretando 4 byte alla volta come lunghezza che precede ciascun elemento:

  • Primo elemento (assente, lunghezza 0)
000000160 00 00 00 00
  • Secondo elemento
000000164             00 00 00 a8
000000168                         40 40 40 40 40 40 40 40
000000170 40 40 40 40 40 40 40 40 40 40 40 40 40 f1 40 40
000000180 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40
000000190 40 40 40 e5 f7 d9 f4 d4 f0 f1 f2 f1 f0 f8 f2 f6
0000001a0 f1 f8 f5 f3 f0 f9 40 40 40 40 40 40 40 40 40 40
0000001b0 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40
0000001c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000001d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000001e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000001f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
000000200 40 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  • Terzo elemento

Avanzando di 0xa8 byte: 0x168 + 0xa8 = 0x210

000000210 00 00 00 1a
000000214             c1 d5 c4 d9 c5 c1 d9 c9 c2 40 ** **
000000220 ** ** ** ** ** ** 00 00 00 00 00 00 00 00

Quarto elemento (assente, lunghezza 0)

00000022e                                           00 00
000000230 00 00

Quinto elemento (attributo; valore “PROD”)

000000232       00 00 00 0b
000000236                   d7 d9 d6 c4 40 40 40 40 40 40
000000240 f0

Sesto elemento (assente, lunghezza 0)

000000241    00 00 00 00

Settimo elemento (assente, lunghezza 0)

000000245                 00 00 00 00

Mettendo a confronto il savefile con l’output del comando DMPOBJ OBJ(EMPTY) OBJTYPE(*LIB) si potranno correlare i campi con la dicitura adottata nella stampa QPSRVDMP.

Categorie
Uncategorized

Hands on #1

Partiamo dal caso più semplice in assoluto: il salvataggio di una libreria vuota.

CRTLIB LIB(EMPTY)
È stata creata la libreria EMPTY.
CRTSAVF FILE(EMPTY)
File EMPTY creato nella libreria QGPL.
SAVLIB LIB(EMPTY) DEV(*SAVF) SAVF(EMPTY)
1 oggetti salvati dalla libreria EMPTY.

Una volta trasferito la dimensione risultante del savefile è 16896 byte.
Come detto in precedenza la lunghezza record di un savefile è 528.
Una prima conferma sulla correttezza delle nostre ipotesi è che 528 * 32 = 16896.

Verifichiamo anche che 8 * 4 = 32: il nostro savefile è di fatto il backup di sole 4 pagine di memoria.

La prima di queste quattro pagine contiene una intestazione che impiega una porzione risibile dei 4KB utilizzati. Stiamo parlando dei soli primi 64 byte (64 su 4096… meno dell’1.6%):

a6 d7 2f 42 b7 82 70 00 00 00 00 20 30 00 30 00 
f4 f1 c7 40 f9 f0 f0 f9 40 40 40 40 40 40 40 40 
40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 
00 00 46 00 00 00 10 00 00 00 00 00 00 00 00 20 

Nel prosieguo indicheremo il primo byte con 1 e gli altri di conseguenza.

I byte dal 17 al 48 sono in EBCDIC e descrivono il tipo macchina e il modello su cui è stato effettuato il backup: nel caso mostrato vale 41G 9009 seguito da 24 blank (0x40).

I byte da 1 a 16 e da 49 a 64 sono composti di unsigned (big endian) di dimensioni diverse; rispettivamente:

  • (8, 4, 2, 2) e
  • (2, 2, 4, 8).

Faremo dunque riferimento a 9 campi:

  • da 1 a 8
  • da 9 a 12
  • da 13 a 14
  • da 15 a 16
  • da 17 a 48: tipo macchina e modello
  • da 49 a 50
  • da 51 a 52
  • da 53 a 56
  • da 57a 64

Con un poco di osservazioni su svariati savefile si verifica facilmente che 0x00000020 (byte 9-12) della prima riga e 0x0000000000000020 della quarta (byte 57-64) altro non sono che la lunghezza del savefile espressa in numero di record. Infatti 0x20 = 32 corrisponde a 16896/528 come rilevato in precedenza.

Generando nuovi savefile i primi 8 byte sono sempre diversi ma crescenti e sono grosso modo sincronizzati tra macchine diverse… sono infatti un timestamp nel formato interno di sistema. Tale formato è documentato come *DTS nelle Usage Notes della API QWCCVTDT.

Il campo 53-56 sembra specificare la dimensione in byte di una pagina di memoria: 0x00001000 = 4096 (ai tempi della architettura CISC il valore era infatti 0x00000200).

  • 1-8 timestamp nel formato *DTS (uint64_t)
  • 9-12 lunghezza savefile in numero record (uint32_t)
  • 13-14
  • 15-16
  • 17-48 tipo macchina e modello
  • 49-50
  • 51-52
  • 53-56 dimensione pagina di memoria in byte (uint32_t)
  • 57-64 lunghezza savefile in numero record (uint64_t)

I campi rimanenti hanno a che fare con la versione del formato savefile utilizzata e con la versione del sistema operativo abilitato al ripristino.

Per ora tuttavia ignoreremo i qualificatori per i formati del savefile (non cambiano dai tempi della introduzione RISC: X’3000′) e ci limitiamo ad una piccola precisazione circa il valore registrato nei byte 51-52.

Se salvassimo con valori diversi per l’opzione TGTRLS:

  • TGTRLS(V7R2M0)
  • TGTRLS(V7R3M0)
  • TGTRLS(V7R4M0)

otterremmo rispettivamente i valori X’4400′, X’4500′ e X’4600′.

In punti successivi nel savefile troveremo le sequenze X’46004400′, X’46004500′ e X’46004600′ rispettivamente. E’ il secondo di tali valori che -ove non rispettato- fa generare al sistema l’errore CPF3743.


Tale campo specifica a partire da quale versione sia possibile effettuare il ripristino o la visualizzazione del savefile.
Se un savefile è stato generato su un sistema a versione V7R4M0 senza specificare un diverso target release (TGTRLS) non sarà possibile effettuarne il ripristino in un sistema a V7R3M0 o V7R2M0 (come tristemente noto agli utenti principianti!).

Categorie
Uncategorized

Primi passi

La fonte pubblica principale di informazioni sui dettagli implementativi relativi alla memorizzazione di oggetti nel sistema operativo IBM i resta -per quanto io sia a conoscenza- il testo di Frank Soltis intitolato FORTRESS ROCHESTER
la cui pubblicazione risale al 2001.

Nell’ottavo capitolo -dedicato agli oggetti- esistono preziose considerazioni per guidarci alla scoperta dei savefile. Non che si faccia alcuna menzione ad essi nel testo ma semplicemente perché la System Object Strutture descritta a partire dalla pagina 125 è illuminante per capire il tracciato dei savefile stessi.

Le informazioni relative ad un singolo oggetto una volta trasferito su un savefile risiedono in punti diversi. Il fatto stesso che un oggetto venga salvato introduce alcune entità intermedie che servono a legare il dump dell’oggetto al nuovo contesto in cui esso viene ad essere incastonato.

A tal fine concorre l’esistenza di un particolare oggetto avente come tipo e sottotipo i valori esadecimali X’19DB’ e che, nella documentazione pubblicata online da IBM, corrisponde al tipo *SRDS definito come Save/restore descriptor space.

In funzione della numerosità e del contenuto di informazioni relative agli oggetti coinvolti in uno specifico salvataggio, un savefile potrà attivare il dump di più oggetti *SRDS. Nella serializzazione risultante avremo quindi la sequenza dei dump degli oggetti formalmente salvati aperta ed eventualmente intervallata dai dump degli oggetti *SRDS necessari a veicolare le informazioni descrittive più generali che li riguardano.

Per correttezza dobbiamo osservare che alcuni oggetti non hanno necessità di un dump dedicato in quanto le informazioni che li riguardano rientrano completamente nel contenuto dell’oggetto *SRDS che li menziona. Il caso più classico di questa situazione è l’oggetto *LIB.

Un altro aspetto macroscopico interessante è che la lunghezza record “utile” di un savefile è di 512 byte: questi diventano 528 “effettivi” concatenando 4 byte per il numero sequenza e 12 per un particolare CRC di controllo.

Ai tempi della architettura CISC, 512 era anche la dimensione di una pagina del sistema operativo: col passaggio alla architettura RISC la pagina è diventata di 4KB ma la lunghezza record del savefile non è stata modificata.

La conseguenza è che (senza compressione) alcuni record di un savefile contengono semplicemente zeri esadecimali fino al raggiungimento dei 4KB utili.

Questa “espansione” nella occupazione di memoria -tra l’altro- era oggetto di indagini preliminari ai tempi delle migrazioni CISC-RISC perché le stime precedentemente utilizzate per dimensionare un nuovo server non erano più valide.

Riassumendo, ci muoveremo all’interno di un savefile (non compresso) spostandoci di 8 record per volta per ricostruire le pagine di memoria come 512 * 8 = 4096 byte contigui.

Categorie
Uncategorized

Comprendiamo meglio

Obiettivo delle presenti note è sintetizzare informazioni raccolte nel corso degli anni in merito alle modalità adottate dal sistema operativo IBM i per salvare oggetti presenti a sistema in file trasferibili all’esterno. Tale formato (SAVEFILE), oltre ad offrire la ovvia possibilità di ripristino sul sistema di origine, funge anche da strumento di interscambio dati. La particolarità di interesse è che ogni sistema a versione pari o superiore a quella del sistema in cui gli oggetti erano stati originati è in grado di effettuare un ripristino.

In questa fase possono avere luogo interessanti conversioni che rendono gli oggetti estratti compatibili con la versione del sistema operativo che li ospiterà.

Per i tipi oggetto in cui la conoscenza si è spinta ai massimi livelli di dettaglio è stato possibile generare SAVEFILE anche su piattaforme e sistemi operativi differenti. Questo è l’obiettivo principale di tale studio perché apre la strada ad interessanti sviluppi applicativi.

In sintesi: quello che era uno standard ad uso interno per il trasferimento di oggetti tra sistemi di una unica famiglia può diventare un formato per generare oggetti esclusivamente via software.