root/usr.bin/fdformat/fdformat.c
/*      $NetBSD: fdformat.c,v 1.18 2021/11/27 22:16:41 rillig Exp $     */

/*-
 * Copyright (c) 1996, 1997 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by John Kohl.
 *
 * 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.
 */

/*
 * fdformat: format a floppy diskette, using interface provided in
 * <sys/fdio.h>
 */
#include <sys/cdefs.h>

#ifndef lint
__RCSID("$NetBSD: fdformat.c,v 1.18 2021/11/27 22:16:41 rillig Exp $");
#endif

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

#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "pathnames.h"

static const char *fdb_array[2] = {_PATH_FLOPPYTAB, 0};

#define MASK_NBPS               0x0001
#define MASK_NCYL               0x0002
#define MASK_NSPT               0x0004
#define MASK_NTRK               0x0008
#define MASK_STEPSPERCYL        0x0010
#define MASK_GAPLEN             0x0020
#define MASK_FILLBYTE           0x0040
#define MASK_XFER_RATE          0x0080
#define MASK_INTERLEAVE         0x0100

#define ALLPARMS (MASK_NBPS|MASK_NCYL|MASK_NSPT|MASK_NTRK|MASK_STEPSPERCYL|MASK_GAPLEN|MASK_FILLBYTE|MASK_XFER_RATE|MASK_INTERLEAVE)

static int      confirm(int);
static void     usage(void) __dead;
static int      verify_track(int, int, int, struct fdformat_parms *, char *);

static int
confirm(int def)
{
        int ch;

        (void)printf(" Yes/no [%c]?", def ? 'y' : 'n');
        ch = getchar();
        switch (ch) {
        case 'y':
        case 'Y':
                return 1;
        case '\n':
                return def;
        case EOF:
        case 'n':
        case 'N':
        default:
                return 0;
        }               
}

static int
verify_track(int fd, int cyl, int trk, struct fdformat_parms *parms, char *buf)
{
        size_t tracksize;
        off_t offset;

        tracksize = parms->nbps * parms->nspt; /* bytes per track */
        offset = tracksize * (cyl * parms->ntrk + trk); /* track offset */

        if (lseek(fd, offset, SEEK_SET) == (off_t) -1) {
                (void)printf("- SEEK ERROR\n");
                return 1;
        }
        if ((size_t)read(fd, buf, tracksize) != tracksize) {
                (void)printf("- VERIFY ERROR\n");
                return 1;
        }
        return 0;
}

static void
usage(void)
{
        (void)fprintf(stderr,
            "Usage: %s [-f device] [-t type] [-n] [-B nbps] [-S nspt]\n"
            "\t[-T ntrk] [-C ncyl] [-P stepspercyl] [-G gaplen]\n"
            "\t[-F fillbyte] [-X xfer_rate] [-I interleave]\n", getprogname());
        exit(1);
}

