root/usr.sbin/mmcformat/mmcformat.c
/* $NetBSD: mmcformat.c,v 1.10 2025/07/11 20:45:27 andvar Exp $ */

/*
 * Copyright (c) 2006, 2008 Reinoud Zandijk
 * All rights reserved.
 *
 * 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 AUTHOR ``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 AUTHOR 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.
 *
 */

#include <sys/cdefs.h>
__RCSID("$NetBSD: mmcformat.c,v 1.10 2025/07/11 20:45:27 andvar Exp $");

#include <sys/types.h>
#include <sys/time.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>

#include "uscsilib.h"


/* globals */
static struct uscsi_dev dev;
extern int scsilib_verbose;

/* #define DEBUG(a) {a;} */
#define DEBUG(a) ;


static uint64_t
getmtime(void)
{
        struct timeval tp;

        gettimeofday(&tp, NULL);
        return (uint64_t) 1000000 * tp.tv_sec + tp.tv_usec;
}


static void
print_eta(uint32_t progress, uint64_t now, uint64_t start_time)
{
        int hours, minutes, seconds;
        uint64_t tbusy, ttot_est, eta;

        if (progress == 0) {
                printf(" ETA --:--:--");
                return;
        }
        tbusy    = now - start_time;
        ttot_est = (tbusy * 0x10000) / progress;
        eta      = (ttot_est - tbusy) / 1000000;

        hours   = (int) (eta/3600);
        minutes = (int) (eta/60) % 60;
        seconds = (int)  eta % 60;
        printf(" ETA %02d:%02d:%02d", hours, minutes, seconds);
}


static void
uscsi_waitop(struct uscsi_dev *mydev)
{
        scsicmd cmd;
        struct uscsi_sense sense;
        uint64_t start_time;
        uint32_t progress;
        uint8_t buffer[256];
        int asc, ascq;
        int cnt = 0;

        bzero(cmd, SCSI_CMD_LEN);
        bzero(buffer, sizeof(buffer));

        /*
         * not be to impatient... give the drive some time to start or it
         * might break off
         */

        start_time = getmtime();
        sleep(10);

        progress = 0;
        while (progress < 0x10000) {
                /* we need a command that is NOT going to stop the formatting */
                bzero(cmd, SCSI_CMD_LEN);
                cmd[0] = 0;                     /* test unit ready */
                uscsi_command(SCSI_READCMD, mydev,
                        cmd, 6, buffer, 0, 10000, &sense);

                /*
                 * asc may be `not-ready' or `no-sense'. ascq for format in
                 * progress is 4 too
                 */
                asc  = sense.asc;
                ascq = sense.ascq;
                if (((asc == 0) && (ascq == 4)) || (asc == 4)) {
                        /* drive not ready : operation/format in progress */
                        if (sense.skey_valid) {
                                progress = sense.sense_key;
                        } else {
                                /* finished */
                                progress = 0x10000;
                        }
                }
                /* check if drive is ready again, ifso break out loop */
                if ((asc == 0) && (ascq == 0)) {
                        progress = 0x10000;
                }

                printf("%3d %% ", (100 * progress / 0x10000));
                printf("%c", "|/-\\" [cnt++ %4]);   /* twirl */

                /* print ETA */
                print_eta(progress, getmtime(), start_time);

                fflush(stdout);
                sleep(1);
                printf("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b");
                fflush(stdout);
        }
        printf("\n");

        return;
}


