root/sys/arch/sgimips/stand/sgivol/sgivol.c
/*      $NetBSD: sgivol.c,v 1.22 2024/05/13 00:11:22 msaitoh Exp $      */

/*-
 * Copyright (c) 2001 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Michael Hitch and Hubert Feyrer.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#if HAVE_NBTOOL_CONFIG_H
#include "nbtool_config.h"
#endif

#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/stat.h>

#if HAVE_NBTOOL_CONFIG_H
#include "../../../../../sys/sys/bootblock.h"
/* Fictitious geometry for cross tool usage against a file image */
#define SGIVOL_NBTOOL_NSECS     32
#define SGIVOL_NBTOOL_NTRACKS   64
#else
#include <sys/disklabel.h>
#endif

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <util.h>
#include <err.h>
#ifndef HAVE_NBTOOL_CONFIG_H
#include <sys/endian.h>
#endif

int     fd;
int     opt_i;                  /* Initialize volume header */
int     opt_r;                  /* Read a file from volume header */
int     opt_w;                  /* Write a file to volume header */
int     opt_d;                  /* Delete a file from volume header */
int     opt_m;                  /* Move (rename) a file in the volume header */
int     opt_p;                  /* Modify a partition */
int     opt_q;                  /* quiet mode */
int     opt_f;                  /* Don't ask, just do what you're told */
int     partno, partfirst, partblocks, parttype;
struct sgi_boot_block *volhdr;
int32_t checksum;
u_int32_t       volhdr_size = SGI_BOOT_BLOCK_SIZE_VOLHDR;

const char *vfilename = "";
const char *ufilename = "";

#if HAVE_NBTOOL_CONFIG_H
struct stat st;
#else
struct disklabel lbl;
#endif

char buf[512];

const char *sgi_types[] = {
        "Volume Header",
        "Repl Trks",
        "Repl Secs",
        "Raw",
        "BSD4.2",
        "SysV",
        "Volume",
        "EFS",
        "LVol",
        "RLVol",
        "XFS",
        "XSFLog",
        "XLV",
        "XVM"
};

void    display_vol(void);
void    init_volhdr(const char *);
void    read_file(void);
void    write_file(const char *);
void    delete_file(const char *);
void    move_file(const char *);
void    modify_partition(const char *);
void    write_volhdr(const char *);
int     allocate_space(int);
void    checksum_vol(void);
int     names_match(int, const char *);
void    usage(void) __dead;

