pilot-link (libpisock)
| Language | C |
|---|---|
| License | libpisock: LGPL-2.0-or-later; pilot-link tools: GPL-2.0 |
| Repository | github.com/jichu4n/pilot-link (mirror with fixes for current compilers) |
| Documentation | comments in include/pi-file.h |
| Depends on | nothing for the file API (optional libusb, libpng, popt for the sync tools) |
| Read / write | yes / 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#
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#
#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#
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:
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:
#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#
pi_file_read_recordreturns a pointer into memory owned by thepi_file_t; copy the data if you need it afterpi_file_close.- For
.prcfiles (resource databases, including some Mobipocket books) usepi_file_read_resourceinstead ofpi_file_read_record. - The 32-byte name in the header is often the book title, but it’s limited to 31 characters.