/* * dasdfmt - Format DASD ECKD devices for use by Linux * * Copyright IBM Corp. 1999, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #include #include #include #include #include "lib/dasd_sys.h" #include "lib/util_opt.h" #include "lib/util_prg.h" #include "lib/util_proc.h" #include "lib/vtoc.h" #include "lib/zt_common.h" #include "dasdfmt.h" #define BUSIDSIZE 8 #define SEC_PER_DAY (60 * 60 * 24) #define SEC_PER_HOUR (60 * 60) static int filedes; static int disk_disabled; static format_data_t format_params; static format_mode_t mode; static char *prog_name; static volatile sig_atomic_t program_interrupt_in_progress; static int reqsize; static const struct util_prg prg = { .desc = "Use dasdfmt to format a DASD ECKD device for use by Linux.\n" "DEVICE is the node of the device (e.g. '/dev/dasda').", .args = "DEVICE", .copyright_vec = { { .owner = "IBM Corp.", .pub_first = 1999, .pub_last = 2017, }, UTIL_PRG_COPYRIGHT_END } }; /* Defines for options with no short command */ #define OPT_CHECK 128 #define OPT_NOZERO 129 static struct util_opt opt_vec[] = { UTIL_OPT_SECTION("FORMAT ACTIONS"), { .option = { "mode", required_argument, NULL, 'M' }, .argument = "MODE", .desc = "Specify scope of operation using MODE:\n" " full: Full device (default)\n" " quick: Only the first two tracks\n" " expand: Unformatted tracks at device end", }, { .option = { "check", no_argument, NULL, OPT_CHECK }, .desc = "Perform complete format check on device", .flags = UTIL_OPT_FLAG_NOSHORT, }, UTIL_OPT_SECTION("FORMAT OPTIONS"), { .option = { "blocksize", required_argument, NULL, 'b' }, .argument = "SIZE", .desc = "Format blocks to SIZE bytes (default 4096)", }, { .option = { "disk_layout", required_argument, NULL, 'd' }, .argument = "LAYOUT", .desc = "Specify the disk layout:\n" " cdl: Compatible Disk Layout (default)\n" " ldl: Linux Disk Layout", }, { .option = { "keep_volser", no_argument, NULL, 'k' }, .desc = "Do not change the current volume serial", }, { .option = { "label", required_argument, NULL, 'l' }, .argument = "VOLSER", .desc = "Specify volume serial number", }, { .option = { "no_label", no_argument, NULL, 'L' }, .desc = "Don't write a disk label", }, { .option = { "requestsize", required_argument, NULL, 'r' }, .argument = "NUM", .desc = "Process NUM cylinders in one formatting step", }, { .option = { "norecordzero", no_argument, NULL, OPT_NOZERO }, .desc = "Prevent storage server from modifying record 0", .flags = UTIL_OPT_FLAG_NOSHORT, }, { .option = { NULL, no_argument, NULL, 'y' }, .desc = "Start formatting without further user-confirmation", .flags = UTIL_OPT_FLAG_NOLONG, }, UTIL_OPT_SECTION("DISPLAY PROGRESS"), { .option = { "hashmarks", required_argument, NULL, 'm' }, .argument = "NUM", .desc = "Show a hashmark every NUM cylinders", }, { .option = { "progressbar", no_argument, NULL, 'p' }, .desc = "Show a progressbar", }, { .option = { "percentage", no_argument, NULL, 'P' }, .desc = "Show progress in percent", }, UTIL_OPT_SECTION("MISC"), { .option = { "check_host_count", no_argument, NULL, 'C' }, .desc = "Check if device is in use by other hosts", }, { .option = { "force", no_argument, NULL, 'F' }, .desc = "Format without performing sanity checking", }, { .option = { "test", no_argument, NULL, 't' }, .desc = "Run in dry-run mode without modifying the DASD", }, { .option = { NULL, no_argument, NULL, 'v' }, .desc = "Print verbose messages when executing", .flags = UTIL_OPT_FLAG_NOLONG, }, UTIL_OPT_HELP, { .option = { "version", no_argument, NULL, 'V' }, .desc = "Print version information, then exit", }, UTIL_OPT_END }; /* * Helper function to calculate the days, hours, minutes, and seconds * for a given timestamp in seconds */ static void calc_time(time_t time, int *d, int *h, int *m, int *s) { *d = time / SEC_PER_DAY; time %= SEC_PER_DAY; *h = time / SEC_PER_HOUR; time %= SEC_PER_HOUR; *m = time / 60; *s = time % 60; } /* * This function calculates and prints the estimated time of accomplishment. */ static void print_eta(int p_new, int started) { static struct timeval start; struct timeval now; time_t time_elapsed; time_t time_end; int d, h, m, s; static int p_init; int p; if (!started) { gettimeofday(&start, NULL); p_init = p_new; } gettimeofday(&now, NULL); time_elapsed = now.tv_sec - start.tv_sec; /* * We might start somewhere in the middle with an initial percentage * value of i.e. 60%. Therefore we need to calculate the relative * percentage of p_new within that remaining range (100 - 60) in order * to correctly estimate the remaining time. */ if (p_init == 100) p = p_init; else p = 100 * (p_new - p_init) / (100 - p_init); if (p == 0) time_end = time_elapsed; else time_end = time_elapsed * (100 - p) / p; /* Calculate days, hours, minutes, and seconds */ calc_time(time_end, &d, &h, &m, &s); if (p_new == 100) calc_time(time_elapsed, &d, &h, &m, &s); /* Avoid printing leading zeros */ if (d > 0) printf(" [%dd %dh %dm %ds%-4s", d, h, m, s, "]"); else if (h > 0) printf(" [%dh %dm %ds%-7s", h, m, s, "]"); else if (m > 0) printf(" [%dm %ds%-6s", m, s, "]"); else if (s > 0 || p > 50) printf(" [%ds%-5s", s, "]"); else printf(" [--%-1s", "]"); } /* * Draw the progress indicator depending on what command line argument is set. * This can either be a progressbar, hashmarks, or percentage. */ static void draw_progress(dasdfmt_info_t *info, int cyl, unsigned int cylinders, int aborted) { static int hashcount; static int started; static int p_old; int p_new = 0; int barlength; int i; if (info->print_progressbar) { printf("cyl %7d of %7d |", cyl, cylinders); p_new = cyl * 100 / cylinders; if (p_new != p_old || !started || aborted) { /* percent value has changed */ p_old = p_new; barlength = cyl * 33 / cylinders; for (i = 1; i <= barlength; i++) printf("#"); for (i = barlength + 1; i <= 33; i++) printf("-"); printf("|%3d%%", p_new); if (aborted) p_new = 100; print_eta(p_new, started); started = 1; } printf("\r"); fflush(stdout); } if (info->print_hashmarks && (cyl / info->hashstep - hashcount) != 0) { printf("#"); fflush(stdout); hashcount++; } if (info->print_percentage) { printf("cyl %7d of %7d |%3d%%\n", cyl, cylinders, cyl * 100 / cylinders); fflush(stdout); } } static int reread_partition_table(void) { int i = 0; int rc = -1; /* If the BLKRRPART ioctl fails, it is most likely due to the device just beeing in use by udev. So it is worthwhile to retry the ioctl after a second as it is likely to succeed. */ while (rc && (i < 5)) { ++i; rc = ioctl(filedes, BLKRRPART, NULL); if (rc) sleep(1); } return rc; } /* * Helper function for recs_per_track. */ static inline unsigned int ceil_quot(unsigned int d1, unsigned int d2) { return (d1 + (d2 - 1)) / d2; } /* * Calculate records per track depending on the device characteristics. */ static unsigned int recs_per_track(struct dasd_eckd_characteristics *rdc, unsigned int kl, unsigned int dl) { int dn, kn; switch (rdc->dev_type) { case 0x3380: if (kl) return 1499 / (15 + 7 + ceil_quot(kl + 12, 32) + ceil_quot(dl + 12, 32)); else return 1499 / (15 + ceil_quot(dl + 12, 32)); case 0x3390: dn = ceil_quot(dl + 6, 232) + 1; if (kl) { kn = ceil_quot(kl + 6, 232) + 1; return 1729 / (10 + 9 + ceil_quot(kl + 6 * kn, 34) + 9 + ceil_quot(dl + 6 * dn, 34)); } else return 1729 / (10 + 9 + ceil_quot(dl + 6 * dn, 34)); case 0x9345: dn = ceil_quot(dl + 6, 232) + 1; if (kl) { kn = ceil_quot(kl + 6, 232) + 1; return 1420 / (18 + 7 + ceil_quot(kl + 6 * kn, 34) + ceil_quot(dl + 6 * dn, 34)); } else return 1420 / (18 + 7 + ceil_quot(dl + 6 * dn, 34)); } return 0; } /* * Evaluate errors recognized by format checks and print appropriate error * messages depending on the content of cdata. */ static void evaluate_format_error(dasdfmt_info_t *info, format_check_t *cdata, unsigned int heads) { struct dasd_eckd_characteristics *rdc; /* Special blocksize values of the first 3 records of trk 0 of cyl 0 */ const int blksizes_trk0[] = { 24, 144, 80 }; /* Special blocksize value of trk 1 cyl 0 */ const int blksize_trk1 = 96; unsigned int cyl; unsigned int head; unsigned int rpt; unsigned int kl = 0; int blksize = cdata->expect.blksize; if (info->print_progressbar || info->print_hashmarks) printf("\n"); /* * If mode is not QUICK and a device format couldn't be determined, the * device is considered not formatted. * Also, reading record zero will never happen. If the record in error * is 0 nonetheless, the device is not formatted at all as well! */ if ((info->dasd_info.format == DASD_FORMAT_NONE && mode != QUICK) || cdata->rec == 0) { ERRMSG("WARNING: The specified device is not " "formatted at all.\n"); return; } cyl = cdata->unit / heads; head = cyl >> 16; head <<= 4; head |= cdata->unit % heads; /* * Set special expected values for the first 3 records of trk 0 of cyl 0 * and trk 1 of cyl 0 when checking a CDL formatted device. */ if ((cdata->expect.intensity & DASD_FMT_INT_COMPAT) && cyl == 0 && head == 0 && cdata->rec < 4) { kl = 4; blksize = blksizes_trk0[cdata->rec - 1]; } if ((cdata->expect.intensity & DASD_FMT_INT_COMPAT) && cyl == 0 && head == 1) { kl = 44; blksize = blksize_trk1; } rdc = (struct dasd_eckd_characteristics *) &info->dasd_info.characteristics; rpt = recs_per_track(rdc, kl, cdata->expect.blksize); ERRMSG("WARNING: The specified device is not formatted as expected.\n"); switch (cdata->result) { case DASD_FMT_ERR_TOO_FEW_RECORDS: ERRMSG("Too few records (found %d, expected %d) " "at cyl: %d trk: %d rec: %d.\n", cdata->num_records, rpt, cyl, head, cdata->rec); break; case DASD_FMT_ERR_TOO_MANY_RECORDS: ERRMSG("Too many records (found %d, expected %d) " "at cyl: %d trk: %d rec: %d.\n", cdata->num_records, rpt, cyl, head, cdata->rec); break; case DASD_FMT_ERR_BLKSIZE: ERRMSG("Invalid blocksize (found %d, expected %d) " "at cyl: %d trk: %d rec: %d.\n", cdata->blksize, blksize, cyl, head, cdata->rec); break; case DASD_FMT_ERR_RECORD_ID: ERRMSG("Invalid record ID at cyl: %d trk: %d rec: %d.\n", cyl, head, cdata->rec); break; case DASD_FMT_ERR_KEY_LENGTH: ERRMSG("Invalid key length (found %d, expected %d) " "at cyl: %d trk: %d rec: %d.\n", cdata->key_length, kl, cyl, head, cdata->rec); break; } } /* * signal handler: * enables the disk again in case of SIGTERM, SIGINT and SIGQUIT */ static void program_interrupt_signal(int sig) { int rc; if (program_interrupt_in_progress) raise(sig); program_interrupt_in_progress = 1; if (disk_disabled) { printf("Re-accessing the device...\n"); rc = ioctl(filedes, BIODASDENABLE, &format_params); if (rc) ERRMSG_EXIT(EXIT_FAILURE, "%s: (signal handler) IOCTL " "BIODASDENABLE failed (%s)\n", prog_name, strerror(errno)); } printf("Rereading the partition table...\n"); rc = reread_partition_table(); if (rc) { ERRMSG("%s: (signal handler) Re-reading partition table " "failed. (%s)\n", prog_name, strerror(errno)); } else { printf("Exiting...\n"); } rc = close(filedes); if (rc) ERRMSG("%s: (signal handler) Unable to close device (%s)\n", prog_name, strerror(errno)); signal(sig, SIG_DFL); raise(sig); } /* * check given device name for blanks and some special characters */ static void get_device_name(dasdfmt_info_t *info, char *name, int argc, char *argv[]) { struct util_proc_dev_entry dev_entry; struct stat dev_stat; if (info->device_id + 1 < argc) ERRMSG_EXIT(EXIT_MISUSE, "%s: More than one device specified!\n", prog_name); if (info->device_id >= argc) ERRMSG_EXIT(EXIT_MISUSE, "%s: No device specified!\n", prog_name); if (info->device_id < argc) { strcpy(info->devname, argv[info->device_id]); } else { if ((strchr(name, ' ') != NULL) || (strchr(name, '#') != NULL) || (strchr(name, '[') != NULL) || (strchr(name, ']') != NULL) || (strchr(name, '!') != NULL) || (strchr(name, '>') != NULL) || (strchr(name, '(') != NULL) || (strchr(name, '<') != NULL) || (strchr(name, ')') != NULL) || (strchr(name, ':') != NULL) || (strchr(name, '&') != NULL) || (strchr(name, ';') != NULL)) ERRMSG_EXIT(EXIT_MISUSE, "%s: Your filename contains " "blanks or special characters!\n", prog_name); strncpy(info->devname, name, PATH_MAX - 1); info->devname[PATH_MAX - 1] = '\0'; } if (stat(info->devname, &dev_stat) != 0) ERRMSG_EXIT(EXIT_MISUSE, "%s: Could not get information for " "device node %s: %s\n", prog_name, info->devname, strerror(errno)); if (minor(dev_stat.st_rdev) & PARTN_MASK) { ERRMSG_EXIT(EXIT_MISUSE, "%s: Unable to format partition %s. " "Please specify a device.\n", prog_name, info->devname); } if (util_proc_dev_get_entry(dev_stat.st_rdev, 1, &dev_entry) == 0) { if (strncmp(dev_entry.name, "dasd", 4) != 0) ERRMSG_EXIT(EXIT_MISUSE, "%s: Unsupported device type '%s'.\n", prog_name, dev_entry.name); } else { printf("%s WARNING: Unable to get driver name for device node %s", prog_name, info->devname); } } /* * Retrieve DASD information */ static void get_device_info(dasdfmt_info_t *info) { dasd_information2_t dasd_info; if (ioctl(filedes, BIODASDINFO2, &dasd_info)) ERRMSG_EXIT(EXIT_FAILURE, "%s: the ioctl call to retrieve " "device information failed (%s).\n", prog_name, strerror(errno)); info->dasd_info = dasd_info; } /* * Retrieve blocksize of device */ static void get_blocksize(unsigned int *blksize) { if (ioctl(filedes, BLKSSZGET, blksize) != 0) ERRMSG_EXIT(EXIT_FAILURE, "%s: the ioctl to get the blocksize " "of the device failed (%s).\n", prog_name, strerror(errno)); } /* * Check whether a specified blocksize matches the blocksize of the device */ static void check_blocksize(dasdfmt_info_t *info, unsigned int blksize) { unsigned int dev_blksize; if (!info->blksize_specified || info->dasd_info.format == DASD_FORMAT_NONE) return; get_blocksize(&dev_blksize); if (dev_blksize != blksize) { ERRMSG_EXIT(EXIT_FAILURE, "WARNING: Device is formatted with a " "different blocksize (%d).\nUse --mode=full to " "perform a clean format.\n", dev_blksize); } } /* * Check whether a specified layout matches the layout * a device is formatted with. */ static void check_layout(dasdfmt_info_t *info, unsigned int intensity) { char layout[4]; if (!info->layout_specified || info->dasd_info.format == DASD_FORMAT_NONE) return; if ((intensity & DASD_FMT_INT_COMPAT) && info->dasd_info.format == DASD_FORMAT_CDL) return; if (!(intensity & DASD_FMT_INT_COMPAT) && info->dasd_info.format == DASD_FORMAT_LDL) return; if (info->dasd_info.format == DASD_FORMAT_CDL) sprintf(layout, "CDL"); if (info->dasd_info.format == DASD_FORMAT_LDL) sprintf(layout, "LDL"); ERRMSG_EXIT(EXIT_FAILURE, "WARNING: Device is formatted with a " "different layout (%s).\n", layout); } /* * check for disk type and set some variables (e.g. usage count) */ static void check_disk(dasdfmt_info_t *info) { int ro, errno_save; if (ioctl(filedes, BLKROGET, &ro) != 0) { errno_save = errno; close(filedes); ERRMSG_EXIT(EXIT_FAILURE, "%s: the ioctl call to retrieve read/write " "status information failed (%s)\n", prog_name, strerror(errno_save)); } if (ro) ERRMSG_EXIT(EXIT_FAILURE, "Disk is read only!\n"); if (!info->force) if (info->dasd_info.open_count > 1) ERRMSG_EXIT(EXIT_BUSY, "Disk in use!\n"); if (strncmp(info->dasd_info.type, "ECKD", 4) != 0) { ERRMSG_EXIT(EXIT_FAILURE, "%s: Unsupported disk type\n%s is not an " "ECKD disk!\n", prog_name, info->devname); } if (dasd_sys_raw_track_access(info->devname)) { ERRMSG_EXIT(EXIT_FAILURE, "%s: Device '%s' is in raw-track access mode\n", prog_name, info->devname); } } /* * check the volume serial for special * characters and move blanks to the end */ static int check_volser(char *s, int devno) { int i, j; for (i = 0; i < 6; i++) { if ((s[i] < 0x20) || (s[i] > 0x7a) || ((s[i] >= 0x21) && (s[i] <= 0x22)) || /* !" */ ((s[i] >= 0x26) && (s[i] <= 0x2f)) || /* &'()*+,-./ */ ((s[i] >= 0x3a) && (s[i] <= 0x3f)) || /* :;<=>? */ ((s[i] >= 0x5b) && (s[i] <= 0x60))) /* \]^_` */ s[i] = ' '; s[i] = toupper(s[i]); } s[6] = 0x00; for (i = 0; i < 6; i++) { if (s[i] == ' ') for (j = i; j < 6; j++) if (s[j] != ' ') { s[i] = s[j]; s[j] = ' '; break; } } if (s[0] == ' ') { printf("Usage error, switching to default.\n"); sprintf(s, "0X%04x", devno); for (i = 0; i < 6; i++) s[i] = toupper(s[i]); return -1; } return 0; } /* * do some blocksize checks */ static int check_param(char *s, size_t buffsize, format_data_t *data) { int tmp = data->blksize; if ((tmp < 512) || (tmp > 4096)) { strncpy(s, "Blocksize must be one of the following positive " "integers:\n512, 1024, 2048, 4096.", buffsize); if (buffsize > 0) s[buffsize - 1] = '\0'; return -1; } while (tmp > 0) { if ((tmp % 2) && (tmp != 1)) { strncpy(s, "Blocksize must be a power of 2.", buffsize); if (buffsize > 0) s[buffsize - 1] = '\0'; return -1; } tmp /= 2; } return 0; } /* * Retrieve disk information and set cylinders and heads accordingly. */ static void set_geo(dasdfmt_info_t *info, unsigned int *cylinders, unsigned int *heads) { struct dasd_eckd_characteristics *characteristics; if (info->verbosity > 0) printf("Retrieving disk geometry...\n"); characteristics = (struct dasd_eckd_characteristics *) &info->dasd_info.characteristics; if (characteristics->no_cyl == LV_COMPAT_CYL && characteristics->long_no_cyl) *cylinders = characteristics->long_no_cyl; else *cylinders = characteristics->no_cyl; *heads = characteristics->trk_per_cyl; } /* * Set VTOC label information */ static void set_label(dasdfmt_info_t *info, volume_label_t *vlabel, format_data_t *p, unsigned int cylinders) { char inp_buffer[5]; if (info->writenolabel) { if (cylinders > LV_COMPAT_CYL && !info->withoutprompt) { printf("\n--->> ATTENTION! <<---\n"); printf("You specified to write no labels to a" " volume with more then %u cylinders.\n" "Cylinders above this limit will not be" " accessible as a linux partition!\n" "Type \"yes\" to continue, no will leave" " the disk untouched: ", LV_COMPAT_CYL); if (fgets(inp_buffer, sizeof(inp_buffer), stdin) == NULL) return; if (strcasecmp(inp_buffer, "yes") && strcasecmp(inp_buffer, "yes\n")) { printf("Omitting ioctl call (disk will " "NOT be formatted).\n"); return; } } } else { if (!info->labelspec && !info->keep_volser) { char buf[7]; sprintf(buf, "0X%04x", info->dasd_info.devno); check_volser(buf, info->dasd_info.devno); vtoc_volume_label_set_volser(vlabel, buf); } if (p->intensity & DASD_FMT_INT_COMPAT) { info->cdl_format = 1; vtoc_volume_label_set_label(vlabel, "VOL1"); vtoc_volume_label_set_key(vlabel, "VOL1"); vtoc_set_cchhb(&vlabel->vtoc, 0x0000, 0x0001, 0x01); } else { vtoc_volume_label_set_label(vlabel, "LNX1"); } } } /* * Check whether hashsteps are within the correct interval. */ static void check_hashmarks(dasdfmt_info_t *info) { if (info->print_hashmarks) { if (info->hashstep < reqsize) info->hashstep = reqsize; if ((info->hashstep < 1) || (info->hashstep > 1000)) { printf("Hashmark increment is not in range <1,1000>, " "using the default.\n"); info->hashstep = 10; } printf("Printing hashmark every %d cylinders.\n", info->hashstep); } } /* * This function checks whether a range of tracks is in regular format * with the specified block size. */ static format_check_t check_track_format(dasdfmt_info_t *info, format_data_t *p) { format_check_t cdata = { .expect = { .blksize = p->blksize, .intensity = p->intensity, .start_unit = p->start_unit, .stop_unit = p->stop_unit }, 0 }; if (ioctl(filedes, BIODASDCHECKFMT, &cdata)) { if (errno == ENOTTY) { ERRMSG("%s: Missing kernel support for format checking", prog_name); if (mode == EXPAND) { ERRMSG(". Mode 'expand' cannot be used"); } else if (!info->check) { ERRMSG(" (--force to override)"); } ERRMSG_EXIT(EXIT_FAILURE, ".\n"); } ERRMSG_EXIT(EXIT_FAILURE, "%s: Could no check format: %s\n", prog_name, strerror(errno)); } return cdata; } /* * Either do the actual format or check depending on the check-value. */ static int process_tracks(dasdfmt_info_t *info, unsigned int cylinders, unsigned int heads, format_data_t *format_params) { format_check_t cdata = { .expect = {0}, 0}; format_data_t step = *format_params; unsigned long step_value; unsigned long cur_trk; int cyl = 0; check_hashmarks(info); cur_trk = format_params->start_unit; while (cur_trk < format_params->stop_unit) { step_value = reqsize * heads - (cur_trk % heads); step.start_unit = cur_trk; if (cur_trk + heads * reqsize >= format_params->stop_unit) step.stop_unit = format_params->stop_unit; else step.stop_unit = cur_trk + step_value - 1; if (info->check) { cdata = check_track_format(info, &step); if (cdata.result) { cyl = cur_trk / heads + 1; draw_progress(info, cyl, cylinders, 1); evaluate_format_error(info, &cdata, heads); break; } } else { if (ioctl(filedes, BIODASDFMT, &step) != 0) ERRMSG_EXIT(EXIT_FAILURE, "%s: the ioctl call " "to format tracks failed. (%s)\n", prog_name, strerror(errno)); } cyl = cur_trk / heads + 1; draw_progress(info, cyl, cylinders, 0); cur_trk += step_value; } /* We're done, draw the 100% mark */ if (!cdata.result) { cyl = step.stop_unit / heads + 1; draw_progress(info, cyl, cylinders, 0); printf("\n"); } return cdata.result; } /* * This function checks the format of the entire disk. */ static void check_disk_format(dasdfmt_info_t *info, unsigned int cylinders, unsigned int heads, format_data_t *check_params) { check_params->start_unit = 0; check_params->stop_unit = (cylinders * heads) - 1; printf("Checking format of the entire disk...\n"); if (info->testmode) { printf("Test mode active, omitting ioctl.\n"); return; } check_blocksize(info, check_params->blksize); check_layout(info, check_params->intensity); /* * If no layout was specified, set the intensity * according to what the layout seems to be. */ if (!info->layout_specified) { if (info->dasd_info.format == DASD_FORMAT_CDL) check_params->intensity |= DASD_FMT_INT_COMPAT; else if (info->dasd_info.format == DASD_FORMAT_LDL) check_params->intensity &= ~DASD_FMT_INT_COMPAT; } if (process_tracks(info, cylinders, heads, check_params)) { ERRMSG_EXIT(EXIT_FAILURE, "Use --mode=full to perform a " "clean format.\n"); } printf("Done. Disk is fine.\n"); } /* * ask the user to specify a blocksize */ static format_data_t ask_user_for_blksize(format_data_t params) { char c, str[ERR_LENGTH], buffer[20]; int i, rc; i = params.blksize; do { params.blksize = i; printf("Please enter the blocksize of the formatting [%d]: ", i); if (fgets(buffer, sizeof(buffer), stdin) == NULL) break; rc = sscanf(buffer, "%d%c", ¶ms.blksize, &c); if ((rc == 2) && (c == '\n')) rc = 1; if (rc == -1) rc = 1; /* this happens, if enter is pressed */ if (rc != 1) printf(" -- wrong input, try again.\n"); if (check_param(str, ERR_LENGTH, ¶ms) < 0) { printf(" -- %s\n", str); rc = 0; } } while (rc != 1); return params; } /* * print all information needed to format the device */ static void dasdfmt_print_info(dasdfmt_info_t *info, volume_label_t *vlabel, unsigned int cylinders, unsigned int heads, format_data_t *p) { char volser[6], vollbl[4]; printf("Drive Geometry: %d Cylinders * %d Heads = %d Tracks\n", cylinders, heads, (cylinders * heads)); printf("\nI am going to format the device "); printf("%s in the following way:\n", info->devname); printf(" Device number of device : 0x%x\n", info->dasd_info.devno); printf(" Labelling device : %s\n", (info->writenolabel) ? "no" : "yes"); if (!info->writenolabel) { vtoc_volume_label_get_label(vlabel, vollbl); printf(" Disk label : %.4s\n", vollbl); vtoc_volume_label_get_volser(vlabel, volser); printf(" Disk identifier : %.6s\n", volser); } printf(" Extent start (trk no) : %u\n", p->start_unit); printf(" Extent end (trk no) : %u\n", p->stop_unit); printf(" Compatible Disk Layout : %s\n", (p->intensity & DASD_FMT_INT_COMPAT) ? "yes" : "no"); printf(" Blocksize : %d\n", p->blksize); printf(" Mode : %s\n", mode_str[mode]); if (info->testmode) printf("Test mode active, omitting ioctl.\n"); } /* * get volser */ static int dasdfmt_get_volser(dasdfmt_info_t *info, char *volser) { unsigned int blksize; int f; volume_label_t vlabel; f = open(info->devname, O_RDONLY); if (f == -1) ERRMSG_EXIT(EXIT_FAILURE, "%s: Unable to open device %s: %s\n", prog_name, info->devname, strerror(errno)); get_blocksize(&blksize); if (close(f) != 0) ERRMSG("%s: error during close: %s\ncontinuing...\n", prog_name, strerror(errno)); if ((strncmp(info->dasd_info.type, "ECKD", 4) == 0) && (!info->dasd_info.FBA_layout)) { /* OS/390 and zOS compatible disk layout */ vtoc_read_volume_label(info->devname, info->dasd_info.label_block * blksize, &vlabel); vtoc_volume_label_get_volser(&vlabel, volser); return 0; } else { return -1; } } /* * do all the labeling (volume label and initial VTOC) */ static void dasdfmt_write_labels(dasdfmt_info_t *info, volume_label_t *vlabel, unsigned int cylinders, unsigned int heads) { int label_position; struct hd_geometry geo; format4_label_t f4; format5_label_t f5; format7_label_t f7; unsigned int blksize; int rc; void *ipl1_record, *ipl2_record; int ipl1_record_len, ipl2_record_len; if (info->verbosity > 0) printf("Retrieving dasd information... "); get_blocksize(&blksize); /* * Don't rely on the cylinders returned by HDIO_GETGEO, they might be * to small. geo is only used to get the number of sectors, which may * vary depending on the format. */ if (ioctl(filedes, HDIO_GETGEO, &geo) != 0) ERRMSG_EXIT(EXIT_FAILURE, "%s: (write labels) IOCTL " "HDIO_GETGEO failed (%s).\n", prog_name, strerror(errno)); if (info->verbosity > 0) printf("ok\n"); /* write empty bootstrap (initial IPL records) */ if (info->verbosity > 0) printf("Writing empty bootstrap...\n"); /* * Note: ldl labels do not contain the key field */ if (info->cdl_format) { /* Prepare copy with key (CDL) */ ipl1_record = &ipl1; ipl2_record = &ipl2; ipl1_record_len = sizeof(ipl1); ipl2_record_len = sizeof(ipl2); } else { /* Prepare copy without key (LDL) */ ipl1_record = ipl1.data; ipl2_record = ipl2.data; ipl1_record_len = sizeof(ipl1.data); ipl2_record_len = sizeof(ipl2.data); } if (lseek(filedes, 0, SEEK_SET) != 0) ERRMSG_EXIT(EXIT_FAILURE, "%s: lseek command 0 failed " "(%s)\n", prog_name, strerror(errno)); rc = write(filedes, ipl1_record, ipl1_record_len); if (rc != ipl1_record_len) ERRMSG_EXIT(EXIT_FAILURE, "%s: Writing the bootstrap IPL1 " "failed, only wrote %d bytes.\n", prog_name, rc); label_position = blksize; rc = lseek(filedes, label_position, SEEK_SET); if (rc != label_position) ERRMSG_EXIT(EXIT_FAILURE, "%s: lseek command to %i failed " "(%s).\n", prog_name, label_position, strerror(errno)); rc = write(filedes, ipl2_record, ipl2_record_len); if (rc != ipl2_record_len) ERRMSG_EXIT(EXIT_FAILURE, "%s: Writing the bootstrap IPL2 " "failed, only wrote %d bytes.\n", prog_name, rc); /* write VTOC */ vtoc_init_format4_label(&f4, geo.cylinders, cylinders, heads, geo.sectors, blksize, info->dasd_info.dev_type); vtoc_init_format5_label(&f5); vtoc_init_format7_label(&f7); vtoc_set_freespace(&f4, &f5, &f7, '+', 0, FIRST_USABLE_TRK, (cylinders * heads - 1), cylinders, heads); label_position = info->dasd_info.label_block * blksize; if (info->verbosity > 0) printf("Writing label...\n"); rc = lseek(filedes, label_position, SEEK_SET); if (rc != label_position) ERRMSG_EXIT(EXIT_FAILURE, "%s: lseek command to %i failed " "(%s).\n", prog_name, label_position, strerror(errno)); /* * Note: cdl volume labels do not contain the 'formatted_blocks' part * and ldl labels do not contain the key field */ if (info->cdl_format) { rc = write(filedes, vlabel, (sizeof(*vlabel) - sizeof(vlabel->formatted_blocks))); } else { vlabel->ldl_version = 0xf2; /* EBCDIC '2' */ vlabel->formatted_blocks = cylinders * heads * geo.sectors; rc = write(filedes, &vlabel->vollbl, (sizeof(*vlabel) - sizeof(vlabel->volkey))); } if (((rc != sizeof(*vlabel) - sizeof(vlabel->formatted_blocks)) && info->cdl_format) || ((rc != (sizeof(*vlabel) - sizeof(vlabel->volkey))) && (!info->cdl_format))) ERRMSG_EXIT(EXIT_FAILURE, "%s: Error writing volume label " "(%d).\n", prog_name, rc); if (info->verbosity > 0) printf("Writing VTOC... "); label_position = (VTOC_START_CC * heads + VTOC_START_HH) * geo.sectors * blksize; rc = lseek(filedes, label_position, SEEK_SET); if (rc != label_position) ERRMSG_EXIT(EXIT_FAILURE, "%s: lseek command to %i failed " "(%s).\n", prog_name, label_position, strerror(errno)); /* write VTOC FMT4 DSCB */ rc = write(filedes, &f4, sizeof(format4_label_t)); if (rc != sizeof(format4_label_t)) ERRMSG_EXIT(EXIT_FAILURE, "%s: Error writing FMT4 label " "(%d).\n", prog_name, rc); label_position += blksize; rc = lseek(filedes, label_position, SEEK_SET); if (rc != label_position) ERRMSG_EXIT(EXIT_FAILURE, "%s: lseek to %i failed (%s).\n", prog_name, label_position, strerror(errno)); /* write VTOC FMT5 DSCB */ rc = write(filedes, &f5, sizeof(format5_label_t)); if (rc != sizeof(format5_label_t)) ERRMSG_EXIT(EXIT_FAILURE, "%s: Error writing FMT5 label " "(%d).\n", prog_name, rc); if ((cylinders * heads) > BIG_DISK_SIZE) { label_position += blksize; rc = lseek(filedes, label_position, SEEK_SET); if (rc != label_position) ERRMSG_EXIT(EXIT_FAILURE, "%s: lseek to %i failed " "(%s).\n", prog_name, label_position, strerror(errno)); /* write VTOC FMT 7 DSCB (only on big disks) */ rc = write(filedes, &f7, sizeof(format7_label_t)); if (rc != sizeof(format7_label_t)) ERRMSG_EXIT(EXIT_FAILURE, "%s: Error writing FMT7 " "label (rc=%d).\n", prog_name, rc); } fsync(filedes); if (info->verbosity > 0) printf("ok\n"); } /* * This function will search for the beginning of an unformatted area * on the device. It checks selected tracks beforehand and makes sure * that the device is formatted to a certain extent. Otherwise the * process is terminated. */ static void dasdfmt_find_start(dasdfmt_info_t *info, unsigned int cylinders, unsigned heads, format_data_t *format_params) { format_check_t cdata; unsigned int middle; unsigned int left = 2; unsigned int right = (cylinders * heads) - 1; unsigned int first = left; check_blocksize(info, format_params->blksize); format_params->start_unit = 0; format_params->stop_unit = 4; cdata = check_track_format(info, format_params); if (cdata.result) { evaluate_format_error(info, &cdata, heads); ERRMSG_EXIT(EXIT_FAILURE, "Use --mode=full to perform a " "clean format.\n"); } printf("Expansion mode active. Searching for starting position...\n"); while (left <= right) { /* new track number to look at */ middle = left + ((right - left) / 2); format_params->start_unit = middle; format_params->stop_unit = middle; cdata = check_track_format(info, format_params); if (cdata.blksize != format_params->blksize) { first = middle; right = middle - 1; } else { left = middle + 1; } } if (first == 2 && cdata.blksize == format_params->blksize) ERRMSG_EXIT(EXIT_FAILURE, "No unformatted part found, aborting.\n"); printf("Done. Unformatted part starts at track %d.\n", first); /* return format_params with start_unit set to the correct value */ format_params->start_unit = first; } /* * formats the disk cylinderwise */ static void dasdfmt_format(dasdfmt_info_t *info, unsigned int cylinders, unsigned int heads, format_data_t *format_params) { process_tracks(info, cylinders, heads, format_params); } static void dasdfmt_prepare_and_format(dasdfmt_info_t *info, unsigned int cylinders, unsigned int heads, format_data_t *p) { format_data_t temp = { .start_unit = 0, .stop_unit = 0, .blksize = p->blksize, .intensity = ((p->intensity & ~DASD_FMT_INT_FMT_NOR0) | DASD_FMT_INT_INVAL) }; if (!((info->withoutprompt) && (info->verbosity < 1))) printf("Formatting the device. This may take a while " "(get yourself a coffee).\n"); if (info->verbosity > 0) printf("Detaching the device...\n"); if (ioctl(filedes, BIODASDDISABLE, p) != 0) ERRMSG_EXIT(EXIT_FAILURE, "%s: (prepare device) IOCTL " "BIODASDDISABLE failed. (%s)\n", prog_name, strerror(errno)); disk_disabled = 1; if (info->verbosity > 0) printf("Invalidate first track...\n"); if (ioctl(filedes, BIODASDFMT, &temp) != 0) ERRMSG_EXIT(EXIT_FAILURE, "%s: (invalidate first track) IOCTL " "BIODASDFMT failed. (%s)\n", prog_name, strerror(errno)); /* except track 0 from standard formatting procss */ p->start_unit = 1; dasdfmt_format(info, cylinders, heads, p); if (info->verbosity > 0) printf("formatting tracks complete...\n"); temp.intensity = p->intensity; if (info->verbosity > 0) printf("Revalidate first track...\n"); if (ioctl(filedes, BIODASDFMT, &temp) != 0) ERRMSG_EXIT(EXIT_FAILURE, "%s: (re-validate first track) IOCTL" " BIODASDFMT failed (%s)\n", prog_name, strerror(errno)); if (info->verbosity > 0) printf("Re-accessing the device...\n"); if (ioctl(filedes, BIODASDENABLE, p) != 0) ERRMSG_EXIT(EXIT_FAILURE, "%s: (prepare device) IOCTL " "BIODASDENABLE failed. (%s)\n", prog_name, strerror(errno)); disk_disabled = 0; } /* * This function will start the expand format process. */ static void dasdfmt_expand_format(dasdfmt_info_t *info, unsigned int cylinders, unsigned int heads, format_data_t *p) { if (!((info->withoutprompt) && (info->verbosity < 1))) printf("Formatting the device. This may take a while " "(get yourself a coffee).\n"); if (info->verbosity > 0) printf("Detaching the device...\n"); if (ioctl(filedes, BIODASDDISABLE, p) != 0) ERRMSG_EXIT(EXIT_FAILURE, "%s: (prepare device) IOCTL " "BIODASDDISABLE failed. (%s)\n", prog_name, strerror(errno)); disk_disabled = 1; dasdfmt_format(info, cylinders, heads, p); if (info->verbosity > 0) printf("Formatting tracks complete...\n"); if (info->verbosity > 0) printf("Re-accessing the device...\n"); if (ioctl(filedes, BIODASDENABLE, p) != 0) ERRMSG_EXIT(EXIT_FAILURE, "%s: (prepare device) IOCTL " "BIODASDENABLE failed. (%s)\n", prog_name, strerror(errno)); disk_disabled = 0; } /* * This function will only format the first two tracks of a DASD. * The rest of the DASD is untouched and left as is. */ static void dasdfmt_quick_format(dasdfmt_info_t *info, unsigned int cylinders, unsigned int heads, format_data_t *p) { format_check_t cdata = { .expect = {0}, 0 }; format_data_t tmp = *p; if (info->force) { printf("Skipping format check due to --force.\n"); } else { check_blocksize(info, p->blksize); printf("Checking the format of selected tracks...\n"); /* Check device format on the first and last 3 regular tracks */ tmp.start_unit = 2; tmp.stop_unit = 4; cdata = check_track_format(info, &tmp); if (!cdata.result) { tmp.start_unit = (cylinders * heads) - 3; tmp.stop_unit = (cylinders * heads) - 1; cdata = check_track_format(info, &tmp); } if (cdata.result) { evaluate_format_error(info, &cdata, heads); ERRMSG_EXIT(EXIT_FAILURE, "Use --mode=full to perform " "a clean format.\n"); } else { printf("Done. Disk seems fine.\n"); } } if (!((info->withoutprompt) && (info->verbosity < 1))) printf("Formatting the first two tracks of the device.\n"); /* Disable the device before we do anything */ if (ioctl(filedes, BIODASDDISABLE, p)) ERRMSG_EXIT(EXIT_FAILURE, "%s: the ioctl to disable the device " "failed. (%s)\n", prog_name, strerror(errno)); disk_disabled = 1; /* Now do the actual formatting of our first two tracks */ if (ioctl(filedes, BIODASDFMT, p)) ERRMSG_EXIT(EXIT_FAILURE, "%s: the ioctl to format the device " "failed. (%s)\n", prog_name, strerror(errno)); /* Re-Enable the device so that we can continue working with it */ if (ioctl(filedes, BIODASDENABLE, p)) ERRMSG_EXIT(EXIT_FAILURE, "%s: the ioctl to enable the device " "failed. (%s)\n", prog_name, strerror(errno)); disk_disabled = 0; } static void do_format_dasd(dasdfmt_info_t *info, volume_label_t *vlabel, format_data_t *p, unsigned int cylinders, unsigned int heads) { char inp_buffer[5]; int count; p->start_unit = 0; switch (mode) { case FULL: /* all tracks */ p->stop_unit = (cylinders * heads) - 1; break; case QUICK: /* just the first two */ p->stop_unit = 1; break; case EXPAND: /* only the end of the disk */ dasdfmt_find_start(info, cylinders, heads, p); p->stop_unit = (cylinders * heads) - 1; break; } if ((info->verbosity > 0) || !info->withoutprompt || info->testmode) dasdfmt_print_info(info, vlabel, cylinders, heads, p); count = u2s_get_host_access_count(info->devname); if (info->force_host) { if (count > 1) { ERRMSG_EXIT(EXIT_FAILURE, "\n%s: Disk %s is online on OS instances in %d different LPARs.\n" "Note: Your installation might include z/VM systems that are configured to\n" "automatically vary on disks, regardless of whether they are subsequently used.\n\n", prog_name, info->devname, count); } else if (count < 0) { ERRMSG("\nHosts access information not available for disk %s.\n\n", info->devname); return; } } else if (count > 1) ERRMSG("\nWARNING:\n" "Disk %s is online on operating system instances in %d different LPARs.\n" "Ensure that the disk is not being used by a system outside your LPAR.\n" "Note: Your installation might include z/VM systems that are configured to\n" "automatically vary on disks, regardless of whether they are subsequently used.\n", info->devname, count); if (!info->testmode) { if (!info->withoutprompt) { printf("\n"); if (mode != EXPAND) printf("--->> ATTENTION! <<---\nAll data of " "that device will be lost.\n"); printf("Type \"yes\" to continue, no will leave the " "disk untouched: "); if (fgets(inp_buffer, sizeof(inp_buffer), stdin) == NULL) return; if (strcasecmp(inp_buffer, "yes") && strcasecmp(inp_buffer, "yes\n")) { printf("Omitting ioctl call (disk will " "NOT be formatted).\n"); return; } } switch (mode) { case FULL: dasdfmt_prepare_and_format(info, cylinders, heads, p); break; case QUICK: dasdfmt_quick_format(info, cylinders, heads, p); break; case EXPAND: dasdfmt_expand_format(info, cylinders, heads, p); break; } printf("Finished formatting the device.\n"); if (!(info->writenolabel || mode == EXPAND)) dasdfmt_write_labels(info, vlabel, cylinders, heads); printf("Rereading the partition table... "); if (reread_partition_table()) { ERRMSG("%s: error during rereading the partition " "table: %s.\n", prog_name, strerror(errno)); } else { printf("ok\n"); } } } int main(int argc, char *argv[]) { dasdfmt_info_t info = { .dasd_info = {0}, {0} }; volume_label_t vlabel; char old_volser[7]; char dev_filename[PATH_MAX]; char str[ERR_LENGTH]; char buf[7]; char *blksize_param_str = NULL; char *reqsize_param_str = NULL; char *hashstep_str = NULL; int rc; unsigned int cylinders, heads; /* Establish a handler for interrupt signals. */ signal(SIGTERM, program_interrupt_signal); signal(SIGINT, program_interrupt_signal); signal(SIGQUIT, program_interrupt_signal); /******************* initialization ********************/ prog_name = argv[0]; util_prg_init(&prg); util_opt_init(opt_vec, NULL); /* set default values */ vtoc_volume_label_init(&vlabel); format_params.blksize = DEFAULT_BLOCKSIZE; format_params.intensity = DASD_FMT_INT_COMPAT; mode = FULL; /*************** parse parameters **********************/ while (1) { rc = util_opt_getopt_long(argc, argv); switch (rc) { case 'F': info.force = 1; break; case 'd': if (strcasecmp(optarg, "cdl") == 0) { format_params.intensity |= DASD_FMT_INT_COMPAT; if (info.writenolabel) { printf("WARNING: using the cdl " "format without writing a " "label doesn't make much " "sense!\n"); exit(1); } } else if (strcasecmp(optarg, "ldl") == 0) { format_params.intensity &= ~DASD_FMT_INT_COMPAT; } else { printf("%s is not a valid option!\n", optarg); exit(1); } info.layout_specified = 1; break; case 'y': info.withoutprompt = 1; break; case OPT_NOZERO: format_params.intensity |= DASD_FMT_INT_FMT_NOR0; break; case 't': info.testmode = 1; break; case 'p': if (!(info.print_hashmarks || info.print_percentage)) info.print_progressbar = 1; break; case 'm': if (!(info.print_progressbar || info.print_percentage)) { hashstep_str = optarg; info.print_hashmarks = 1; } break; case 'P': if (!(info.print_hashmarks || info.print_progressbar)) info.print_percentage = 1; break; case 'v': info.verbosity = 1; break; case 'h': util_prg_print_help(); util_opt_print_help(); exit(EXIT_SUCCESS); case 'V': util_prg_print_version(); exit(EXIT_SUCCESS); case 'l': strncpy(buf, optarg, 6); if (check_volser(buf, 0) < 0) break; vtoc_volume_label_set_volser(&vlabel, buf); info.labelspec = 1; break; case 'L': if (format_params.intensity & DASD_FMT_INT_COMPAT) { printf("WARNING: using the cdl format " "without writing a label doesn't " "make much sense!\n"); exit(1); } info.writenolabel = 1; break; case 'b': blksize_param_str = optarg; info.blksize_specified = 1; break; case 'r': reqsize_param_str = optarg; info.reqsize_specified = 1; break; case 'k': info.keep_volser = 1; break; case 'C': info.force_host = 1; break; case 'M': if (strcasecmp(optarg, "full") == 0) mode = FULL; else if (strcasecmp(optarg, "quick") == 0) mode = QUICK; else if (strcasecmp(optarg, "expand") == 0) mode = EXPAND; else ERRMSG_EXIT(EXIT_FAILURE, "%s: The specified mode '%s' is " "invalid. Consult the man page for " "more information.\n", prog_name, optarg); break; case OPT_CHECK: info.check = 1; break; case -1: /* End of options string - start of devices list */ info.device_id = optind; break; default: ERRMSG_EXIT(EXIT_MISUSE, "Try '%s --help' for more" " information.\n", prog_name); } if (rc == -1) break; /* exit loop if finished */ } CHECK_SPEC_MAX_ONCE(info.blksize_specified, "blocksize"); CHECK_SPEC_MAX_ONCE(info.labelspec, "label"); CHECK_SPEC_MAX_ONCE(info.writenolabel, "omit-label-writing flag"); if (info.blksize_specified) PARSE_PARAM_INTO(format_params.blksize, blksize_param_str, 10, "blocksize"); if (info.reqsize_specified) { PARSE_PARAM_INTO(reqsize, reqsize_param_str, 10, "requestsize"); if (reqsize < 1 || reqsize > 255) ERRMSG_EXIT(EXIT_FAILURE, "invalid requestsize %d specified\n", reqsize); } else { reqsize = DEFAULT_REQUESTSIZE; } if (info.print_hashmarks) PARSE_PARAM_INTO(info.hashstep, hashstep_str, 10, "hashstep"); get_device_name(&info, dev_filename, argc, argv); filedes = open(info.devname, O_RDWR); if (filedes == -1) ERRMSG_EXIT(EXIT_FAILURE, "%s: Unable to open device %s: %s\n", prog_name, info.devname, strerror(errno)); get_device_info(&info); /* Either let the user specify the blksize or get it from the kernel */ if (!info.blksize_specified) { if (!(mode == FULL || info.dasd_info.format == DASD_FORMAT_NONE) || info.check) get_blocksize(&format_params.blksize); else format_params = ask_user_for_blksize(format_params); } if (info.keep_volser) { if (info.labelspec) { ERRMSG_EXIT(EXIT_MISUSE, "%s: The -k and -l options " "are mutually exclusive\n", prog_name); } if (!(format_params.intensity & DASD_FMT_INT_COMPAT)) { printf("WARNING: VOLSER cannot be kept " "when using the ldl format!\n"); exit(1); } if (dasdfmt_get_volser(&info, old_volser) == 0) vtoc_volume_label_set_volser(&vlabel, old_volser); else ERRMSG_EXIT(EXIT_FAILURE, "%s: VOLSER not found on device %s\n", prog_name, info.devname); } check_disk(&info); if (check_param(str, ERR_LENGTH, &format_params) < 0) ERRMSG_EXIT(EXIT_MISUSE, "%s: %s\n", prog_name, str); set_geo(&info, &cylinders, &heads); set_label(&info, &vlabel, &format_params, cylinders); if (info.check) check_disk_format(&info, cylinders, heads, &format_params); else do_format_dasd(&info, &vlabel, &format_params, cylinders, heads); if (close(filedes) != 0) ERRMSG("%s: error during close: %s\ncontinuing...\n", prog_name, strerror(errno)); return 0; }