int
main(int argc, char *argv[])
{
#define RESET_OPTS()    opt_i = opt_m = opt_r = opt_w = opt_d = opt_p = 0

        int ch;
        while ((ch = getopt(argc, argv, "qfih:rwdmp?")) != -1) {
                switch (ch) {
                /* -i, -r, -w, -d, -m and -p override each other */
                /* -q implies -f */
                case 'q':
                        ++opt_q;
                        ++opt_f;
                        break;
                case 'f':
                        ++opt_f;
                        break;
                case 'i':
                        RESET_OPTS();
                        ++opt_i;
                        break;
                case 'h':
                        volhdr_size = atoi(optarg);
                        break;
                case 'r':
                        RESET_OPTS();
                        ++opt_r;
                        break;
                case 'w':
                        RESET_OPTS();
                        ++opt_w;
                        break;
                case 'd':
                        RESET_OPTS();
                        ++opt_d;
                        break;
                case 'm':
                        RESET_OPTS();
                        ++opt_m;
                        break;
                case 'p':
                        RESET_OPTS();
                        ++opt_p;
                        partno = atoi(argv[0]);
                        partfirst = atoi(argv[1]);
                        partblocks = atoi(argv[2]);
                        parttype = atoi(argv[3]);
                        break;
                case '?':
                default:
                        usage();
                }
        }
        argc -= optind;
        argv += optind;

        if (opt_m || opt_r || opt_w) {
                if (argc != 3)
                        usage();
                vfilename = argv[0];
                ufilename = argv[1];
                argc -= 2;
                argv += 2;
        }
        if (opt_d) {
                if (argc != 2)
                        usage();
                vfilename = argv[0];
                argc--;
                argv++;
        }

        if (opt_p) {
                if (argc != 5)
                        usage();
                partno = atoi(argv[0]);
                partfirst = atoi(argv[1]);
                partblocks = atoi(argv[2]);
                parttype = atoi(argv[3]);
                argc -= 4;
                argv += 4;
        }
        if (argc != 1)
                usage();
        
        fd = open(argv[0],
            (opt_i | opt_m | opt_w | opt_d | opt_p) ? O_RDWR : O_RDONLY);
        if (fd == -1) {
#ifndef HAVE_NBTOOL_CONFIG_H
                snprintf(buf, sizeof(buf), "/dev/r%s%c", argv[0],
                    'a' + getrawpartition());
                fd = open(buf, (opt_i | opt_w | opt_d | opt_p) 
                    ? O_RDWR : O_RDONLY);
                if (fd == -1)
#endif
                err(EXIT_FAILURE, "Error opening device `%s'", argv[0]);
        }

        if (read(fd, buf, sizeof(buf)) != sizeof(buf))
                err(EXIT_FAILURE, "Can't read volhdr from `%s'", argv[0]);

#if HAVE_NBTOOL_CONFIG_H
        if (fstat(fd, &st) == -1)
                err(EXIT_FAILURE, "Can't stat `%s'", argv[0]);
        if (!S_ISREG(st.st_mode))
                errx(EXIT_FAILURE, "Not a regular file `%s'", argv[0]);

        if (st.st_size % SGI_BOOT_BLOCK_BLOCKSIZE)
                errx(EXIT_FAILURE, "Size must be multiple of %d", 
                    SGI_BOOT_BLOCK_BLOCKSIZE);
        if (st.st_size < (SGIVOL_NBTOOL_NSECS * SGIVOL_NBTOOL_NTRACKS))
                errx(EXIT_FAILURE, "Minimum size of %d required",
                    SGIVOL_NBTOOL_NSECS * SGIVOL_NBTOOL_NTRACKS);
#else
        if (ioctl(fd, DIOCGDINFO, &lbl) == -1)
                err(EXIT_FAILURE, "ioctl DIOCGDINFO failed");
#endif
        volhdr = (struct sgi_boot_block *) buf;
        if (opt_i) {
                init_volhdr(argv[0]);
                return 0;
        }
        if (be32toh(volhdr->magic) != SGI_BOOT_BLOCK_MAGIC)
                errx(EXIT_FAILURE, "No Volume Header found, magic=%x. "
                    "Use -i first.\n", be32toh(volhdr->magic));
        if (opt_r) {
                read_file();
                return 0;
        }
        if (opt_w) {
                write_file(argv[0]);
                return 0;
        }
        if (opt_d) {
                delete_file(argv[0]);
                return 0;
        }
        if (opt_m) {
                move_file(argv[0]);
                return 0;
        }
        if (opt_p) {
                modify_partition(argv[0]);
                return 0;
        }

        if (!opt_q)
                display_vol();

        return 0;
}

/*
 * Compare the name in `slot' of voldir to `b'. Be careful, as the
 * name in voldir need not be nul-terminated and `b' may be longer
 * than the maximum (in which case it will never match).
 *
 * Returns non-0 if names are equal.
 */
int
names_match(int slot, const char *b)
{
        int cmp;

        if (slot < 0 || slot >= SGI_BOOT_BLOCK_MAXVOLDIRS)
                errx(EXIT_FAILURE, "Internal error: bad slot in %s()",
                    __func__);

        cmp = strncmp(volhdr->voldir[slot].name, b,
            sizeof(volhdr->voldir[slot].name));

        return cmp == 0 && strlen(b) <= sizeof(volhdr->voldir[slot].name);
}