static char const *
print_mmc_profile(int profile)
{
        static char scrap[100];

        switch (profile) {
        case 0x00 : return "Unknown[0] profile";
        case 0x01 : return "Non removable disc";
        case 0x02 : return "Removable disc";
        case 0x03 : return "Magneto Optical with sector erase";
        case 0x04 : return "Magneto Optical write once";
        case 0x05 : return "Advance Storage Magneto Optical";
        case 0x08 : return "CD-ROM";
        case 0x09 : return "CD-R recordable";
        case 0x0a : return "CD-RW rewritable";
        case 0x10 : return "DVD-ROM";
        case 0x11 : return "DVD-R sequential";
        case 0x12 : return "DVD-RAM rewritable";
        case 0x13 : return "DVD-RW restricted overwrite";
        case 0x14 : return "DVD-RW sequential";
        case 0x1a : return "DVD+RW rewritable";
        case 0x1b : return "DVD+R recordable";
        case 0x20 : return "DDCD readonly";
        case 0x21 : return "DDCD-R recordable";
        case 0x22 : return "DDCD-RW rewritable";
        case 0x2b : return "DVD+R double layer";
        case 0x40 : return "BD-ROM";
        case 0x41 : return "BD-R Sequential Recording (SRM)";
        case 0x42 : return "BD-R Random Recording (RRM)";
        case 0x43 : return "BD-RE rewritable";
        }
        sprintf(scrap, "Reserved profile 0x%02x", profile);
        return scrap;
}


static int
uscsi_get_mmc_profile(struct uscsi_dev *mydev, int *mmc_profile)
{
        scsicmd  cmd;
        uint8_t  buf[32];
        int error;

        *mmc_profile = 0;

        bzero(cmd, SCSI_CMD_LEN);
        cmd[ 0] = 0x46;                         /* Get configuration */
        cmd[ 8] = 32;                           /* just a small buffer size */
        cmd[ 9] = 0;                            /* control */
        error = uscsi_command(SCSI_READCMD, mydev, cmd, 10, buf, 32, 30000, NULL);
        if (!error) {
                *mmc_profile = buf[7] | (buf[6] << 8);
        }

        return error;
}


static int
uscsi_set_packet_parameters(struct uscsi_dev *mydev, int blockingnr)
{
        scsicmd  cmd;
        int      val_len;
        uint8_t  res[10000], *pos;
        int      error;

        /* Set up CD/DVD recording parameters */
        DEBUG(printf("Setting device's recording parameters\n"));

        val_len = 0x32+2+8;
        bzero(res, val_len);

        pos = res + 8;

        bzero(cmd, SCSI_CMD_LEN);
        pos[ 0] = 0x05;         /* page code 5 : cd writing             */
        pos[ 1] = 0x32;         /* length in bytes                      */
        pos[ 2] = 0;            /* write type 0 : packet/incremental    */

        /* next session OK, data packet, rec. incr. fixed packets       */
        pos[ 3] = (3<<6) | 32 | 5;
        pos[ 4] = 10;           /* ISO mode 2; XA form 1                */
        pos[ 8] = 0x20;         /* CD-ROM XA disc or DDCD disc          */
        pos[10] = (blockingnr >> 24) & 0xff;    /* MSB packet size      */
        pos[11] = (blockingnr >> 16) & 0xff;
        pos[12] = (blockingnr >>  8) & 0xff;
        pos[13] = (blockingnr      ) & 0xff;    /* LSB packet size      */

        bzero(cmd, SCSI_CMD_LEN);
        cmd[0] = 0x55;                  /* MODE SELECT (10)             */
        cmd[1] = 16;                    /* PF format                    */
        cmd[7] = val_len >> 8;          /* length of blob               */
        cmd[8] = val_len & 0xff;
        cmd[9] = 0;                     /* control                      */

        error = uscsi_command(SCSI_WRITECMD, mydev,
                        cmd, 10, res, val_len, 30000, NULL);
        if (error) {
                perror("While WRITING parameter page 5");
                return error;
        }

        /* flag OK */
        return 0;
}