#define numarg(which, maskn, op)                                \
        do {                                                    \
                tmplong = strtol(optarg, &tmpcharp, 0);         \
                if (*tmpcharp != '\0' || tmplong op 0)          \
                    errx(1,                                     \
                        "Invalid numerical argument `%s' for "  \
                        # which, optarg);                       \
                if (errno == ERANGE && (tmplong == LONG_MIN ||  \
                    tmplong == LONG_MAX))                       \
                    err(1,                                      \
                        "Bad numerical argument `%s' for "      \
                        # which, optarg);                       \
                parms.which = tmplong;                          \
                parmmask |= MASK_##maskn;                       \
        } while (0)

#define getparm(structname, maskname)                           \
        do {                                                    \
                if (cgetnum(fdbuf, # structname, &tmplong) == -1) \
                        errx(1, "Parameter " # structname       \
                            " missing for type `%s'", optarg);  \
                parms.structname = tmplong;                     \
                parmmask |= MASK_ ## maskname;                  \
        } while (0)


#define copyparm(which, mask)                                   \
        if ((parmmask & MASK_##mask) == 0)                      \
                parms.which = fetchparms.which

int
main(int argc, char *argv[])
{
        char *fdbuf = NULL, *trackbuf = NULL;
        int errcnt = 0;
        int verify = 1;
        int ch;
        long tmplong;
        int tmpint;
        char *tmpcharp;
        int parmmask = 0;
        struct fdformat_parms parms, fetchparms;
        struct fdformat_cmd cmd;
        const char *filename = _PATH_FLOPPY_DEV;
        int fd;
        int trk, cyl;

        while ((ch = getopt(argc, argv, "f:t:nB:C:S:T:P:G:F:X:I:")) != -1)
                switch (ch) {
                case 't':               /* disk type */
                        switch (cgetent(&fdbuf, fdb_array, optarg)) {
                        case 0:
                                break;
                        case 1:
                        case -3:
                                errx(1, "tc= loop or missing entry entry in "
                                     _PATH_FLOPPYTAB " for type %s", optarg);
                                break;
                        case -1:
                                errx(1, "Unknown floppy disk type %s", optarg);
                                break;
                        default:
                                err(1, "Problem accessing " _PATH_FLOPPYTAB);
                                break;
                        }

                        getparm(nbps, NBPS);
                        getparm(ncyl, NCYL);
                        getparm(nspt, NSPT);
                        getparm(ntrk, NTRK);
                        getparm(stepspercyl, STEPSPERCYL);
                        getparm(gaplen, GAPLEN);
                        getparm(fillbyte, FILLBYTE);
                        getparm(xfer_rate, XFER_RATE);
                        getparm(interleave, INTERLEAVE);
                        break;
                case 'f':               /* device name */
                        filename = optarg;
                        break;
                case 'n':               /* no verify */
                        verify = 0;
                        break;
                case 'B':
                        numarg(nbps, NBPS, <=);
                        break;
                case 'C':
                        numarg(ncyl, NCYL, <=);
                        break;
                case 'S':
                        numarg(nspt, NSPT, <=);
                        break;
                case 'T':
                        numarg(ntrk, NTRK, <=);
                        break;
                case 'P':
                        numarg(stepspercyl, STEPSPERCYL, <=);
                        break;
                case 'G':
                        numarg(gaplen, GAPLEN, <=);
                        break;
                case 'F':
                        numarg(fillbyte, FILLBYTE, <);
                        break;
                case 'X':
                        numarg(xfer_rate, XFER_RATE, <=);
                        break;
                case 'I':
                        numarg(interleave, INTERLEAVE, <=);
                        break;
                case '?':
                default:
                        usage();
                }

        if (optind < argc)
                usage();

        fd = open(filename, O_RDWR);
        if (fd == -1)
                err(1, "Cannot open %s", filename);
        if (ioctl(fd, FDIOCGETFORMAT, &fetchparms) == -1) {
                if (errno == ENOTTY)
                        err(1, "Device `%s' does not support floppy formatting",
                            filename);
                else
                        err(1, "Cannot fetch current floppy"
                            " formatting parameters");
        }

        copyparm(nbps, NBPS);
        copyparm(ncyl, NCYL);
        copyparm(nspt, NSPT);
        copyparm(ntrk, NTRK);
        copyparm(stepspercyl, STEPSPERCYL);
        copyparm(gaplen, GAPLEN);
        copyparm(fillbyte, FILLBYTE);
        copyparm(xfer_rate, XFER_RATE);
        copyparm(interleave, INTERLEAVE);

        parms.fdformat_version = FDFORMAT_VERSION;

        tmpint = FDOPT_NORETRY|FDOPT_SILENT;
        if (ioctl(fd, FDIOCSETOPTS, &tmpint) == -1 ||
            ioctl(fd, FDIOCSETFORMAT, &parms) == -1) {
                errx(1, "Cannot set requested formatting parameters:"
                    " %d cylinders, %d tracks, %d sectors of %d bytes",
                    parms.ncyl, parms.ntrk, parms.nspt, parms.nbps);
        }

        (void)printf("Ready to format %s with %d cylinders, %d tracks,"
            " %d sectors of %d bytes\n(%d KB)",
            filename, parms.ncyl, parms.ntrk, parms.nspt, parms.nbps,
            parms.ncyl * parms.ntrk * parms.nspt * parms.nbps / 1024);
        if (!confirm(1))
                errx(1,"Formatting abandoned -- not confirmed.");
        
        if (verify) {
                trackbuf = malloc(parms.nbps * parms.nspt);
                if (trackbuf == NULL)
                        warn("Cannot allocate verification buffer");
        }

        cmd.formatcmd_version = FDFORMAT_VERSION;
        for (cyl = 0; (unsigned int)cyl < parms.ncyl; cyl++) {
                cmd.cylinder = cyl;
                for (trk = 0; (unsigned int)trk < parms.ntrk; trk++) {
                        cmd.head = trk;
                        (void)printf("\rFormatting track %i / head %i ", cyl, trk);
                        (void)fflush(stdout);
                        if (ioctl(fd, FDIOCFORMAT_TRACK, &cmd) == 0) {
                                if (verify)
                                        errcnt += verify_track(fd, cyl, trk,
                                            &parms, trackbuf);
                        } else if (errno == EINVAL) {
                                (void)putchar('\n');
                                errx(1, "Formatting botch at <%d,%d>",
                                     cyl, trk);
                        } else if (errno == EIO) {
                                (void)printf("- IO ERROR\n");
                                errcnt++;
                        }
                }
        }
        (void)printf("\rFormatting %i tracks total complete.\n",
                parms.ncyl * parms.ntrk);
        if (errcnt)
                errx(1, "%d track formatting error%s",
                    errcnt, errcnt == 1 ? "" : "s");
        return 0;
}