void
display_vol(void)
{
        int32_t *l;
        int i;

#if HAVE_NBTOOL_CONFIG_H
        printf("disklabel shows %d sectors\n", 
            st.st_size / SGI_BOOT_BLOCK_BLOCKSIZE);
#else
        printf("disklabel shows %d sectors\n", lbl.d_secperunit);
#endif
        l = (int32_t *)buf;
        checksum = 0;
        for (i = 0; i < 512 / 4; ++i)
                checksum += be32toh(l[i]);
        printf("checksum: %08x%s\n", checksum, checksum == 0 ? "" : " *ERROR*");
        printf("root part: %d\n", be16toh(volhdr->root));
        printf("swap part: %d\n", be16toh(volhdr->swap));
        printf("bootfile: %s\n", volhdr->bootfile);
        /* volhdr->devparams[0..47] */
        printf("\nVolume header files:\n");
        for (i = 0; i < SGI_BOOT_BLOCK_MAXVOLDIRS; ++i)
                if (volhdr->voldir[i].name[0])
                        printf("%-8s offset %4d blocks, length %8d bytes "
                            "(%d blocks)\n",
                            volhdr->voldir[i].name, 
                            be32toh(volhdr->voldir[i].block),
                            be32toh(volhdr->voldir[i].bytes), 
                            (be32toh(volhdr->voldir[i].bytes) + 511) / 512);
        printf("\nSGI partitions:\n");
        for (i = 0; i < SGI_BOOT_BLOCK_MAXPARTITIONS; ++i) {
                if (be32toh(volhdr->partitions[i].blocks)) {
                        printf("%2d:%c blocks %8d first %8d type %2d (%s)\n",
                          i, i + 'a', be32toh(volhdr->partitions[i].blocks),
                               be32toh(volhdr->partitions[i].first),
                               be32toh(volhdr->partitions[i].type),
                          be32toh(volhdr->partitions[i].type) > 13 ? "???" :
                            sgi_types[be32toh(volhdr->partitions[i].type)]);
                }
        }
}

void
init_volhdr(const char *fname)
{
        memset(buf, 0, sizeof(buf));
        volhdr->magic = htobe32(SGI_BOOT_BLOCK_MAGIC);
        volhdr->root = htobe16(0);
        volhdr->swap = htobe16(1);
        strcpy(volhdr->bootfile, "/netbsd");
#if HAVE_NBTOOL_CONFIG_H
        volhdr->dp.dp_skew = 0;
#else
        volhdr->dp.dp_skew = lbl.d_trackskew;
#endif
        volhdr->dp.dp_gap1 = 1; /* XXX */
        volhdr->dp.dp_gap2 = 1; /* XXX */
#if HAVE_NBTOOL_CONFIG_H        
        volhdr->dp.dp_cyls = 
            htobe16(st.st_size / (SGIVOL_NBTOOL_NSECS * SGIVOL_NBTOOL_NTRACKS));
#else
        volhdr->dp.dp_cyls = htobe16(lbl.d_ncylinders);
#endif
        volhdr->dp.dp_shd0 = 0;
#if HAVE_NBTOOL_CONFIG_H
        volhdr->dp.dp_trks0 = htobe16(SGIVOL_NBTOOL_NTRACKS);
        volhdr->dp.dp_secs = htobe16(SGIVOL_NBTOOL_NSECS);
        volhdr->dp.dp_secbytes = htobe16(SGI_BOOT_BLOCK_BLOCKSIZE);
        volhdr->dp.dp_interleave = htobe16(1);
#else
        volhdr->dp.dp_trks0 = htobe16(lbl.d_ntracks);
        volhdr->dp.dp_secs = htobe16(lbl.d_nsectors);
        volhdr->dp.dp_secbytes = htobe16(lbl.d_secsize);
        volhdr->dp.dp_interleave = htobe16(lbl.d_interleave);
#endif
        volhdr->dp.dp_nretries = htobe32(22);
#if HAVE_NBTOOL_CONFIG_H
        volhdr->partitions[10].blocks = 
            htobe32(st.st_size / SGI_BOOT_BLOCK_BLOCKSIZE);
#else
        volhdr->partitions[10].blocks = htobe32(lbl.d_secperunit);
#endif
        volhdr->partitions[10].first = 0;
        volhdr->partitions[10].type = htobe32(SGI_PTYPE_VOLUME);
        volhdr->partitions[8].blocks = htobe32(volhdr_size);
        volhdr->partitions[8].first = 0;
        volhdr->partitions[8].type = htobe32(SGI_PTYPE_VOLHDR);
#if HAVE_NBTOOL_CONFIG_H
        volhdr->partitions[0].blocks = 
            htobe32((st.st_size / SGI_BOOT_BLOCK_BLOCKSIZE) - volhdr_size);
#else
        volhdr->partitions[0].blocks = htobe32(lbl.d_secperunit - volhdr_size);
#endif
        volhdr->partitions[0].first = htobe32(volhdr_size);
        volhdr->partitions[0].type = htobe32(SGI_PTYPE_BSD);
        write_volhdr(fname);
}

