// 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");  
                        }
 
                }
 
        }
 
 
 
}