static int
get_format_capabilities(struct uscsi_dev *mydev, uint8_t *buf, uint32_t *len)
{
        scsicmd         cmd;
        int             list_length;
        int             trans_len;
        size_t          buf_len = 512;
        int             error;

        assert(*len >= buf_len);
        bzero(buf, buf_len);

        trans_len = 12;                         /* only fixed header first */
        bzero(cmd, SCSI_CMD_LEN);
        cmd[0] = 0x23;                          /* Read format capabilities */
        cmd[7] = trans_len >> 8;                /* MSB allocation length */
        cmd[8] = trans_len & 0xff;              /* LSB allocation length */
        cmd[9] = 0;                             /* control */
        error = uscsi_command(SCSI_READCMD, mydev,
                        cmd, 10, buf, trans_len, 30000, NULL);
        if (error) {
                fprintf(stderr, "While reading format capabilities : %s\n",
                        strerror(error));
                return error;
        }

        list_length = buf[ 3];

        if (list_length % 8) {
                printf( "\t\tWarning: violating SCSI spec,"
                        "capacity list length ought to be multiple of 8\n");
                printf("\t\tInterpreting as including header of 4 bytes\n");
                assert(list_length % 8 == 4);
                list_length -= 4;
        }

        /* read in full capacity list */
        trans_len = 12 + list_length;           /* complete structure */
        bzero(cmd, SCSI_CMD_LEN);
        cmd[0] = 0x23;                          /* Read format capabilities */
        cmd[7] = trans_len >> 8;                /* MSB allocation length */
        cmd[8] = trans_len & 0xff;              /* LSB allocation length */
        cmd[9] = 0;                             /* control */
        error = uscsi_command(SCSI_READCMD, mydev,
                        cmd, 10, buf, trans_len, 30000, NULL);
        if (error) {
                fprintf(stderr, "While reading format capabilities : %s\n",
                        strerror(error));
                return error;
        }

        *len = list_length;
        return 0;
}


static void
print_format(int format_tp, uint32_t num_blks, uint32_t param,
        int dscr_type, int verbose, int *supported)
{
        char const *format_str, *nblks_str, *param_str, *user_spec;

        format_str = nblks_str = param_str = "reserved";
        user_spec = "";
        *supported = 1;

        switch (format_tp) {
        case  0x00 :
                format_str = "full format capacity";
                nblks_str  = "sectors";
                param_str  = "block length in bytes";
                user_spec  = "'-F [-b blockingnr]'";
                break;
        case  0x01 :
                format_str = "spare area expansion";
                nblks_str  = "extension in blocks";
                param_str  = "block length in bytes";
                user_spec  = "'-S'";
                break;
        /* 0x02 - 0x03 reserved */
        case  0x04 :
                format_str = "variable length zone'd format";
                nblks_str  = "zone length";
                param_str  = "zone number";
                *supported = 0;
                break;
        case  0x05 :
                format_str = "fixed length zone'd format";
                nblks_str  = "zone length";
                param_str  = "last zone number";
                *supported = 0;
                break;
        /* 0x06 - 0x0f reserved */
        case  0x10 :
                format_str = "CD-RW/DVD-RW full packet format";
                nblks_str  = "addressable blocks";
                param_str  = "fixed packet size/ECC blocksize in sectors";
                user_spec  = "'-F -p [-b blockingnr]'";
                break;
        case  0x11 :
                format_str = "CD-RW/DVD-RW grow session";
                nblks_str  = "addressable blocks";
                param_str  = "fixed packet size/ECC blocksize in sectors";
                user_spec  = "'-G'";
                break;
        case  0x12 :
                format_str = "CD-RW/DVD-RW add session";
                nblks_str  = "addressable blocks";
                param_str  = "maximum fixed packet size/ECC blocksize "
                             "in sectors";
                *supported = 0;
                break;
        case  0x13 :
                format_str = "DVD-RW max growth of last complete session";
                nblks_str  = "addressable blocks";
                param_str  = "ECC blocksize in sectors";
                user_spec  = "'-G'";
                break;
        case  0x14 :
                format_str = "DVD-RW quick grow last session";
                nblks_str  = "addressable blocks";
                param_str  = "ECC blocksize in sectors";
                *supported = 0;
                break;
        case  0x15 :
                format_str = "DVD-RW quick full format";
                nblks_str  = "addressable blocks";
                param_str  = "ECC blocksize in sectors";
                *supported = 0;
                break;
        /* 0x16 - 0x23 reserved */
        case  0x24 :
                format_str = "background MRW format";
                nblks_str  = "Defect Management Area blocks";
                param_str  = "not used";
                user_spec  = "'[-R] [-s] [-w] -F -M [-b blockingnr]'";
                break;
        /* 0x25 reserved */
        case  0x26 :
                format_str = "background DVD+RW full format";
                nblks_str  = "sectors";
                param_str  = "not used";
                user_spec  = "'[-R] [-w] -F'";
                break;
        /* 0x27 - 0x2f reserved */
        case  0x30 :
                format_str = "BD-RE full format with spare area";
                nblks_str  = "blocks";
                param_str  = "total spare area size in clusters";
                user_spec  = "'[-s] -F'";
                break;
        case  0x31 :
                format_str = "BD-RE full format without spare area";
                nblks_str  = "blocks";
                param_str  = "block length in bytes";
                user_spec  = "'-F'";
                break;
        /* 0x32 - 0x3f reserved */
        default :
                break;
        }

        if (verbose) {
                printf("\n\tFormat type 0x%02x : %s\n", format_tp, format_str);

                switch (dscr_type) {
                case  1 :
                        printf( "\t\tUnformatted media,"
                                "maximum formatted capacity\n");
                        break;
                case  2 :
                        printf( "\t\tFormatted media,"
                                "current formatted capacity\n");
                        break;
                case  3 :
                        printf( "\t\tNo media present or incomplete session, "
                                "maximum formatted capacity\n");
                        break;
                default :
                        printf("\t\tUnspecified descriptor type\n");
                        break;
                }

                printf("\t\tNumber of blocks : %12d\t(%s)\n",
                        num_blks, nblks_str);
                printf("\t\tParameter        : %12d\t(%s)\n",
                        param, param_str);

                if (format_tp == 0x24) {
                        printf( "\t\tExpert select    : "
                                "'-X 0x%02x:0xffffff:0' or "
                                "'-X 0x%02x:0xffff0000:0'\n",
                                format_tp, format_tp);
                } else {
                        printf( "\t\tExpert select    : "
                                "'-X 0x%02x:%d:%d'\n",
                                format_tp, num_blks, param);
                }
                if (*supported) {
                        printf("\t\tmmc_format arg   : %s\n", user_spec);
                } else {
                        printf("\t\t** not supported **\n");
                }
        }
}