void
read_file(void)
{
        FILE *fp;
        int i;

        if (!opt_q)
                printf("Reading file %s\n", vfilename);
        for (i = 0; i < SGI_BOOT_BLOCK_MAXVOLDIRS; ++i) {
                if (strncmp(vfilename, volhdr->voldir[i].name,
                            strlen(volhdr->voldir[i].name)) == 0)
                        break;
        }
        if (i >= SGI_BOOT_BLOCK_MAXVOLDIRS)
                errx(EXIT_FAILURE, "File `%s' not found", vfilename);
        /* XXX assumes volume header starts at 0? */
        lseek(fd, be32toh(volhdr->voldir[i].block) * 512, SEEK_SET);
        fp = fopen(ufilename, "w");
        if (fp == NULL)
                err(EXIT_FAILURE, "Can't open `%s'", ufilename);
        i = be32toh(volhdr->voldir[i].bytes);
        while (i > 0) {
                if (read(fd, buf, sizeof(buf)) != sizeof(buf))
                        err(EXIT_FAILURE, "Error reading from `%s'", ufilename);
                fwrite(buf, 1, i > sizeof(buf) ? sizeof(buf) : i, fp);
                i -= i > sizeof(buf) ? sizeof(buf) : i;
        }
        fclose(fp);
}

