// qnx6label.c -- read/set the volume labels of qnx6fs partitions by Pierre-Marie Baty <pm@pmbaty.com>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#define QNX6FS_SUPERBLOCK_MAGIC 0x68191122
static uint32_t crc32_be (uint32_t crc, const uint8_t *p, size_t len)
{
// big endian CRC32 function to recompute a qnx6fs superblock checksum
const uint32_t CRCPOLY_BE = 0x04c11db7;
size_t bit_index;
while (len--)
{
crc ^= *p++ << 24;
for (bit_index = 0; bit_index < 8; bit_index++)
crc = (crc << 1) ^ ((crc & 0x80000000) ? CRCPOLY_BE : 0);
}
return (crc);
}
int main (int argc, char **argv)
{
// program entrypoint
// references:
// https://docs.kernel.org/filesystems/qnx6.html
// https://github.com/torvalds/linux/blob/master/fs/qnx6/qnx6.h
// https://github.com/torvalds/linux/blob/master/include/linux/qnx6_fs.h
// https://github.com/torvalds/linux/blob/master/fs/qnx6/inode.c
typedef struct __attribute__((packed)) qnx6_rootnode_s
{
uint64_t size;
uint32_t ptr[16]; // 16 block pointers in sbl/inode
uint8_t levels;
uint8_t mode;
uint8_t spare[6];
} qnx6_rootnode_t;
typedef struct __attribute__((packed)) qnx6_superblock_s
{
uint32_t sb_magic; // offset 0 -- "\x22\x11\x19\x68" or 0x68191122
uint32_t sb_checksum; // offset 4
uint64_t sb_serial; // offset 8
uint32_t sb_ctime; // offset 16 -- time the fs was created
uint32_t sb_atime; // offset 20 -- last access time
uint32_t sb_flags; // offset 24
uint16_t sb_version1; // offset 28 -- filesystem version info
uint16_t sb_version2; // offset 30 -- filesystem version info
uint8_t sb_volumeid[16]; // offset 32
uint32_t sb_blocksize; // offset 48
uint32_t sb_num_inodes; // offset 52
uint32_t sb_free_inodes; // offset 56
uint32_t sb_num_blocks; // offset 60
uint32_t sb_free_blocks; // offset 64
uint32_t sb_allocgroup; // offset 68
qnx6_rootnode_t rootnode_inode; // offset 72
qnx6_rootnode_t rootnode_bitmap; // offset 152
qnx6_rootnode_t rootnode_longfile; // offset 232
qnx6_rootnode_t rootnode_unknown; // offset 312
} qnx6_superblock_t; // size 392
typedef struct qnx6_superblock_info_s
{
bool is_mandatory;
size_t offset;
uint8_t bytes[512];
qnx6_superblock_t *sb; // points at beginning of bytes array
} qnx6_superblock_info_t;
qnx6_superblock_info_t qnx6_superblocks[4] =
{
{ true, 0x2000, "", (qnx6_superblock_t *) qnx6_superblocks[0].bytes }, // mandatory first superblock
{ false, 0x2e00, "", (qnx6_superblock_t *) qnx6_superblocks[1].bytes },
{ true, SIZE_MAX, "", (qnx6_superblock_t *) qnx6_superblocks[2].bytes }, // mandatory last superblock
{ false, SIZE_MAX, "", (qnx6_superblock_t *) qnx6_superblocks[3].bytes },
};
qnx6_superblock_t *latest_superblock;
const char *dev_pathname;
size_t superblock_index;
size_t label_offset;
size_t char_index;
size_t end_pos;
uint8_t *p;
char *new_label;
char label[17];
FILE *fp;
// consistency checks
if ((argc < 2) || (*(argv[1]) == '-'))
{
fprintf (stderr
, "usage: qnx6label <device> [new-label]\n");
}
dev_pathname = argv[1]; // read arguments
new_label = (argc > 2 ? argv[2] : NULL);
// open and read all mandatory superblocks of the passed device pathname
if ((fp
= fopen (dev_pathname
, "r+b")) == NULL
)
{
fprintf (stderr
, "error: can't open %s: %s\n", dev_pathname
, strerror (errno
));
}
if (end_pos < 0x4000)
{
fprintf (stderr
, "error: %s too small to contain a qnx6 filesystem\n", dev_pathname
);
}
qnx6_superblocks[2].offset = (end_pos & ~0xfff) - 0x1000; // compute mandatory last superblock offset
qnx6_superblocks[3].offset = (end_pos & ~0xfff) - 0x1000 + 0xe00; // compute optional last superblock offset
latest_superblock = NULL;
for (superblock_index = 0; superblock_index < sizeof (qnx6_superblocks) / sizeof (qnx6_superblocks[0]); superblock_index++)
{
// seek at the right offset and read the (supposed) qnx6fs superblock data
if ((fseek (fp
, qnx6_superblocks
[superblock_index
].
offset, SEEK_SET
) != 0)
|| (fread (qnx6_superblocks
[superblock_index
].
bytes, sizeof (qnx6_superblocks
[superblock_index
].
bytes), 1, fp
) != 1))
{
fprintf (stderr
, "error: can't read superblock #%zd of %s at 0x%zx: %s\n", superblock_index
, dev_pathname
, qnx6_superblocks
[superblock_index
].
offset, strerror (errno
));
}
// validate that all mandatory superblocks are present and keep track of the newest one
if (qnx6_superblocks[superblock_index].sb->sb_magic == QNX6FS_SUPERBLOCK_MAGIC)
{
if ((latest_superblock == NULL) || (qnx6_superblocks[superblock_index].sb->sb_serial > latest_superblock->sb_serial))
latest_superblock = qnx6_superblocks[superblock_index].sb; // update newest superblock pointer
}
else if (qnx6_superblocks[superblock_index].is_mandatory)
{
fprintf (stderr
, "error: superblock #%zd invalid: this does not appear to be a qnx6 filesystem\n", superblock_index
);
}
}
if (latest_superblock == NULL)
{
fprintf (stderr
, "error: no qnx6fs superblock found: this does not appear to be a qnx6 filesystem\n");
}
// do we want to read or set the volume label ?
if (new_label == NULL)
{
// read the label from the newest superblock
memcpy (label
, latest_superblock
->sb_volumeid
, 16);
label[16] = 0;
// print it to the standard output
}
else if (argc == 3)
{
// update the label in all superblocks, fix checksums and update the blocks
for (superblock_index = 0; superblock_index < sizeof (qnx6_superblocks) / sizeof (qnx6_superblocks[0]); superblock_index++)
{
if (qnx6_superblocks[superblock_index].sb->sb_magic != QNX6FS_SUPERBLOCK_MAGIC)
continue; // skip invalid superblocks
strncpy ((char *) qnx6_superblocks
[superblock_index
].
sb->sb_volumeid
, new_label
, sizeof (qnx6_superblocks
[superblock_index
].
sb->sb_volumeid
));
qnx6_superblocks[superblock_index].sb->sb_checksum = crc32_be (0, &qnx6_superblocks[superblock_index].bytes[8], 504); // fix the superblock checksum
if ((fseek (fp
, qnx6_superblocks
[superblock_index
].
offset, SEEK_SET
) != 0)
|| (fwrite (qnx6_superblocks
[superblock_index
].
bytes, sizeof (qnx6_superblocks
[superblock_index
].
bytes), 1, fp
) != 1))
{
fprintf (stderr
, "error: can't update superblock #%zd of %s at 0x%zx: %s\n", superblock_index
, dev_pathname
, qnx6_superblocks
[superblock_index
].
offset, strerror (errno
));
fprintf (stderr
, "*** FILESYSTEM LEFT INCONSISTENT, USE chkqnx6fs TO FIX ***\n");
}
}
}
}