// doslabel.c -- read/set the volume labels of DOS partitions by Pierre-Marie Baty <pm@pmbaty.com>
 
 
 
#include <stdio.h>
 
#include <stdlib.h>
 
#include <string.h>
 
#include <errno.h>
 
#include <ctype.h>
 
 
 
 
 
int main (int argc, char **argv)
 
{
 
        // program entrypoint
 
 
 
        typedef struct __attribute__((packed)) fatfs_header_s
 
        {
 
                unsigned char bootstrap_code[3]; // offset 0
 
                char oem_name_and_version[8]; // offset 3
 
                unsigned short bytes_per_sector; // offset 11
 
                unsigned char sectors_per_cluster; // offset 13
 
                unsigned short reserved_sectors; // offset 14
 
                unsigned char fat_copies; // offset 16
 
                unsigned short rootdir_entries; // offset 17
 
                unsigned short total_sectors; // offset 19
 
                unsigned char media_type; // offset 21
 
                unsigned short sectors_per_fat; // offset 22
 
                unsigned short sectors_per_track; // offset 24
 
                unsigned short heads; // offset 26
 
                unsigned short hidden_sectors; // offset 28;
 
        } fatfs_header_t;
 
 
 
        unsigned char first_sector[512];
 
        fatfs_header_t *fat_header;
 
        const char *dev_pathname;
 
        size_t label_offset;
 
        size_t char_index;
 
        char *new_label;
 
        char label[12];
 
        FILE *fp;
 
 
 
        // consistency checks
 
        if ((argc < 2) || (*(argv[1]) == '-'))
 
        {
 
                fprintf (stderr
, "usage: doslabel <device> [new-label]\n");  
        }
 
 
 
        dev_pathname = argv[1]; // read arguments
 
        new_label = (argc > 2 ? argv[2] : NULL);
 
 
 
        // open and read the first sector of the passed device pathname
 
        if (((fp 
= fopen (dev_pathname
, "r+b")) == NULL
) || (fread (first_sector
, 512, 1, fp
) != 1))  
        {
 
                fprintf (stderr
, "error: can't read first sector of %s: %s\n", dev_pathname
, strerror (errno
));  
        }
 
 
 
        // consistency checks
 
        fat_header = (fatfs_header_t *) first_sector;
 
 
 
        // is it a FAT12/FAT16 or FAT32 BPB ?
 
        if ((memcmp (&first_sector
[510], "\x55\xaa", 2) == 0)  
            && (   (fat_header->bytes_per_sector == 512)
 
                || (fat_header->bytes_per_sector == 1024)
 
                || (fat_header->bytes_per_sector == 2048)
 
                || (fat_header->bytes_per_sector == 4096))
 
            && (   (fat_header->sectors_per_cluster == 1)
 
                || (fat_header->sectors_per_cluster == 2)
 
                || (fat_header->sectors_per_cluster == 4)
 
                || (fat_header->sectors_per_cluster == 8)
 
                || (fat_header->sectors_per_cluster == 16)
 
                || (fat_header->sectors_per_cluster == 32)
 
                || (fat_header->sectors_per_cluster == 64)
 
                || (fat_header->sectors_per_cluster == 128))
 
            && (   (fat_header->reserved_sectors == 1)
 
                || (fat_header->reserved_sectors == 32)))
 
                label_offset = (fat_header->sectors_per_fat != 0 ? 43 : 71);
 
        else
 
        {
 
                // volume labels in exFAT filesystems are actually directory entries with a special flag
 
                // we don't support that at the moment (FIXME)
 
                // that's fine, because EFI partitions can't be exFAT -- only FAT12/16.
 
                fprintf (stderr
, "error: only FAT12/FAT16/FAT32 filesystems are supported\n");  
        }
 
 
 
        // do we want to read or set the volume label ?
 
        if (new_label == NULL)
 
        {
 
                // read the label and strip it from trailing spaces
 
                memcpy (label
, &first_sector
[label_offset
], 11);  
                label[11] = 0;
 
                for (char_index = 10; char_index > 0; char_index--)
 
                        if (label[char_index] == ' ')
 
                                label[char_index] = 0;
 
                        else
 
                                break;
 
                if (label[char_index] == ' ')
 
                        label[char_index] = 0;
 
 
 
                // print it to the standard output
 
        }
 
        else if (argc == 3)
 
        {
 
                // write a new DOS partition label
 
                fseek (fp
, label_offset
, SEEK_SET
); // can't fail  
                memset (label
, ' ', sizeof (label
));  
                if (fwrite (label
, 11, 1, fp
) != 1)  
                {
 
                        fprintf (stderr
, "error: can't write new volume label to %s: %s\n", dev_pathname
, strerror (errno
));  
                }
 
        }
 
 
 
}