void
write_file(const char *fname)
{
        FILE *fp;
        int slot;
        size_t namelen;
        int block, i;
        off_t off;
        struct stat st;
        char fbuf[512];

        if (!opt_q)
                printf("Writing file %s\n", ufilename);
        if (stat(ufilename, &st) == -1)
                err(EXIT_FAILURE, "Can't stat `%s'", ufilename);
        if (!opt_q)
                printf("File %s has %ju bytes\n", ufilename,
                    (uintmax_t)st.st_size);
        slot = -1;
        for (i = 0; i < SGI_BOOT_BLOCK_MAXVOLDIRS; ++i) {
                if (volhdr->voldir[i].name[0] == '\0' && slot < 0)
                        slot = i;
                if (names_match(i, vfilename)) {
                        slot = i;
                        break;
                }
        }
        if (slot == -1)
                errx(EXIT_FAILURE, "No directory space for file %s", vfilename);
        /* -w can overwrite, -a won't overwrite */
        if (be32toh(volhdr->voldir[slot].block) > 0) {
                if (!opt_q)
                        printf("File %s exists, removing old file\n", 
                                vfilename);
                volhdr->voldir[slot].name[0] = 0;
                volhdr->voldir[slot].block = volhdr->voldir[slot].bytes = 0;
        }
        if (st.st_size == 0) {
                errx(EXIT_FAILURE, "Empty file `%s'", ufilename);
                exit(1);
        }
        /* XXX assumes volume header starts at 0? */
        block = allocate_space((int)st.st_size);
        if (block < 0)
                errx(EXIT_FAILURE, "No space for file `%s'", vfilename);

        /*
         * Make sure the name in the volume header is max. 8 chars,
         * NOT including NUL.
         */
        namelen = strlen(vfilename);
        if (namelen > sizeof(volhdr->voldir[slot].name)) {
                printf("Warning: '%s' is too long for volume header, ",
                       vfilename);
                namelen = sizeof(volhdr->voldir[slot].name);
                printf("truncating to '%.8s'\n", vfilename);
        }

        /* Populate it w/ NULs */
        memset(volhdr->voldir[slot].name, 0,
            sizeof(volhdr->voldir[slot].name));
        /* Then copy the name */
        memcpy(volhdr->voldir[slot].name, vfilename, namelen);

        volhdr->voldir[slot].block = htobe32(block);
        volhdr->voldir[slot].bytes = htobe32(st.st_size);

        write_volhdr(fname);

        /* write the file itself */
        off = lseek(fd, block * 512, SEEK_SET);
        if (off == -1)
                err(EXIT_FAILURE, "Seek failed `%s'", fname);
        i = st.st_size;
        fp = fopen(ufilename, "r");
        if (fp == NULL)
                err(EXIT_FAILURE, "Can't open `%s'", ufilename);
        while (i > 0) {
                int j = i > 512 ? 512 : i;
                if (fread(fbuf, 1, j, fp) != j)
                        err(EXIT_FAILURE, "Can't read `%s'", ufilename);
                if (write(fd, fbuf, 512) != 512)
                        err(EXIT_FAILURE, "Can't write `%s'", fname);
                i -= j;
        }
        fclose(fp);
}

void
delete_file(const char *fname)
{
        int i;

        for (i = 0; i < SGI_BOOT_BLOCK_MAXVOLDIRS; ++i) {
                if (names_match(i, vfilename)) {
                        break;
                }
        }
        if (i >= SGI_BOOT_BLOCK_MAXVOLDIRS)
                errx(EXIT_FAILURE, "File `%s' not found", vfilename);

        /* XXX: we don't compact the file space, so get fragmentation */
        volhdr->voldir[i].name[0] = '\0';
        volhdr->voldir[i].block = volhdr->voldir[i].bytes = 0;
        write_volhdr(fname);
}

void
move_file(const char *fname)
{
        char dstfile[sizeof(volhdr->voldir[0].name) + 1];
        size_t namelen;
        int i, slot = -1;

        /*
         * Make sure the name in the volume header is max. 8 chars,
         * NOT including NUL.
         */
        namelen = strlen(ufilename);
        if (namelen > sizeof(volhdr->voldir[0].name)) {
                printf("Warning: '%s' is too long for volume header, ",
                       ufilename);
                namelen = sizeof(volhdr->voldir[0].name);
                printf("truncating to '%.8s'\n", ufilename);
        }
        memset(dstfile, 0, sizeof(dstfile));
        memcpy(dstfile, ufilename, namelen);

        for (i = 0; i < SGI_BOOT_BLOCK_MAXVOLDIRS; i++) {
                if (names_match(i, vfilename)) {
                        if (slot != -1)
                                errx(EXIT_FAILURE,
                                    "Error: Cannot move '%s' to '%s' - "
                                    "duplicate source files exist!",
                                    vfilename, dstfile);
                        slot = i;
                }
                if (names_match(i, dstfile))
                        errx(EXIT_FAILURE, "Error: Cannot move '%s' to '%s' - "
                            "destination file already exists!",
                            vfilename, dstfile);
        }
        if (slot == -1)
                errx(EXIT_FAILURE, "File `%s' not found", vfilename);

        /* `dstfile' is already padded with NULs */ 
        memcpy(volhdr->voldir[slot].name, dstfile,
            sizeof(volhdr->voldir[slot].name));
        
        write_volhdr(fname);
}