static void
process_format_caps(uint8_t *buf, int list_length, int verbose,
        uint8_t *allow, uint32_t *blks, uint32_t *params)
{
        uint32_t        num_blks, param;
        uint8_t        *fcd;
        int             dscr_type, format_tp;
        int             supported;

        bzero(allow,  255);
        bzero(blks,   255*4);
        bzero(params, 255*4);

        fcd = buf + 4;
        list_length -= 4;               /* strip header */

        if (verbose)
                printf("\tCurrent/max capacity followed by additional capacity,"
                        "reported length of %d bytes (8/entry)\n", list_length);

        while (list_length > 0) {
                num_blks    = fcd[ 3]        | (fcd[ 2] << 8) |
                             (fcd[ 1] << 16) | (fcd[ 0] << 24);
                dscr_type   = fcd[ 4] & 3;
                format_tp   = fcd[ 4] >> 2;
                param       = fcd[ 7] | (fcd[ 6] << 8) |  (fcd[ 5] << 16);

                print_format(format_tp, num_blks, param, dscr_type, verbose,
                        &supported);

                 allow[format_tp] = 1;  /* TODO = supported? */
                  blks[format_tp] = num_blks;
                params[format_tp] = param;

                fcd += 8;
                list_length-=8;
        }
}



/* format a CD-RW disc */
/* old style format 7 */
static int
uscsi_format_cdrw_mode7(struct uscsi_dev *mydev, uint32_t blocks)
{
        scsicmd cmd;
        struct uscsi_sense sense;
        uint8_t  buffer[16];
        int error;

        if (blocks % 32) {
                blocks -= blocks % 32;
        }

        bzero(cmd, SCSI_CMD_LEN);
        bzero(buffer, sizeof(buffer));

        cmd[0] = 0x04;                  /* format unit                     */
        cmd[1] = 0x17;                  /* parameter list format 7 follows */
        cmd[5] = 0;                     /* control                         */

        /* format list header */
        buffer[ 0] = 0;                 /* reserved                        */
        buffer[ 1] = 0x80 | 0x02;       /* Valid info, immediate return    */
        buffer[ 2] = 0;                 /* MSB format descriptor length    */
        buffer[ 3] = 8;                 /* LSB ...                         */

        /*
         * for CD-RW the initialisation pattern bit is reserved, but there IS
         * one
         */

        buffer[ 4] = 0;                 /* no header                       */
        buffer[ 5] = 0;                 /* default pattern                 */
        buffer[ 6] = 0;                 /* pattern length MSB              */
        buffer[ 7] = 0;                 /* pattern length LSB              */

        /* 8 bytes of format descriptor */
        /* (s)ession bit 1<<7, (g)row bit 1<<6  */
        /* SG   action  */
        /* 00   format disc with number of user data blocks     */
        /* 10   create new session with number of data blocks   */
        /* x1   grow session to be number of data blocks        */

        buffer[ 8] = 0x00;              /* session and grow bits (7 and 6)  */
        buffer[ 9] = 0;                 /* reserved */
        buffer[10] = 0;                 /* reserved */
        buffer[11] = 0;                 /* reserved */
        buffer[12] = (blocks >> 24) & 0xff;     /* blocks MSB   */
        buffer[13] = (blocks >> 16) & 0xff;
        buffer[14] = (blocks >>  8) & 0xff;
        buffer[15] = (blocks      ) & 0xff;     /* blocks LSB   */

        /* this will take a while .... */
        error = uscsi_command(SCSI_WRITECMD, mydev,
                        cmd, 6, buffer, sizeof(buffer), UINT_MAX, &sense);
        if (error)
                return error;

        uscsi_waitop(mydev);
        return 0;
}


