/* $NetBSD: sgivol.c,v 1.21 2014/03/28 15:00:53 christos 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"
/* Ficticious 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);
}