void
modify_partition(const char *fname)
{
        if (!opt_q)
                printf("Modify partition %d start %d length %d\n", 
                        partno, partfirst, partblocks);
        if (partno < 0 || partno >= SGI_BOOT_BLOCK_MAXPARTITIONS)
                errx(EXIT_FAILURE, "Invalid partition number: %d", partno);

        volhdr->partitions[partno].blocks = htobe32(partblocks);
        volhdr->partitions[partno].first = htobe32(partfirst);
        volhdr->partitions[partno].type = htobe32(parttype);
        write_volhdr(fname);
}

void
write_volhdr(const char *fname)
{
        int i;

        checksum_vol();

        if (!opt_q)
                display_vol();
        if (!opt_f) {
                printf("\nDo you want to update volume (y/n)? ");
                i = getchar();
                if (i != 'Y' && i != 'y')
                        exit(1);
        }
        i = lseek(fd, 0, SEEK_SET);
        if (i < 0) {
                perror("lseek 0");
                exit(1);
        }
        i = write(fd, buf, 512);
        if (i < 0)
                errx(EXIT_FAILURE, "write volhdr `%s'", fname);
}

int
allocate_space(int size)
{
        int n, blocks;
        int first;

        blocks = (size + 511) / 512;
        first = 2;
        n = 0;
        while (n < SGI_BOOT_BLOCK_MAXVOLDIRS) {
                if (volhdr->voldir[n].name[0]) {
                        if (first < (be32toh(volhdr->voldir[n].block) +
                          (be32toh(volhdr->voldir[n].bytes) + 511) / 512) &&
                            (first + blocks) > be32toh(volhdr->voldir[n].block)) {
                                first = be32toh(volhdr->voldir[n].block) +
                                        (be32toh(volhdr->voldir[n].bytes) + 511) / 512;
#if 0
                                printf("allocate: n=%d first=%d blocks=%d size=%d\n", n, first, blocks, size);
                                printf("%s %d %d\n", volhdr->voldir[n].name, volhdr->voldir[n].block, volhdr->voldir[n].bytes);
                                printf("first=%d block=%d last=%d end=%d\n", first, volhdr->voldir[n].block,
                                       first + blocks - 1, volhdr->voldir[n].block + (volhdr->voldir[n].bytes + 511) / 512);
#endif
                                n = 0;
                                continue;
                        }
                }
                ++n;
        }
#if HAVE_NBTOOL_CONFIG_H
        if (first + blocks > (st.st_size / SGI_BOOT_BLOCK_BLOCKSIZE))
#else
        if (first + blocks > lbl.d_secperunit)
#endif
                first = -1;
        /* XXX assumes volume header is partition 8 */
        /* XXX assumes volume header starts at 0? */
        if (first + blocks >= be32toh(volhdr->partitions[8].blocks))
                first = -1;
        return (first);
}

void
checksum_vol(void)
{
        int32_t *l;
        int i;

        volhdr->checksum = checksum = 0;
        l = (int32_t *)buf;
        for (i = 0; i < 512 / 4; ++i)
                checksum += be32toh(l[i]);
        volhdr->checksum = htobe32(-checksum);
}

void
usage(void)
{
        const char *p = getprogname();
        printf("Usage:\t%s [-qf] -i [-h vhsize] device\n"
               "\t%s [-qf] -r vhfilename diskfilename device\n"
               "\t%s [-qf] -w vhfilename diskfilename device\n"
               "\t%s [-qf] -d vhfilename device\n"
               "\t%s [-qf] -m vhfilename vhfilename device\n"
               "\t%s [-qf] -p partno partfirst partblocks "
               "parttype device\n", p, p, p, p, p, p);
        exit(0);
}