static int
uscsi_format_disc(struct uscsi_dev *mydev, int immed, int format_type,
        uint32_t blocks, uint32_t param, int certification, int cmplist)
{
        scsicmd cmd;
        struct uscsi_sense sense;
        uint8_t  buffer[16], fmt_flags;
        int error;

        fmt_flags = 0x80;               /* valid info flag */
        if (immed)
                fmt_flags |= 2;
        if (certification == 0)
                fmt_flags |= 32;

        if (cmplist)
                cmplist = 8;

#if 0
        if (mmc_profile != 0x43) {
                /* certification specifier only valid for BD-RE */
                certification = 0;
        }
#endif

        bzero(cmd, SCSI_CMD_LEN);
        bzero(buffer, sizeof(buffer));

        cmd[0] = 0x04;                  /* format unit                     */
        cmd[1] = 0x11 | cmplist;        /* parameter list format 1 follows */
        cmd[5] = 0;                     /* control                         */

        /* format list header */
        buffer[ 0] = 0;                 /* reserved                        */
        buffer[ 1] = 0x80 | fmt_flags;  /* Valid info, flags follow        */
        buffer[ 2] = 0;                 /* MSB format descriptor length    */
        buffer[ 3] = 8;                 /* LSB ...                         */

        /* 8 bytes of format descriptor */
        buffer[ 4] = (blocks >> 24) & 0xff;     /* blocks MSB        */
        buffer[ 5] = (blocks >> 16) & 0xff;
        buffer[ 6] = (blocks >>  8) & 0xff;
        buffer[ 7] = (blocks      ) & 0xff;     /* blocks LSB        */
        buffer[ 8] = (format_type << 2) | certification;
        buffer[ 9] = (param  >> 16) & 0xff;     /* parameter MSB     */
        buffer[10] = (param  >>  8) & 0xff;     /*      packet size  */
        buffer[11] = (param       ) & 0xff;     /* parameter LSB     */

        /* this will take a while .... */
        error = uscsi_command(SCSI_WRITECMD, mydev,
                        cmd, 6, buffer, 12, UINT_MAX, &sense);
        if (error)
                return error;

        if (immed)
                uscsi_waitop(mydev);

        return 0;
}


static int
uscsi_blank_disc(struct uscsi_dev *mydev)
{
        scsicmd cmd;
        int error;

        /* XXX check if the device can blank! */


        /* blank disc */
        bzero(cmd, SCSI_CMD_LEN);
        cmd[ 0] = 0xA1;                 /* blank                        */
        cmd[ 1] = 16;                   /* Immediate, blank complete    */
        cmd[11] = 0;                    /* control                      */

        /* this will take a while .... */
        error = uscsi_command(SCSI_WRITECMD, mydev,
                        cmd, 12, NULL, 0, UINT_MAX, NULL);
        if (error)
                return error;

        uscsi_waitop(mydev);
        return 0;
}


