All formats

pilot-link (libpisock)

LanguageC
Licenselibpisock: LGPL-2.0-or-later; pilot-link tools: GPL-2.0
Repositorygithub.com/jichu4n/pilot-link (mirror with fixes for current compilers)
Documentationcomments in include/pi-file.h
Depends onnothing for the file API (optional libusb, libpng, popt for the sync tools)
Read / writeyes / yes, PDB and PRC containers; no PalmDOC compression

pilot-link is the set of libraries and tools that synced Palm devices with Linux and Unix computers. Its library, libpisock, includes pi-file, an API for database files on disk: pi_file_open reads a .pdb or .prc file, pi_file_get_info returns the header (name, type, creator, dates), and pi_file_read_record returns records. pi_file_create and pi_file_append_record write new files.

It handles the container only. PalmDOC decompression, eReader and MOBI headers are up to you. The project stopped development around 2007; the GitHub mirror keeps it building with current autotools and compilers. Distributions no longer package it.

Installation#

Shell
git clone https://github.com/jichu4n/pilot-link
cd pilot-link
./autogen.sh
./configure --prefix=/usr/local --without-libpng --disable-conduits
make
sudo make install

Compile your program with cc prog.c -lpisock (add -I/usr/local/include -L/usr/local/lib for that prefix).

Basic usage: extract the text of a PalmDOC book#

C
#include <stdio.h>
#include <string.h>
#include <pi-file.h>

#define TAG(a, b, c, d) (((unsigned long)(a) << 24) | ((b) << 16) | ((c) << 8) | (d))

/* PalmDOC LZ77: decompress one record into out (at most 4096 bytes) */
static size_t palmdoc_decompress(const unsigned char *in, size_t n, unsigned char *out, size_t cap) {
    size_t i = 0, o = 0;
    while (i < n && o < cap) {
        unsigned c = in[i++];
        if (c >= 1 && c <= 8) {                 /* copy c literal bytes */
            while (c-- && i < n && o < cap) out[o++] = in[i++];
        } else if (c < 0x80) {                  /* single literal byte */
            out[o++] = c;
        } else if (c >= 0xC0) {                 /* space + character */
            out[o++] = ' ';
            if (o < cap) out[o++] = c ^ 0x80;
        } else if (i < n) {                     /* distance / length pair */
            unsigned pair = ((c << 8) | in[i++]) & 0x3FFF;
            size_t dist = pair >> 3, len = (pair & 7) + 3;
            if (dist == 0 || dist > o) break;   /* corrupt data */
            while (len-- && o < cap) { out[o] = out[o - dist]; o++; }
        }
    }
    return o;
}

int main(int argc, char **argv) {
    if (argc < 2) return 1;
    pi_file_t *pf = pi_file_open(argv[1]);
    if (!pf) { fprintf(stderr, "can't open %s\n", argv[1]); return 1; }

    struct DBInfo info;
    pi_file_get_info(pf, &info);
    if (info.type != TAG('T','E','X','t') || info.creator != TAG('R','E','A','d')) {
        fprintf(stderr, "not a PalmDOC book\n");
        pi_file_close(pf);
        return 1;
    }

    void *buf;
    size_t size;
    if (pi_file_read_record(pf, 0, &buf, &size, NULL, NULL, NULL) < 0 || size < 16) {
        pi_file_close(pf);
        return 1;
    }
    const unsigned char *r0 = buf;
    int compression = (r0[0] << 8) | r0[1];     /* 1 = none, 2 = PalmDOC */
    int text_records = (r0[8] << 8) | r0[9];

    fprintf(stderr, "title: %s\n", info.name);
    for (int i = 1; i <= text_records; i++) {
        if (pi_file_read_record(pf, i, &buf, &size, NULL, NULL, NULL) < 0) break;
        if (compression == 2) {
            unsigned char out[4096];
            size_t len = palmdoc_decompress(buf, size, out, sizeof(out));
            fwrite(out, 1, len, stdout);
        } else {
            fwrite(buf, 1, size, stdout);
        }
    }
    pi_file_close(pf);   /* record buffers belong to pf: don't free them */
    return 0;
}

How to list the records in a PDB file#

C
pi_file_t *pf = pi_file_open("book.pdb");
int count = 0;
pi_file_get_entries(pf, &count);
for (int i = 0; i < count; i++) {
    void *buf; size_t size; int attrs, category; recordid_t uid;
    if (pi_file_read_record(pf, i, &buf, &size, &attrs, &category, &uid) >= 0)
        printf("record %d: %zu bytes, uid %lu\n", i, size, (unsigned long)uid);
}
pi_file_close(pf);

How to read the header#

struct DBInfo (from pi-dlp.h) has the name, flags, version, type, creator and dates converted to time_t:

C
struct DBInfo info;
pi_file_get_info(pf, &info);
printf("name: %s\n", info.name);
printf("type/creator: %c%c%c%c/%c%c%c%c\n",
       (int)(info.type >> 24), (int)(info.type >> 16) & 0xFF,
       (int)(info.type >> 8) & 0xFF, (int)info.type & 0xFF,
       (int)(info.creator >> 24), (int)(info.creator >> 16) & 0xFF,
       (int)(info.creator >> 8) & 0xFF, (int)info.creator & 0xFF);
printf("created: %s", ctime(&info.createDate));

How to create a PalmDOC file (uncompressed)#

Compression 1 (none) is valid PalmDOC, so the simplest writer stores the text in 4096-byte records:

C
#include <string.h>
#include <time.h>
#include <pi-file.h>

#define TAG(a, b, c, d) (((unsigned long)(a) << 24) | ((b) << 16) | ((c) << 8) | (d))

int write_palmdoc(const char *path, const char *title, const unsigned char *text, size_t len) {
    struct DBInfo info;
    memset(&info, 0, sizeof(info));
    strncpy(info.name, title, sizeof(info.name) - 1);
    info.type = TAG('T','E','X','t');
    info.creator = TAG('R','E','A','d');
    info.createDate = info.modifyDate = time(NULL);

    pi_file_t *pf = pi_file_create(path, &info);
    if (!pf) return -1;

    unsigned short nrec = (unsigned short)((len + 4095) / 4096);
    unsigned char r0[16] = {0};
    r0[1] = 1;                                     /* no compression */
    r0[4] = len >> 24; r0[5] = len >> 16; r0[6] = len >> 8; r0[7] = len;
    r0[8] = nrec >> 8; r0[9] = nrec;
    r0[10] = 0x10; r0[11] = 0x00;                  /* 4096 */
    pi_file_append_record(pf, r0, sizeof(r0), 0, 0, 0);

    for (size_t off = 0; off < len; off += 4096) {
        size_t n = len - off < 4096 ? len - off : 4096;
        pi_file_append_record(pf, (void *)(text + off), n, 0, 0, 0);  /* uid 0 = auto */
    }
    return pi_file_close(pf);                      /* writes the file */
}

Tips#