static int
usage(char *program)
{
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s [options] devicename\n", program);
        fprintf(stderr,
        "-B             blank cd-rw disc before formatting\n"
        "-F             format cd-rw disc\n"
        "-O             CD-RW formatting 'old-style' for old CD-RW drives\n"
        "-M             select MRW format\n"
        "-R             restart MRW & DVD+RW format\n"
        "-G             grow last CD-RW/DVD-RW session\n"
        "-S             grow spare space DVD-RAM/BD-RE\n"
        "-s             format DVD+MRW/BD-RE with extra spare space\n"
        "-w             wait until completion of background format\n"
        "-p             explicitly set packet format\n"
        "-c num         media certification for DVD-RAM/BD-RE : "
                        "0 no, 1 full, 2 quick\n"
        "-r             recompile defect list for DVD-RAM (cmplist)\n"
        "-h -H -I       help/inquiry formats\n"
        "-X format      expert format selector form 'fmt:blks:param' with -c\n"
        "-b blockingnr  in sectors (for CD-RW)\n"
        "-D             verbose SCSI command errors\n"
        );
        return 1;
}


int
main(int argc, char *argv[])
{
        struct uscsi_addr saddr;
        uint32_t blks[256], params[256];
        uint32_t format_type, format_blks, format_param, blockingnr;
        uint8_t  allow[256];
        uint8_t caps[512];
        uint32_t caps_len = sizeof(caps);
        char *progname;
        int blank, format, mrw, background;
        int inquiry, spare, oldtimer;
        int expert;
        int restart_format, grow_session, grow_spare, packet_wr;
        int mmc_profile, flag, error, display_usage;
        int certification, cmplist;
        int wait_until_finished;
        progname = strdup(argv[0]);
        if (argc == 1) {
                return usage(progname);
        }

        blank               = 0;
        format              = 0;
        mrw                 = 0;
        restart_format      = 0;
        grow_session        = 0;
        grow_spare          = 0;
        wait_until_finished = 0;
        packet_wr           = 0;
        certification       = 1;
        cmplist             = 0;
        inquiry             = 0;
        spare               = 0;
        inquiry             = 0;
        oldtimer            = 0;
        expert              = 0;
        display_usage       = 0;
        blockingnr          = 32;
        uscsilib_verbose    = 0;
        while ((flag = getopt(argc, argv, "BFMRGSwpsc:rhHIX:Ob:D")) != -1) {
                switch (flag) {
                        case 'B' :
                                blank = 1;
                                break;
                        case 'F' :
                                format = 1;
                                break;
                        case 'M' :
                                mrw = 1;
                                break;
                        case 'R' :
                                restart_format = 1;
                                break;
                        case 'G' :
                                grow_session = 1;
                                break;
                        case 'S' :
                                grow_spare = 1;
                                break;
                        case 'w' :
                                wait_until_finished = 1;
                                break;
                        case 'p' :
                                packet_wr = 1;
                                break;
                        case 's' :
                                spare = 1;
                                break;
                        case 'c' :
                                certification = atoi(optarg);
                                break;
                        case 'r' :
                                cmplist = 1;
                                break;
                        case 'h' :
                        case 'H' :
                                display_usage = 1;
                                break;
                        case 'I' :
                                inquiry = 1;
                                break;
                        case 'X' :
                                /* TODO parse expert mode string */
                                printf("-X not implemented yet\n");
                                expert = 1;
                                exit(1);
                                break;
                        case 'O' :
                                /* oldtimer CD-RW format */
                                oldtimer = 1;
                                format   = 1;
                                break;
                        case 'b' :
                                blockingnr = atoi(optarg);
                                break;
                        case 'D' :
                                uscsilib_verbose = 1;
                                break;
                        default :
                                return usage(progname);
                }
        }
        argv += optind;
        argc -= optind;

        if (!blank && !format && !grow_session && !grow_spare &&
            !expert && !inquiry && !display_usage) {
                fprintf(stderr, "%s : at least one of -B, -F, -G, -h, -H -S, "
                                "-X or -I needs to be specified\n\n", progname);
                return usage(progname);
        }

        if (format + grow_session + grow_spare + expert > 1) {
                fprintf(stderr, "%s : at most one of -F, -G, -S or -X "
                                "needs to be specified\n\n", progname);
                return usage(progname);
        }

        if (argc != 1) return usage(progname);

        /* Open the device */
        dev.dev_name = strdup(*argv);
        printf("Opening device %s\n", dev.dev_name);
        error = uscsi_open(&dev);
        if (error) {
                fprintf(stderr, "Device failed to open : %s\n",
                        strerror(error));
                exit(1);
        }

        error = uscsi_check_for_scsi(&dev);
        if (error) {
                fprintf(stderr, "sorry, not a SCSI/ATAPI device : %s\n",
                        strerror(error));
                exit(1);
        }

        error = uscsi_identify(&dev, &saddr);
        if (error) {
                fprintf(stderr, "SCSI/ATAPI identify returned : %s\n",
                        strerror(error));
                exit(1);
        }

        printf("\nDevice identifies itself as : ");

        if (saddr.type == USCSI_TYPE_SCSI) {
                printf("SCSI   busnum = %d, target = %d, lun = %d\n",
                        saddr.addr.scsi.scbus, saddr.addr.scsi.target,
                        saddr.addr.scsi.lun);
        } else {
                printf("ATAPI  busnum = %d, drive = %d\n",
                        saddr.addr.atapi.atbus, saddr.addr.atapi.drive);
        }

        printf("\n");

        /* get MMC profile */
        error = uscsi_get_mmc_profile(&dev, &mmc_profile);
        if (error) {
                fprintf(stderr,
                        "Can't get the disc's MMC profile because of :"
                        " %s\n", strerror(error));
                fprintf(stderr, "aborting\n");
                uscsi_close(&dev);
                return 1;
        }

        /* blank disc section */
        if (blank) {
                printf("\nBlanking disc.... "); fflush(stdout);
                error = uscsi_blank_disc(&dev);

                if (error) {
                        printf("fail\n"); fflush(stdout);
                        fprintf(stderr,
                                "Blanking failed because of : %s\n",
                                strerror(error));
                        uscsi_close(&dev);

                        return 1;
                } else {
                        printf("success!\n\n");
                }
        }

        /* re-get MMC profile */
        error = uscsi_get_mmc_profile(&dev, &mmc_profile);
        if (error) {
                fprintf(stderr,
                        "Can't get the disc's MMC profile because of : %s\n",
                        strerror(error));
                fprintf(stderr, "aborting\n");
                uscsi_close(&dev);
                return 1;
        }

        error = get_format_capabilities(&dev, caps, &caps_len);
        if (error)
                exit(1);

        process_format_caps(caps, caps_len, inquiry, allow, blks, params);

        format_type = 0;
        /* expert format section */
        if (expert) {
        }

        if (!format && !grow_spare && !grow_session) {
                /* we're done */
                if (display_usage)
                        usage(progname);
                uscsi_close(&dev);
                exit(0);
        }

        /* normal format section */
        if (format) {
                /* get current mmc profile of disc */

                if (oldtimer && mmc_profile != 0x0a) {
                        printf("Oldtimer flag only defined for CD-RW; "
                                "ignored\n");
                }

                switch (mmc_profile) {
                case 0x12 :     /* DVD-RAM      */
                        format_type = 0x00;
                        break;
                case 0x0a :     /* CD-RW        */
                        format_type = mrw ? 0x24 : 0x10;
                        packet_wr   = 1;
                        break;
                case 0x13 :     /* DVD-RW restricted overwrite */
                case 0x14 :     /* DVD-RW sequential            */
                        format_type = 0x10;
                        /*
                         * Some drives suddenly stop supporting this format
                         * type when packet_wr = 1
                         */
                        packet_wr   = 0;
                        break;
                case 0x1a :     /* DVD+RW       */
                        format_type = mrw ? 0x24 : 0x26;
                        break;
                case 0x43 :     /* BD-RE        */
                        format_type = spare ? 0x30 : 0x31;
                        break;
                default :
                        fprintf(stderr, "Can't format discs of type %s\n",
                                print_mmc_profile(mmc_profile));
                        uscsi_close(&dev);
                        exit(1);
                }
        }

        if (grow_spare) {
                switch (mmc_profile) {
                case 0x12 :     /* DVD-RAM */
                case 0x43 :     /* BD-RE   */
                        format_type = 0x01;
                        break;
                default :
                        fprintf(stderr,
                                "Can't grow spare area for discs of type %s\n",
                                print_mmc_profile(mmc_profile));
                        uscsi_close(&dev);
                        exit(1);
                }
        }

        if (grow_session) {
                switch (mmc_profile) {
                case 0x0a :     /* CD-RW */
                        format_type = 0x11;
                        break;
                case 0x13 :     /* DVD-RW restricted overwrite */
                case 0x14 :     /* DVD-RW sequential ? */
                        format_type = 0x13;
                        break;
                default :
                        uscsi_close(&dev);
                        fprintf(stderr,
                                "Can't grow session for discs of type %s\n",
                                print_mmc_profile(mmc_profile));
                        exit(1);
                }
        }

        /* check if format type is allowed */
        format_blks  = blks[format_type];
        format_param = params[format_type];
        if (!allow[format_type]) {
                if (!inquiry)
                        process_format_caps(caps, caps_len, 1, allow,
                                blks, params);

                printf("\n");
                fflush(stdout);
                fprintf(stderr,
                        "Drive indicates it can't format with deduced format "
                        "type 0x%02x\n", format_type);
                uscsi_close(&dev);
                exit(1);
        }

        if (restart_format && !((mmc_profile == 0x1a) || (format_type == 0x24)))
        {
                fprintf(stderr,
                        "Format restarting only for MRW formats or DVD+RW "
                        "formats\n");
                uscsi_close(&dev);
                exit(1);
        }

        if (restart_format && !wait_until_finished) {
                printf( "Warning : format restarting without waiting for it be "
                        "finished is prolly not handy\n");
        }

        /* explicitly select packet write just in case */
        if (packet_wr) {
                printf("Explicitly setting packet type and blocking number\n");
                error = uscsi_set_packet_parameters(&dev, blockingnr);
                if (error) {
                        fprintf(stderr,
                                "Can't set packet writing and blocking number: "
                                "%s\n", strerror(error));
                        uscsi_close(&dev);
                        exit(1);
                }
        }

        /* determine if formatting is done in the background */
        background = 0;
        if (format_type == 0x24) background = 1;
        if (format_type == 0x26) background = 1;

        /* special case format type 0x24 : MRW */
        if (format_type == 0x24) {
                format_blks  = spare ? 0xffff0000 : 0xffffffff;
                format_param = restart_format;
        }
        /* special case format type 0x26 : DVD+RW */
        if (format_type == 0x26) {
                format_param = restart_format;
        }

        /* verbose to the user */
        DEBUG(
                printf("Actual format selected: "
                        "format_type 0x%02x, blks %d, param %d, "
                        "certification %d, cmplist %d\n",
                        format_type, format_blks, format_param,
                        certification, cmplist);
        );
        printf("\nFormatting.... "); fflush(stdout);

        /* formatting time! */
        if (oldtimer) {
                error = uscsi_format_cdrw_mode7(&dev, format_blks);
                background = 0;
        } else {
                error = uscsi_format_disc(&dev, !background, format_type,
                                format_blks, format_param, certification,
                                cmplist);
        }

        /* what now? */
        if (error) {
                printf("fail\n"); fflush(stdout);
                fprintf(stderr, "Formatting failed because of : %s\n",
                        strerror(error));
        } else {
                if (background) {
                        printf("background formatting in progress\n");
                        if (wait_until_finished) {
                                printf("Waiting for completion ... ");
                                uscsi_waitop(&dev);
                        }
                        /* explicitly do NOT close disc ... (for now) */
                        return 0;
                } else {
                        printf("success!\n\n");
                }
        }

        /* finish up */
        uscsi_close(&dev);

        return error;
}