# # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2006 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # /usr/src/cmd/lib/fs/pcfs is the directory of all pcfs specific commands # whose executable reside in $(INSDIR1) and $(INSDIR2). # SUBDIR1= mount fstyp SUBDIR2= mkfs fsck SUBDIRS= $(SUBDIR1) $(SUBDIR2) all: TARGET= all install: TARGET= install clean: TARGET= clean clobber: TARGET= clobber catalog: TARGET= catalog # for messaging catalog # POFILE= pcfs.po # Hammerhead: GNU Make % substitution only replaces first %; use foreach. POFILES= $(foreach d,$(SUBDIR2),$(d)/$(d).po) .KEEP_STATE: .PARALLEL: $(SUBDIRS) all install: $(SUBDIRS) catalog: $(POFILE) $(POFILE): $(SUBDIR2) $(RM) $@ cat $(POFILES) > $@ clean clobber: $(SUBDIRS) $(SUBDIRS): FRC @cd $@; pwd; $(MAKE) $(TARGET) FRC: /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1999 by Sun Microsystems, Inc. * All rights reserved. * Copyright 2024 MNX Cloud, Inc. */ #ifndef _PCFS_BPB_H #define _PCFS_BPB_H #ifdef __cplusplus extern "C" { #endif /* * Common Bios Parameter Block definitions for the pcfs user-level utilities */ #define MINBPS 512 #define MAXBPS 4096 #define OPCODE1 0xE9 #define OPCODE2 0xEB #define BOOTSECSIG 0xAA55 /* * Offset (in bytes) from address of boot sector to where we put * the backup copy of that sector. (FAT32 only) */ #define BKUP_BOOTSECT_OFFSET 0xC00 #define uppercase(c) ((c) >= 'a' && (c) <= 'z' ? (c) - 'a' + 'A' : (c)) #define FAT12_TYPE_STRING "FAT12 " #define FAT16_TYPE_STRING "FAT16 " #define FAT32_TYPE_STRING "FAT32 " #define FAT12_ENTSPERSECT 341 #define FAT16_ENTSPERSECT 256 #define FAT32_ENTSPERSECT 128 #ifndef SUNIXOSBOOT #define SUNIXOSBOOT 190 /* Solaris UNIX boot partition */ #endif /* * MS-DOS Disk layout: * * --------------------- * | Boot sector | * |-------------------| * | Reserved area | * |-------------------| * | FAT #1 | * |-------------------| * | FAT #2 | * |-------------------| * | Root directory | * |-------------------| * | | * | File area | * |___________________| */ #ifdef _LITTLE_ENDIAN #pragma pack(1) #endif struct _orig_bios_param_blk { uint16_t bytes_per_sector; uchar_t sectors_per_cluster; uint16_t resv_sectors; uchar_t num_fats; uint16_t num_root_entries; /* * The sectors_in_volume field will be zero on larger volumes (>32Mb) * and newer file systems (>=MSDOS4.0). In these cases the * sectors_in_logical_volume field should be used instead. */ uint16_t sectors_in_volume; uchar_t media; uint16_t sectors_per_fat; uint16_t sectors_per_track; uint16_t heads; /* * Number of sectors in the partition prior to the start of the logical disk */ uint32_t hidden_sectors; uint32_t sectors_in_logical_volume; }; #ifdef _LITTLE_ENDIAN #pragma pack() #endif #ifdef _LITTLE_ENDIAN #pragma pack(1) #endif struct _bpb32_extensions { uint32_t big_sectors_per_fat; uint16_t ext_flags; uchar_t fs_vers_lo; uchar_t fs_vers_hi; uint32_t root_dir_clust; uint16_t fsinfosec; uint16_t backupboot; uint16_t reserved[6]; }; #ifdef _LITTLE_ENDIAN #pragma pack() #endif #ifdef _LITTLE_ENDIAN #pragma pack(1) #endif struct _bpb_extensions { uchar_t phys_drive_num; uchar_t reserved; uchar_t ext_signature; uint32_t volume_id; uchar_t volume_label[11]; uchar_t type[8]; }; #ifdef _LITTLE_ENDIAN #pragma pack() #endif #ifdef _LITTLE_ENDIAN #pragma pack(1) #endif struct _sun_bpb_extensions { uint16_t bs_offset_high; uint16_t bs_offset_low; }; #ifdef _LITTLE_ENDIAN #pragma pack() #endif /* * bpb_t is a conglomeration of all the fields a bpb can have. Every * bpb will have the orig_bios struct, but only FAT32's will have bpb32, * and only Solaris boot diskettes will have the sunbpb structure. */ typedef struct _bios_param_blk { struct _orig_bios_param_blk bpb; struct _bpb32_extensions bpb32; struct _bpb_extensions ebpb; struct _sun_bpb_extensions sunbpb; } bpb_t; #ifdef _LITTLE_ENDIAN #pragma pack(1) struct _bpb_head { uchar_t bs_jump_code[3]; uchar_t bs_oem_name[8]; struct _orig_bios_param_blk bs_bpb; }; #pragma pack() #pragma pack(1) struct _boot_sector { struct _bpb_head bs_front; struct _bpb_extensions bs_ebpb; struct _sun_bpb_extensions bs_sebpb; uchar_t bs_bootstrap[444]; uchar_t bs_signature[2]; }; #pragma pack() #pragma pack(1) struct _boot_sector32 { struct _bpb_head bs_front; struct _bpb32_extensions bs_bpb32; struct _bpb_extensions bs_ebpb; uchar_t bs_bootstrap[420]; uchar_t bs_signature[2]; }; #pragma pack() #else #define ORIG_BPB_START_INDEX 8 /* index into filler field */ #define EXT_BPB_START_INDEX 33 /* index into filler field */ #define BPB_32_START_INDEX 33 /* index into filler field */ #define EXT_BPB_32_START_INDEX 61 /* index into filler field */ struct _boot_sector { uchar_t bs_jump_code[3]; uchar_t bs_filler[59]; uchar_t bs_sun_bpb[4]; uchar_t bs_bootstrap[444]; uchar_t bs_signature[2]; }; struct _boot_sector32 { uchar_t bs_jump_code[3]; uchar_t bs_filler[87]; uchar_t bs_bootstrap[420]; uchar_t bs_signature[2]; }; #endif typedef union _ubso { struct _boot_sector bs; struct _boot_sector32 bs32; struct mboot mb; uchar_t buf[MAXBPS]; } boot_sector_t; #ifdef __cplusplus } #endif #endif /* _PCFS_BPB_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright (c) 2011 Gary Mills * Copyright 2024 MNX Cloud, Inc. */ /* * common functions used by pcfs tools. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pcfs_common.h" #include "pcfs_bpb.h" /* * The assumption here is that _BIG_ENDIAN implies sparc, and * so in addition to swapping bytes we also have to construct * packed structures by hand to avoid bus errors due to improperly * aligned pointers. */ #ifdef _BIG_ENDIAN void swap_pack_grab32bpb(bpb_t *wbpb, struct _boot_sector *bsp); void swap_pack_grabbpb(bpb_t *wbpb, struct _boot_sector *bsp); #endif /* _BIG_ENDIAN */ /* * Validate sector size. */ bool is_sector_size_valid(size_t size) { if (size != 512 && size != 1024 && size != 2048 && size != 4096) return (false); return (true); } /* * Use DKIOCGMEDIAINFO to get sector size. */ int get_media_sector_size(int fd, size_t *sizep) { struct dk_minfo dkminfo; if (ioctl(fd, DKIOCGMEDIAINFO, &dkminfo) != -1) { *sizep = dkminfo.dki_lbsize; return (0); } /* In case the DKIOCGMEDIAINFO is not supported, return MINBPS. */ if (errno == ENOTTY) { *sizep = MINBPS; return (0); } return (errno); } /* * store_16_bits * Save the lower 16 bits of a 32 bit value (v) into the provided * buffer (pointed at by *bp), and increment the buffer pointer * as well. This way the routine can be called multiple times in * succession to fill buffers. The value is stored in little-endian * order. */ void store_16_bits(uchar_t **bp, uint32_t v) { uchar_t *l = *bp; *l++ = v & 0xff; *l = (v >> 8) & 0xff; *bp += 2; } void read_16_bits(uchar_t *bp, uint32_t *value) { *value = *bp++; *value += *bp << 8; } /* * store_32_bits * Save the 32 bit value (v) into the provided buffer (pointed * at by *bp), and increment the buffer pointer as well. This way * the routine can be called multiple times in succession to fill * buffers. The value is stored in little-endian order. */ void store_32_bits(uchar_t **bp, uint32_t v) { uchar_t *l = *bp; int b; for (b = 0; b < 4; b++) { *l++ = v & 0xff; v = v >> 8; } *bp += 4; } void read_32_bits(uchar_t *bp, uint32_t *value) { *value = *bp++; *value += *bp++ << 8; *value += *bp++ << 16; *value += *bp++ << 24; } /* * dump_bytes -- display bytes as hex numbers. * b is the pointer to the byte buffer * n is the number of bytes in the buffer */ /* Note: BPL = bytes to display per line */ #define BPL 16 void dump_bytes(uchar_t *buf, int n) { int printedCount; int countdown = n; int countup = 0; int offset = 0; int byte; /* Display offset, 16 bytes per line, and printable ascii version */ while (countdown > 0) { printedCount = 0; (void) fprintf(stderr, "\n%06x: ", offset); /* * Print Hex value of characters in columns on left */ for (byte = 0; byte < BPL; byte++) { if (countup + byte < n) { (void) fprintf(stderr, "%02x ", (buf[countup + byte] & 0xff)); printedCount++; } else { (void) fprintf(stderr, " "); } } /* * Right side has the printable character or '.' for * unprintable for each column of the left. */ for (byte = 0; byte < BPL; byte++) { if ((countup + byte < n) && ((buf[countup + byte] >= ' ') && (buf[countup + byte] <= '~'))) { (void) fprintf(stderr, "%c", buf[countup + byte]); } else { (void) fprintf(stderr, "."); } } countup += printedCount; offset += printedCount; countdown -= printedCount; } (void) fprintf(stderr, "\n\n"); } /* * header_for_dump -- display simple header over what will be output. */ void header_for_dump(void) { int byte; (void) fprintf(stderr, "\n "); for (byte = 0; byte < BPL; byte++) (void) fprintf(stderr, "%02x ", byte); (void) fprintf(stderr, "\n "); byte = 3 * BPL; while (byte-- > 0) (void) fprintf(stderr, "-"); } /* * We are basically (incorrectly) assuming that if you aren't running * on x86 the BPB has to be packed by hand AND that the bytes must * be swapped. One or both of these assumptions may one day be invalid. * (if they aren't already :-)) */ #ifdef _BIG_ENDIAN /* * swap_pack_grab{32}bpb * If not on an x86 we assume the structures making up the bpb * were not packed and that longs and shorts need to be byte swapped * (we've kept everything in host order up until now). A new architecture * might not need to swap or might not need to pack, in which case * new routines will have to be written. Of course if an architecture * supports both packing and little-endian host order, it can follow the * same path as the x86 code. */ void swap_pack_grabbpb(bpb_t *wbpb, struct _boot_sector *bsp) { uchar_t *grabp; grabp = (uchar_t *)&(bsp->bs_filler[ORIG_BPB_START_INDEX]); ((uchar_t *)&(wbpb->bpb.bytes_per_sector))[1] = *grabp++; ((uchar_t *)&(wbpb->bpb.bytes_per_sector))[0] = *grabp++; wbpb->bpb.sectors_per_cluster = *grabp++; ((uchar_t *)&(wbpb->bpb.resv_sectors))[1] = *grabp++; ((uchar_t *)&(wbpb->bpb.resv_sectors))[0] = *grabp++; wbpb->bpb.num_fats = *grabp++; ((uchar_t *)&(wbpb->bpb.num_root_entries))[1] = *grabp++; ((uchar_t *)&(wbpb->bpb.num_root_entries))[0] = *grabp++; ((uchar_t *)&(wbpb->bpb.sectors_in_volume))[1] = *grabp++; ((uchar_t *)&(wbpb->bpb.sectors_in_volume))[0] = *grabp++; wbpb->bpb.media = *grabp++; ((uchar_t *)&(wbpb->bpb.sectors_per_fat))[1] = *grabp++; ((uchar_t *)&(wbpb->bpb.sectors_per_fat))[0] = *grabp++; ((uchar_t *)&(wbpb->bpb.sectors_per_track))[1] = *grabp++; ((uchar_t *)&(wbpb->bpb.sectors_per_track))[0] = *grabp++; ((uchar_t *)&(wbpb->bpb.heads))[1] = *grabp++; ((uchar_t *)&(wbpb->bpb.heads))[0] = *grabp++; ((uchar_t *)&(wbpb->bpb.hidden_sectors))[3] = *grabp++; ((uchar_t *)&(wbpb->bpb.hidden_sectors))[2] = *grabp++; ((uchar_t *)&(wbpb->bpb.hidden_sectors))[1] = *grabp++; ((uchar_t *)&(wbpb->bpb.hidden_sectors))[0] = *grabp++; ((uchar_t *)&(wbpb->bpb.sectors_in_logical_volume))[3] = *grabp++; ((uchar_t *)&(wbpb->bpb.sectors_in_logical_volume))[2] = *grabp++; ((uchar_t *)&(wbpb->bpb.sectors_in_logical_volume))[1] = *grabp++; ((uchar_t *)&(wbpb->bpb.sectors_in_logical_volume))[0] = *grabp++; wbpb->ebpb.phys_drive_num = *grabp++; wbpb->ebpb.reserved = *grabp++; wbpb->ebpb.ext_signature = *grabp++; ((uchar_t *)&(wbpb->ebpb.volume_id))[3] = *grabp++; ((uchar_t *)&(wbpb->ebpb.volume_id))[2] = *grabp++; ((uchar_t *)&(wbpb->ebpb.volume_id))[1] = *grabp++; ((uchar_t *)&(wbpb->ebpb.volume_id))[0] = *grabp++; (void) strncpy((char *)wbpb->ebpb.volume_label, (char *)grabp, 11); grabp += 11; (void) strncpy((char *)wbpb->ebpb.type, (char *)grabp, 8); } void swap_pack_grab32bpb(bpb_t *wbpb, struct _boot_sector *bsp) { uchar_t *grabp; grabp = (uchar_t *)&(bsp->bs_filler[BPB_32_START_INDEX]); ((uchar_t *)&(wbpb->bpb32.big_sectors_per_fat))[3] = *grabp++; ((uchar_t *)&(wbpb->bpb32.big_sectors_per_fat))[2] = *grabp++; ((uchar_t *)&(wbpb->bpb32.big_sectors_per_fat))[1] = *grabp++; ((uchar_t *)&(wbpb->bpb32.big_sectors_per_fat))[0] = *grabp++; ((uchar_t *)&(wbpb->bpb32.ext_flags))[1] = *grabp++; ((uchar_t *)&(wbpb->bpb32.ext_flags))[0] = *grabp++; wbpb->bpb32.fs_vers_lo = *grabp++; wbpb->bpb32.fs_vers_hi = *grabp++; ((uchar_t *)&(wbpb->bpb32.root_dir_clust))[3] = *grabp++; ((uchar_t *)&(wbpb->bpb32.root_dir_clust))[2] = *grabp++; ((uchar_t *)&(wbpb->bpb32.root_dir_clust))[1] = *grabp++; ((uchar_t *)&(wbpb->bpb32.root_dir_clust))[0] = *grabp++; ((uchar_t *)&(wbpb->bpb32.fsinfosec))[1] = *grabp++; ((uchar_t *)&(wbpb->bpb32.fsinfosec))[0] = *grabp++; ((uchar_t *)&(wbpb->bpb32.backupboot))[1] = *grabp++; ((uchar_t *)&(wbpb->bpb32.backupboot))[0] = *grabp++; ((uchar_t *)&(wbpb->bpb32.reserved[0]))[1] = *grabp++; ((uchar_t *)&(wbpb->bpb32.reserved[0]))[0] = *grabp++; ((uchar_t *)&(wbpb->bpb32.reserved[1]))[1] = *grabp++; ((uchar_t *)&(wbpb->bpb32.reserved[1]))[0] = *grabp++; ((uchar_t *)&(wbpb->bpb32.reserved[2]))[1] = *grabp++; ((uchar_t *)&(wbpb->bpb32.reserved[2]))[0] = *grabp++; ((uchar_t *)&(wbpb->bpb32.reserved[3]))[1] = *grabp++; ((uchar_t *)&(wbpb->bpb32.reserved[3]))[0] = *grabp++; ((uchar_t *)&(wbpb->bpb32.reserved[4]))[1] = *grabp++; ((uchar_t *)&(wbpb->bpb32.reserved[4]))[0] = *grabp++; ((uchar_t *)&(wbpb->bpb32.reserved[5]))[1] = *grabp++; ((uchar_t *)&(wbpb->bpb32.reserved[5]))[0] = *grabp++; } #endif /* _BIG_ENDIAN */ char * stat_actual_disk(const char *diskname, struct stat *info, char **suffix) { char *actualdisk; if (stat(diskname, info) != 0) { /* * Device named on command line doesn't exist. That * probably means there is a partition-specifying * suffix attached to the actual disk name. */ if ((actualdisk = strdup(diskname)) == NULL) { (void) fprintf(stderr, gettext("Out of memory for disk name.\n")); exit(2); } if ((*suffix = strchr(actualdisk, ':')) != NULL) { **suffix = '\0'; (*suffix)++; } if (stat(actualdisk, info)) { err(2, "Failed to stat disk device %s", actualdisk); } } else { if ((actualdisk = strdup(diskname)) == NULL) { (void) fprintf(stderr, gettext("Out of memory for disk name.\n")); exit(2); } } return (actualdisk); } extern void usage(void); void bad_arg(char *option) { (void) fprintf(stderr, gettext("Unrecognized option -o %s.\n"), option); usage(); exit(2); } void missing_arg(char *option) { (void) fprintf(stderr, gettext("Option %s requires a value.\n"), option); usage(); exit(3); } static int parse_drvnum(char *pn) { int drvnum; /* * Determine logical drive to seek after. */ if ((strlen(pn) == 1) && ((*pn >= 'c') && (*pn <= 'z'))) { drvnum = *pn - 'c' + 1; } else if ((*pn >= '0') && (*pn <= '9')) { char *d; int v = 0; d = pn; while ((*d != '\0') && (*d >= '0') && (*d <= '9')) { v *= 10; v += *d - '0'; d++; } if ((*d != '\0') || (v > 24)) { (void) fprintf(stderr, gettext("%s: bogus logical drive specification.\n"), pn); return (-1); } drvnum = v; } else if (strcmp(pn, "boot") == 0) { drvnum = 99; } else { (void) fprintf(stderr, gettext("%s: bogus logical drive specification.\n"), pn); return (-1); } return (drvnum); } /* * isDosDrive() * Boolean function. Give it the systid field for an fdisk partition * and it decides if that's a systid that describes a DOS drive. We * use systid values defined in sys/dktp/fdisk.h. */ static int isDosDrive(uchar_t checkMe) { return ((checkMe == DOSOS12) || (checkMe == DOSOS16) || (checkMe == DOSHUGE) || (checkMe == FDISK_WINDOWS) || (checkMe == FDISK_EXT_WIN) || (checkMe == FDISK_FAT95) || (checkMe == DIAGPART)); } /* * isDosExtended() * Boolean function. Give it the systid field for an fdisk partition * and it decides if that's a systid that describes an extended DOS * partition. */ static int isDosExtended(uchar_t checkMe) { return ((checkMe == EXTDOS) || (checkMe == FDISK_EXTLBA)); } /* * isBootPart() * Boolean function. Give it the systid field for an fdisk partition * and it decides if that's a systid that describes a Solaris boot * partition. */ static int isBootPart(uchar_t checkMe) { return (checkMe == X86BOOT); } off64_t findPartitionOffset(int fd, size_t bpsec, char *ldrive) { struct ipart part[FD_NUMPART]; struct mboot extmboot; struct mboot mb; diskaddr_t xstartsect; off64_t nextseek = 0; off64_t lastseek = 0; off64_t found = 0; off64_t error = -1; int logicalDriveCount = 0; int extendedPart = -1; int primaryPart = -1; int bootPart = -1; uint32_t xnumsect = 0; int drvnum; int driveIndex; int i; /* * Count of drives in the current extended partition's * FDISK table, and indexes of the drives themselves. */ int extndDrives[FD_NUMPART]; int numDrives = 0; /* * Count of drives (beyond primary) in master boot record's * FDISK table, and indexes of the drives themselves. */ int extraDrives[FD_NUMPART]; int numExtraDrives = 0; if ((drvnum = parse_drvnum(ldrive)) < 0) return (error); if (read(fd, &mb, bpsec) != (ssize_t)bpsec) { (void) fprintf(stderr, gettext("Couldn't read a Master Boot Record\n")); return (error); } if (ltohs(mb.signature) != BOOTSECSIG) { (void) fprintf(stderr, gettext("Bad signature on master boot record (%x)\n"), ltohs(mb.signature)); return (error); } /* * Copy partition table into memory */ (void) memcpy(part, mb.parts, sizeof (part)); /* * Get a summary of what is in the Master FDISK table. * Normally we expect to find one partition marked as a DOS drive. * This partition is the one Windows calls the primary dos partition. * If the machine has any logical drives then we also expect * to find a partition marked as an extended DOS partition. * * Sometimes we'll find multiple partitions marked as DOS drives. * The Solaris fdisk program allows these partitions * to be created, but Windows fdisk no longer does. We still need * to support these, though, since Windows does. We also need to fix * our fdisk to behave like the Windows version. * * It turns out that some off-the-shelf media have *only* an * Extended partition, so we need to deal with that case as * well. * * Only a single (the first) Extended or Boot Partition will * be recognized. Any others will be ignored. */ for (i = 0; i < FD_NUMPART; i++) { if (isDosDrive(part[i].systid)) { if (primaryPart < 0) { logicalDriveCount++; primaryPart = i; } else { extraDrives[numExtraDrives++] = i; } continue; } if ((extendedPart < 0) && isDosExtended(part[i].systid)) { extendedPart = i; continue; } if ((bootPart < 0) && isBootPart(part[i].systid)) { bootPart = i; continue; } } if (drvnum == BOOT_PARTITION_DRIVE) { if (bootPart < 0) { (void) fprintf(stderr, gettext("No boot partition found on drive\n")); return (error); } found = ltohi(part[bootPart].relsect) * bpsec; return (found); } if (drvnum == PRIMARY_DOS_DRIVE && primaryPart >= 0) { found = ltohi(part[primaryPart].relsect) * bpsec; return (found); } /* * We are not looking for the C: drive (or there was no primary * drive found), so we had better have an extended partition or * extra drives in the Master FDISK table. */ if ((extendedPart < 0) && (numExtraDrives == 0)) { (void) fprintf(stderr, gettext("No such logical drive " "(missing extended partition entry)\n")); return (error); } if (extendedPart >= 0) { nextseek = xstartsect = ltohi(part[extendedPart].relsect); xnumsect = ltohi(part[extendedPart].numsect); do { /* * If the seek would not cause us to change * position on the drive, then we're out of * extended partitions to examine. */ if (nextseek == lastseek) break; logicalDriveCount += numDrives; /* * Seek the next extended partition, and find * logical drives within it. */ if (lseek64(fd, nextseek * bpsec, SEEK_SET) < 0 || read(fd, &extmboot, sizeof (extmboot)) != sizeof (extmboot)) { perror(gettext("Unable to read extended " "partition record")); return (error); } (void) memcpy(part, extmboot.parts, sizeof (part)); lastseek = nextseek; if (ltohs(extmboot.signature) != MBB_MAGIC) { (void) fprintf(stderr, gettext("Bad signature on " "extended partition\n")); return (error); } /* * Count up drives, and track where the next * extended partition is in case we need it. We * are expecting only one extended partition. If * there is more than one we'll only go to the * first one we see, but warn about ignoring. */ numDrives = 0; for (i = 0; i < FD_NUMPART; i++) { if (isDosDrive(part[i].systid)) { extndDrives[numDrives++] = i; continue; } else if (isDosExtended(part[i].systid)) { if (nextseek != lastseek) { /* * Already found an extended * partition in this table. */ (void) fprintf(stderr, gettext("WARNING: " "Ignoring unexpected " "additional extended " "partition")); continue; } nextseek = xstartsect + ltohi(part[i].relsect); continue; } } } while (drvnum > logicalDriveCount + numDrives); if (drvnum <= logicalDriveCount + numDrives) { /* * The number of logical drives we've found thus * far is enough to get us to the one we were * searching for. */ driveIndex = logicalDriveCount + numDrives - drvnum; found = ltohi(part[extndDrives[driveIndex]].relsect) + lastseek; if (found > (xstartsect + xnumsect)) { (void) fprintf(stderr, gettext("Logical drive start sector (%d) " "is not within the partition!\n"), found); return (error); } else { found *= bpsec; } return (found); } else { /* * We ran out of extended dos partition * drives. The only hope now is to go * back to extra drives defined in the master * fdisk table. But we overwrote that table * already, so we must load it in again. */ logicalDriveCount += numDrives; (void) memcpy(part, mb.parts, sizeof (part)); } } /* * Still haven't found the drive, is it an extra * drive defined in the main FDISK table? */ if (drvnum <= logicalDriveCount + numExtraDrives) { driveIndex = logicalDriveCount + numExtraDrives - drvnum; found = ltohi(part[extraDrives[driveIndex]].relsect) * bpsec; return (found); } return (error); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1999 by Sun Microsystems, Inc. * All rights reserved. * Copyright (c) 2011 Gary Mills * Copyright 2024 MNX Cloud, Inc. */ #ifndef _PCFS_COMMON_H #define _PCFS_COMMON_H /* * Common routines for the pcfs user-level utilities */ #ifdef __cplusplus extern "C" { #endif #include #include #include #include #include #include "pcfs_bpb.h" #define IN_RANGE(n, x, y) (((n) >= (x)) && ((n) <= (y))) /* * A macro implementing a ceiling function for integer divides. */ #define idivceil(dvend, dvsor) \ ((dvend)/(dvsor) + (((dvend)%(dvsor) == 0) ? 0 : 1)) /* * These defines should move into a kernel header file eventually * and pcfs_mount may want to refuse to mount FAT32's that aren't "clean" * * If Windows shuts down properly it sets the fourth bit of the 8th * and final reserved byte at the start of the FAT. */ #define WIN_SHUTDOWN_STATUS_BYTE 7 #define WIN_SHUTDOWN_BIT_MASK 0x8 /* * Define some special logical drives we use. */ #define BOOT_PARTITION_DRIVE 99 #define PRIMARY_DOS_DRIVE 1 /* * Function prototypes */ extern off64_t findPartitionOffset(int fd, size_t bpsec, char *ldrive); extern char *stat_actual_disk(const char *diskname, struct stat *info, char **suffix); extern void header_for_dump(void); extern void store_16_bits(uchar_t **bp, uint32_t v); extern void store_32_bits(uchar_t **bp, uint32_t v); extern void read_16_bits(uchar_t *bp, uint32_t *value); extern void read_32_bits(uchar_t *bp, uint32_t *value); extern void missing_arg(char *option); extern void dump_bytes(uchar_t *b, int n); extern void bad_arg(char *option); extern void usage(void); extern bool is_sector_size_valid(size_t size); extern int get_media_sector_size(int fd, size_t *sizep); /* * The assumption here is that _BIG_ENDIAN implies sparc, and * so in addition to swapping bytes we also have to construct * packed structures by hand to avoid bus errors due to improperly * aligned pointers. */ #ifdef _BIG_ENDIAN extern void swap_pack_grab32bpb(bpb_t *wbpb, struct _boot_sector *bsp); extern void swap_pack_grabbpb(bpb_t *wbpb, struct _boot_sector *bsp); #endif /* _BIG_ENDIAN */ #ifdef __cplusplus } #endif #endif /* _PCFS_COMMON_H */ # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2006 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # Copyright 2024 MNX Cloud, Inc. # FSTYPE= pcfs LIBPROG= fsck ATTMK= $(LIBPROG) include ../../Makefile.fstype COMMONOBJS= pcfs_common.o getresponse.o COMMONSRCS= ../common/pcfs_common.c $(SRC)/common/util/getresponse.c FSCKOBJS= fsck_main.o bpb.o clusters.o fat.o dir.o FSCKSRCS= $(FSCKOBJS:%.o=%.c) # # Error injection module for debugging purposes # #DEBUGOBJS= inject.o #DEBUGSRCS= $(DEBUGOBJS:%.o=%.c) OBJS= $(FSCKOBJS) $(DEBUGOBJS) $(COMMONOBJS) SRCS= $(FSCKSRCS) $(DEBUGSRCS) $(COMMONSRCS) # for messaging catalog # POFILES= $(OBJS:%.o=%.po) POFILE= fsck.po catalog: $(POFILE) CPPFLAGS += -D_LARGEFILE64_SOURCE CPPFLAGS += -I../common CPPFLAGS += -I$(SRC)/common/util CERRWARN += -Wno-parentheses CERRWARN += -Wno-unused-variable CERRWARN += $(CNOWARN_UNINIT) $(LIBPROG): $(OBJS) $(LINK.c) -o $@ $(OBJS) $(LDLIBS) $(POST_PROCESS) %.o : ../common/%.c $(COMPILE.c) $(OUTPUT_OPTION) $< $(POST_PROCESS_O) %.o : $(SRC)/common/util/%.c $(COMPILE.c) $(OUTPUT_OPTION) $< $(POST_PROCESS_O) $(POFILE): $(RM) $@ $(COMPILE.cpp) $(SRCS) > $(POFILE).i $(XGETTEXT) $(XGETFLAGS) $(POFILE).i sed "/^domain/d" messages.po > $@ $(RM) $(POFILE).i messages.po clean: $(RM) $(FSCKOBJS) $(DEBUGOBJS) $(COMMONOBJS) $(POFILE).i messages.po /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1999,2000 by Sun Microsystems, Inc. * All rights reserved. * Copyright (c) 2011 Gary Mills * Copyright 2024 MNX Cloud, Inc. */ /* * fsck_pcfs -- routines for manipulating the BPB (BIOS parameter block) * of the file system. */ #include #include #include #include #include #include #include #include #include #include #include "pcfs_common.h" #include "fsck_pcfs.h" #include "pcfs_bpb.h" extern off64_t FirstClusterOffset; extern off64_t PartitionOffset; extern int32_t BytesPerCluster; extern int32_t TotalClusters; extern int32_t LastCluster; extern int32_t RootDirSize; extern int32_t FATSize; extern short FATEntrySize; extern bpb_t TheBIOSParameterBlock; extern int IsFAT32; extern int Verbose; static void computeFileAreaSize(void) { int32_t dataSectors; int32_t overhead; /* * Compute bytes/cluster for later reference */ BytesPerCluster = TheBIOSParameterBlock.bpb.sectors_per_cluster * TheBIOSParameterBlock.bpb.bytes_per_sector; /* * First we'll find total number of sectors in the file area... */ if (TheBIOSParameterBlock.bpb.sectors_in_volume > 0) dataSectors = TheBIOSParameterBlock.bpb.sectors_in_volume; else dataSectors = TheBIOSParameterBlock.bpb.sectors_in_logical_volume; overhead = TheBIOSParameterBlock.bpb.resv_sectors; RootDirSize = TheBIOSParameterBlock.bpb.num_root_entries * sizeof (struct pcdir); overhead += RootDirSize / TheBIOSParameterBlock.bpb.bytes_per_sector; if (TheBIOSParameterBlock.bpb.sectors_per_fat) { /* * Good old FAT12 or FAT16 */ overhead += TheBIOSParameterBlock.bpb.num_fats * TheBIOSParameterBlock.bpb.sectors_per_fat; /* * Compute this for later - when we actually pull in a copy * of the FAT */ FATSize = TheBIOSParameterBlock.bpb.sectors_per_fat * TheBIOSParameterBlock.bpb.bytes_per_sector; } else { /* * FAT32 * I'm unsure if this is always going to work. At one * point during the creation of this program and mkfs_pcfs * it seemed that Windows had created an fs where it had * rounded big_sectors_per_fat up to a cluster boundary. * Later, though, I encountered a problem where I wasn't * finding the root directory because I was looking in the * wrong place by doing that same roundup. So, for now, * I'm backing off on the cluster boundary thing and just * believing what I am told. */ overhead += TheBIOSParameterBlock.bpb.num_fats * TheBIOSParameterBlock.bpb32.big_sectors_per_fat; /* * Compute this for later - when we actually pull in a copy * of the FAT */ FATSize = TheBIOSParameterBlock.bpb32.big_sectors_per_fat * TheBIOSParameterBlock.bpb.bytes_per_sector; } /* * Now change sectors to clusters. The computed value for * TotalClusters is persistent for the remainder of execution. */ dataSectors -= overhead; TotalClusters = dataSectors / TheBIOSParameterBlock.bpb.sectors_per_cluster; /* * Also need to compute last cluster and offset of the first cluster */ LastCluster = TotalClusters + FIRST_CLUSTER; FirstClusterOffset = overhead * TheBIOSParameterBlock.bpb.bytes_per_sector; FirstClusterOffset += PartitionOffset; /* * XXX this should probably be more sophisticated */ if (IsFAT32) FATEntrySize = 32; else { if (TotalClusters <= DOS_F12MAXC) FATEntrySize = 12; else FATEntrySize = 16; } if (Verbose) { (void) fprintf(stderr, gettext("Disk has a file area of %d " "allocation units,\neach with %d sectors = %llu " "bytes.\n"), TotalClusters, TheBIOSParameterBlock.bpb.sectors_per_cluster, (uint64_t)TotalClusters * TheBIOSParameterBlock.bpb.sectors_per_cluster * TheBIOSParameterBlock.bpb.bytes_per_sector); (void) fprintf(stderr, gettext("File system overhead of %d sectors.\n"), overhead); (void) fprintf(stderr, gettext("The last cluster is %d\n"), LastCluster); } } /* * XXX - right now we aren't attempting to fix anything that looks bad, * instead we just give up. */ void readBPB(int fd) { boot_sector_t ubpb; /* * The BPB is the first sector of the file system */ if (lseek64(fd, PartitionOffset, SEEK_SET) < 0) { mountSanityCheckFails(); perror(gettext("Cannot seek to start of disk partition")); (void) close(fd); exit(7); } if (Verbose) (void) fprintf(stderr, gettext("Reading BIOS parameter block\n")); if (read(fd, ubpb.buf, bpsec) < bpsec) { mountSanityCheckFails(); perror(gettext("Read BIOS parameter block")); (void) close(fd); exit(2); } if (ltohs(ubpb.mb.signature) != BOOTSECSIG) { mountSanityCheckFails(); (void) fprintf(stderr, gettext("Bad signature on BPB. Giving up.\n")); exit(2); } #ifdef _BIG_ENDIAN swap_pack_grabbpb(&TheBIOSParameterBlock, &(ubpb.bs)); #else (void) memcpy(&(TheBIOSParameterBlock.bpb), &(ubpb.bs.bs_front.bs_bpb), sizeof (TheBIOSParameterBlock.bpb)); (void) memcpy(&(TheBIOSParameterBlock.ebpb), &(ubpb.bs.bs_ebpb), sizeof (TheBIOSParameterBlock.ebpb)); #endif if (TheBIOSParameterBlock.bpb.bytes_per_sector != 512 && TheBIOSParameterBlock.bpb.bytes_per_sector != 1024 && TheBIOSParameterBlock.bpb.bytes_per_sector != 2048 && TheBIOSParameterBlock.bpb.bytes_per_sector != 4096) { mountSanityCheckFails(); (void) fprintf(stderr, gettext("Bogus bytes per sector value. Giving up.\n")); exit(2); } if (!(ISP2(TheBIOSParameterBlock.bpb.sectors_per_cluster) && IN_RANGE(TheBIOSParameterBlock.bpb.sectors_per_cluster, 1, 128))) { mountSanityCheckFails(); (void) fprintf(stderr, gettext("Bogus sectors per cluster value. Giving up.\n")); (void) close(fd); exit(6); } if (TheBIOSParameterBlock.bpb.sectors_per_fat == 0) { #ifdef _BIG_ENDIAN swap_pack_grab32bpb(&TheBIOSParameterBlock, &(ubpb.bs)); #else (void) memcpy(&(TheBIOSParameterBlock.bpb32), &(ubpb.bs32.bs_bpb32), sizeof (TheBIOSParameterBlock.bpb32)); #endif IsFAT32 = 1; } if (!IsFAT32) { if ((TheBIOSParameterBlock.bpb.num_root_entries == 0) || ((TheBIOSParameterBlock.bpb.num_root_entries * sizeof (struct pcdir)) % TheBIOSParameterBlock.bpb.bytes_per_sector) != 0) { mountSanityCheckFails(); (void) fprintf(stderr, gettext("Bogus number of root entries. " "Giving up.\n")); exit(2); } } else { if (TheBIOSParameterBlock.bpb.num_root_entries != 0) { mountSanityCheckFails(); (void) fprintf(stderr, gettext("Bogus number of root entries. " "Giving up.\n")); exit(2); } } /* * In general, we would expect the number of FATs field to * equal 2. Our mkfs and Windows have this as a default * value. I suppose someone could override the default, * though, so we'll sort of arbitrarily accept any number * between 1 and 4 inclusive as reasonable values. * * XXX: Warn, but continue, if value is suspicious? (>2?) */ if (TheBIOSParameterBlock.bpb.num_fats > 4 || TheBIOSParameterBlock.bpb.num_fats < 1) { mountSanityCheckFails(); (void) fprintf(stderr, gettext("Bogus number of FATs. Giving up.\n")); exit(2); } computeFileAreaSize(); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1999,2000 by Sun Microsystems, Inc. * All rights reserved. * Copyright (c) 2016 by Delphix. All rights reserved. * Copyright 2024 MNX Cloud, Inc. */ /* * fsck_pcfs -- routines for manipulating clusters. */ #include #include #include #include #include #include #include #include #include #include #include #include "getresponse.h" #include "pcfs_common.h" #include "fsck_pcfs.h" extern ClusterContents TheRootDir; extern off64_t FirstClusterOffset; extern off64_t PartitionOffset; extern int32_t BytesPerCluster; extern int32_t TotalClusters; extern int32_t LastCluster; extern int32_t RootDirSize; extern int32_t FATSize; extern bpb_t TheBIOSParameterBlock; extern short FATEntrySize; extern int RootDirModified; extern int OkayToRelink; extern int ReadOnly; extern int IsFAT32; extern int Verbose; static struct pcdir BlankPCDIR; static CachedCluster *ClusterCache; static ClusterInfo **InUse; static int32_t ReservedClusterCount; static int32_t AllocedClusterCount; static int32_t FreeClusterCount; static int32_t BadClusterCount; /* * Internal statistics */ static int32_t CachedClusterCount; int32_t HiddenClusterCount; int32_t FileClusterCount; int32_t DirClusterCount; int32_t HiddenFileCount; int32_t FileCount; int32_t DirCount; static int32_t orphanSizeLookup(int32_t clusterNum); static void freeNameInfo(int32_t clusterNum) { /* silent failure for bogus clusters */ if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster) return; if (InUse[clusterNum - FIRST_CLUSTER]->path != NULL) { if (InUse[clusterNum - FIRST_CLUSTER]->path->references > 1) { InUse[clusterNum - FIRST_CLUSTER]->path->references--; } else { free(InUse[clusterNum - FIRST_CLUSTER]->path->fullName); free(InUse[clusterNum - FIRST_CLUSTER]->path); } InUse[clusterNum - FIRST_CLUSTER]->path = NULL; } } static void printOrphanPath(int32_t clusterNum) { /* silent failure for bogus clusters */ if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster) return; if (InUse[clusterNum - FIRST_CLUSTER]->path != NULL) { (void) printf(gettext("\nOrphaned allocation units originally " "allocated to:\n")); (void) printf("%s\n", InUse[clusterNum - FIRST_CLUSTER]->path->fullName); freeNameInfo(clusterNum); } else { (void) printf(gettext("\nOrphaned allocation units originally " "allocated to an unknown file or directory:\n")); (void) printf(gettext("Orphaned chain begins with allocation " "unit %d.\n"), clusterNum); } } static void printOrphanSize(int32_t clusterNum) { int32_t size = orphanSizeLookup(clusterNum); if (size > 0) { (void) printf(gettext("%d bytes in the orphaned chain of " "allocation units.\n"), size); if (Verbose) { (void) printf(gettext("[Starting at allocation " "unit %d]\n"), clusterNum); } } } static void printOrphanInfo(int32_t clusterNum) { printOrphanPath(clusterNum); printOrphanSize(clusterNum); } static bool askAboutFreeing(int32_t clusterNum) { /* * If it is not OkayToRelink, we haven't already printed the size * of the orphaned chain. */ if (!OkayToRelink) printOrphanInfo(clusterNum); /* * If we are in preen mode, preenBail won't return. */ preenBail("Need user confirmation to free orphaned chain.\n"); (void) printf( gettext("Free the allocation units in the orphaned chain ? " "(y/n) ")); if (AlwaysYes) return (true); if (AlwaysNo) return (false); return (yes()); } static bool askAboutRelink(int32_t clusterNum) { /* * Display the size of the chain for the user to consider. */ printOrphanInfo(clusterNum); /* * If we are in preen mode, preenBail won't return. */ preenBail("Need user confirmation to re-link orphaned chain.\n"); (void) printf(gettext("Re-link orphaned chain into file system ? " "(y/n) ")); if (AlwaysYes) return (true); if (AlwaysNo) return (false); return (yes()); } static int isHidden(int32_t clusterNum) { /* silent failure for bogus clusters */ if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster) return (0); if (InUse[clusterNum - FIRST_CLUSTER] == NULL) return (0); return (InUse[clusterNum - FIRST_CLUSTER]->flags & CLINFO_HIDDEN); } static int isInUse(int32_t clusterNum) { /* silent failure for bogus clusters */ if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster) return (0); return ((InUse[clusterNum - FIRST_CLUSTER] != NULL) && (InUse[clusterNum - FIRST_CLUSTER]->dirent != NULL)); } /* * Caller's may request that we cache the data from a readCluster. * The xxxClusterxxxCachexxx routines handle looking for cached data * or initially caching the data. * * XXX - facilitate releasing cached data for low memory situations. */ static CachedCluster * findClusterCacheEntry(int32_t clusterNum) { CachedCluster *loop = ClusterCache; while (loop != NULL) { if (loop->clusterNum == clusterNum) return (loop); loop = loop->next; } return (NULL); } static uchar_t * findClusterDataInTheCache(int32_t clusterNum) { CachedCluster *loop = ClusterCache; while (loop) { if (loop->clusterNum == clusterNum) return (loop->clusterData.bytes); loop = loop->next; } return (NULL); } static uchar_t * addToCache(int32_t clusterNum, uchar_t *buf, int32_t *datasize) { CachedCluster *new; uchar_t *cp; if ((new = (CachedCluster *)malloc(sizeof (CachedCluster))) == NULL) { perror(gettext("No memory for cached cluster info")); return (buf); } new->clusterNum = clusterNum; new->modified = 0; if ((cp = (uchar_t *)calloc(1, BytesPerCluster)) == NULL) { perror(gettext("No memory for cached copy of cluster")); free(new); return (buf); } (void) memcpy(cp, buf, *datasize); new->clusterData.bytes = cp; if (Verbose) { (void) fprintf(stderr, gettext("Allocation unit %d cached.\n"), clusterNum); } if (ClusterCache == NULL) { ClusterCache = new; new->next = NULL; } else if (new->clusterNum < ClusterCache->clusterNum) { new->next = ClusterCache; ClusterCache = new; } else { CachedCluster *loop = ClusterCache; CachedCluster *trailer = NULL; while (loop && new->clusterNum > loop->clusterNum) { trailer = loop; loop = loop->next; } trailer->next = new; if (loop) { new->next = loop; } else { new->next = NULL; } } CachedClusterCount++; return (new->clusterData.bytes); } static int seekCluster(int fd, int32_t clusterNum) { off64_t seekto; int saveError; seekto = FirstClusterOffset + ((off64_t)clusterNum - FIRST_CLUSTER) * BytesPerCluster; if (lseek64(fd, seekto, SEEK_SET) != seekto) { saveError = errno; (void) fprintf(stderr, gettext("Seek to Allocation unit #%d failed: "), clusterNum); (void) fprintf(stderr, strerror(saveError)); (void) fprintf(stderr, "\n"); return (0); } return (1); } /* * getcluster * Get cluster bytes off the disk. We always read those bytes into * the same static buffer. If the caller wants its own copy of the * data it'll have to make its own copy. We'll return all the data * read, even if it's short of a full cluster. This is for future use * when we might want to relocate any salvagable data from bad clusters. */ static int getCluster(int fd, int32_t clusterNum, uchar_t **data, int32_t *datasize) { static uchar_t *clusterBuffer = NULL; int saveError; int try; *datasize = 0; *data = NULL; if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster) return (RDCLUST_BADINPUT); if (clusterBuffer == NULL && (clusterBuffer = (uchar_t *)malloc(BytesPerCluster)) == NULL) { perror(gettext("No memory for a cluster data buffer")); return (RDCLUST_MEMERR); } for (try = 0; try < RDCLUST_MAX_RETRY; try++) { if (!seekCluster(fd, clusterNum)) return (RDCLUST_FAIL); if ((*datasize = read(fd, clusterBuffer, BytesPerCluster)) == BytesPerCluster) { *data = clusterBuffer; return (RDCLUST_GOOD); } } if (*datasize >= 0) { *data = clusterBuffer; (void) fprintf(stderr, gettext("Short read of allocation unit #%d\n"), clusterNum); } else { saveError = errno; (void) fprintf(stderr, "Allocation unit %d:", clusterNum); (void) fprintf(stderr, strerror(saveError)); (void) fprintf(stderr, "\n"); } return (RDCLUST_FAIL); } static void writeCachedCluster(int fd, CachedCluster *clustInfo) { ssize_t bytesWritten; if (ReadOnly) return; if (Verbose) (void) fprintf(stderr, gettext("Allocation unit %d modified.\n"), clustInfo->clusterNum); if (seekCluster(fd, clustInfo->clusterNum) == 0) return; if ((bytesWritten = write(fd, clustInfo->clusterData.bytes, BytesPerCluster)) != BytesPerCluster) { if (bytesWritten < 0) { perror(gettext("Failed to write modified " "allocation unit")); } else { (void) fprintf(stderr, gettext("Short write of allocation unit %d\n"), clustInfo->clusterNum); } (void) close(fd); exit(13); } } /* * It's cheaper to allocate a lot at a time; malloc overhead pushes * you over the brink much more quickly if you don't. * This numbers seems to be a fair trade-off between reduced malloc overhead * and additional overhead by over-allocating. */ #define CHUNKSIZE 1024 static ClusterInfo *pool; static ClusterInfo * newClusterInfo(void) { ClusterInfo *ret; if (pool == NULL) { int i; pool = (ClusterInfo *)malloc(sizeof (ClusterInfo) * CHUNKSIZE); if (pool == NULL) { perror( gettext("Out of memory for cluster information")); exit(9); } for (i = 0; i < CHUNKSIZE - 1; i++) pool[i].nextfree = &pool[i+1]; pool[CHUNKSIZE-1].nextfree = NULL; } ret = pool; pool = pool->nextfree; memset(ret, 0, sizeof (*ret)); return (ret); } /* Should be called with verified arguments */ static ClusterInfo * cloneClusterInfo(int32_t clusterNum) { ClusterInfo *cl = InUse[clusterNum - FIRST_CLUSTER]; if (cl->refcnt > 1) { ClusterInfo *newCl = newClusterInfo(); cl->refcnt--; *newCl = *cl; newCl->refcnt = 1; if (newCl->path) newCl->path->references++; InUse[clusterNum - FIRST_CLUSTER] = newCl; } return (InUse[clusterNum - FIRST_CLUSTER]); } static void updateFlags(int32_t clusterNum, int newflags) { ClusterInfo *cl = InUse[clusterNum - FIRST_CLUSTER]; if (cl->flags != newflags && cl->refcnt > 1) cl = cloneClusterInfo(clusterNum); cl->flags = newflags; } static void freeClusterInfo(ClusterInfo *old) { if (--old->refcnt <= 0) { if (old->path && --old->path->references <= 0) { free(old->path->fullName); free(old->path); } old->nextfree = pool; pool = old; } } /* * Allocate entries in our sparse array of cluster information. * Returns non-zero if the structure already has been allocated * (for those keeping score at home). * * The template parameter, if non-NULL, is used to facilitate sharing * the ClusterInfo nodes for the clusters belonging to the same file. * The first call to allocInUse for a new file should have *template * set to 0; on return, *template then points to the newly allocated * ClusterInfo. Second and further calls keep the same value * in *template and that ClusterInfo ndoe is then used for all * entries in the file. Code that modifies the ClusterInfo nodes * should take care proper sharing semantics are maintained (i.e., * copy-on-write using cloneClusterInfo()) * * The ClusterInfo used in the template is guaranted to be in use in * at least one other cluster as we never return a value if we didn't * set it first. So we can overwrite it without the possibility of a leak. */ static int allocInUse(int32_t clusterNum, ClusterInfo **template) { ClusterInfo *newCl; if (InUse[clusterNum - FIRST_CLUSTER] != NULL) return (CLINFO_PREVIOUSLY_ALLOCED); if (template != NULL && *template != NULL) newCl = *template; else { newCl = newClusterInfo(); if (template) *template = newCl; } InUse[clusterNum - FIRST_CLUSTER] = newCl; newCl->refcnt++; return (CLINFO_NEWLY_ALLOCED); } static void markFree(int32_t clusterNum) { /* silent failure for bogus clusters */ if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster) return; if (InUse[clusterNum - FIRST_CLUSTER]) { if (InUse[clusterNum - FIRST_CLUSTER]->saved) free(InUse[clusterNum - FIRST_CLUSTER]->saved); freeClusterInfo(InUse[clusterNum - FIRST_CLUSTER]); InUse[clusterNum - FIRST_CLUSTER] = NULL; } } static void markOrphan(int fd, int32_t clusterNum, struct pcdir *dp) { /* silent failure for bogus clusters */ if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster) return; (void) markInUse(fd, clusterNum, dp, NULL, 0, VISIBLE, NULL); if (InUse[clusterNum - FIRST_CLUSTER] != NULL) updateFlags(clusterNum, InUse[clusterNum - FIRST_CLUSTER]->flags | CLINFO_ORPHAN); } static void markBad(int32_t clusterNum, uchar_t *recovered, int32_t recoveredLen) { /* silent failure for bogus clusters */ if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster) return; (void) allocInUse(clusterNum, NULL); if (recoveredLen) { (void) cloneClusterInfo(clusterNum); InUse[clusterNum - FIRST_CLUSTER]->saved = recovered; } updateFlags(clusterNum, InUse[clusterNum - FIRST_CLUSTER]->flags | CLINFO_BAD); BadClusterCount++; if (Verbose) (void) fprintf(stderr, gettext("Allocation unit %d marked bad.\n"), clusterNum); } static void clearOrphan(int32_t c) { /* silent failure for bogus clusters */ if (c < FIRST_CLUSTER || c > LastCluster) return; if (InUse[c - FIRST_CLUSTER] != NULL) updateFlags(c, InUse[c - FIRST_CLUSTER]->flags & ~CLINFO_ORPHAN); } static void clearInUse(int32_t c) { ClusterInfo **clp; /* silent failure for bogus clusters */ if (c < FIRST_CLUSTER || c > LastCluster) return; clp = &InUse[c - FIRST_CLUSTER]; if (*clp != NULL) { freeClusterInfo(*clp); *clp = NULL; } } static void clearAllClusters_InUse() { int32_t cc; for (cc = FIRST_CLUSTER; cc < LastCluster; cc++) { clearInUse(cc); } } static void makeUseTable(void) { if (InUse != NULL) { clearAllClusters_InUse(); return; } if ((InUse = (ClusterInfo **) calloc(TotalClusters, sizeof (ClusterInfo *))) == NULL) { perror(gettext("No memory for internal table")); exit(9); } } static void countClusters(void) { int32_t c; BadClusterCount = HiddenClusterCount = AllocedClusterCount = FreeClusterCount = 0; for (c = FIRST_CLUSTER; c < LastCluster; c++) { if (badInFAT(c)) { BadClusterCount++; } else if (isMarkedBad(c)) { /* * This catches the bad sectors found * during thorough verify that have never been * allocated to a file. Without this check, we * count these guys as free. */ BadClusterCount++; markBadInFAT(c); } else if (isHidden(c)) { HiddenClusterCount++; } else if (isInUse(c)) { AllocedClusterCount++; } else { FreeClusterCount++; } } } /* * summarizeFAT * Mark orphans without directory entries as allocated. * XXX - these chains should be reclaimed! * XXX - merge this routine with countClusters (same loop, duh.) */ static void summarizeFAT(int fd) { int32_t c; ClusterInfo *tmpl = NULL; for (c = FIRST_CLUSTER; c < LastCluster; c++) { if (!freeInFAT(c) && !badInFAT(c) && !reservedInFAT(c) && !isInUse(c)) { (void) markInUse(fd, c, &BlankPCDIR, NULL, 0, VISIBLE, &tmpl); } } } static void getReadyToSearch(int fd) { getFAT(fd); if (!IsFAT32) getRootDirectory(fd); } static char PathName[MAXPATHLEN]; static void summarize(int fd, int includeFAT) { struct pcdir *ignorep1, *ignorep2 = NULL; int32_t ignore32; char ignore; int pathlen; ReservedClusterCount = 0; AllocedClusterCount = 0; HiddenClusterCount = 0; FileClusterCount = 0; FreeClusterCount = 0; DirClusterCount = 0; BadClusterCount = 0; HiddenFileCount = 0; FileCount = 0; DirCount = 0; ignorep1 = ignorep2 = NULL; ignore = '\0'; PathName[0] = '\0'; pathlen = 0; getReadyToSearch(fd); /* * Traverse the full meta-data tree to talley what clusters * are in use. The root directory is an area outside of the * file space on FAT12 and FAT16 file systems. On FAT32 file * systems, the root directory is in a file area cluster just * like any other directory. */ if (!IsFAT32) { traverseFromRoot(fd, 0, PCFS_VISIT_SUBDIRS, PCFS_TRAVERSE_ALL, ignore, &ignorep1, &ignore32, &ignorep2, PathName, &pathlen); } else { DirCount++; traverseDir(fd, TheBIOSParameterBlock.bpb32.root_dir_clust, 0, PCFS_VISIT_SUBDIRS, PCFS_TRAVERSE_ALL, ignore, &ignorep1, &ignore32, &ignorep2, PathName, &pathlen); } if (includeFAT) summarizeFAT(fd); countClusters(); } int isMarkedBad(int32_t clusterNum) { /* silent failure for bogus clusters */ if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster) return (0); if (InUse[clusterNum - FIRST_CLUSTER] == NULL) return (0); return (InUse[clusterNum - FIRST_CLUSTER]->flags & CLINFO_BAD); } static int isMarkedOrphan(int32_t clusterNum) { /* silent failure for bogus clusters */ if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster) return (0); if (InUse[clusterNum - FIRST_CLUSTER] == NULL) return (0); return (InUse[clusterNum - FIRST_CLUSTER]->flags & CLINFO_ORPHAN); } static void orphanChain(int fd, int32_t c, struct pcdir *ndp) { ClusterInfo *tmpl = NULL; /* silent failure for bogus clusters */ if (c < FIRST_CLUSTER || c > LastCluster) return; clearInUse(c); markOrphan(fd, c, ndp); c = nextInChain(c); while (c != 0) { clearInUse(c); clearOrphan(c); (void) markInUse(fd, c, ndp, NULL, 0, VISIBLE, &tmpl); c = nextInChain(c); } } static int32_t findAFreeCluster(int32_t startAt) { int32_t look = startAt; for (;;) { if (freeInFAT(look)) { break; } if (look == LastCluster) look = FIRST_CLUSTER; else look++; if (look == startAt) break; } if (look != startAt) return (look); else return (0); } static void setEndOfDirectory(struct pcdir *dp) { dp->pcd_filename[0] = PCD_UNUSED; } static void emergencyEndOfDirectory(int fd, int32_t secondToLast) { ClusterContents dirdata; int32_t dirdatasize = 0; if (readCluster(fd, secondToLast, &(dirdata.bytes), &dirdatasize, RDCLUST_DO_CACHE) != RDCLUST_GOOD) { (void) fprintf(stderr, gettext("Unable to read allocation unit %d.\n"), secondToLast); (void) fprintf(stderr, gettext("Cannot allocate a new allocation unit to hold an" " end-of-directory marker.\nCannot access allocation unit" " to overwrite existing directory entry with\nthe marker." " Needed directory truncation has failed. Giving up.\n")); (void) close(fd); exit(11); } setEndOfDirectory(dirdata.dirp); markClusterModified(secondToLast); } static void makeNewEndOfDirectory(struct pcdir *entry, int32_t secondToLast, int32_t newCluster, ClusterContents *newData) { setEndOfDirectory(newData->dirp); markClusterModified(newCluster); /* * There are two scenarios. One is that we truncated the * directory in the very beginning. The other is that we * truncated it in the middle or at the end. In the first * scenario, the secondToLast argument is not a valid cluster * (it's zero), and so we actually need to change the start * cluster for the directory to this new start cluster. In * the second scenario, the secondToLast cluster we received * as an argument needs to be pointed at the new end of * directory. */ if (secondToLast == 0) { updateDirEnt_Start(entry, newCluster); } else { writeFATEntry(secondToLast, newCluster); } markLastInFAT(newCluster); } static void createNewEndOfDirectory(int fd, struct pcdir *entry, int32_t secondToLast) { ClusterContents dirdata; int32_t dirdatasize = 0; int32_t freeCluster; if (((freeCluster = findAFreeCluster(secondToLast)) != 0)) { if (readCluster(fd, freeCluster, &(dirdata.bytes), &dirdatasize, RDCLUST_DO_CACHE) == RDCLUST_GOOD) { if (Verbose) { (void) fprintf(stderr, gettext("Grabbed allocation unit #%d " "for truncated\ndirectory's new end " "of directory.\n"), freeCluster); } makeNewEndOfDirectory(entry, secondToLast, freeCluster, &dirdata); return; } } if (secondToLast == 0) { if (freeCluster == 0) { (void) fprintf(stderr, gettext("File system full.\n")); } else { (void) fprintf(stderr, gettext("Unable to read allocation unit %d.\n"), freeCluster); } (void) fprintf(stderr, gettext("Cannot allocate a new allocation unit to hold " "an end-of-directory marker.\nNo existing directory " "entries can be overwritten with the marker,\n" "the only unit allocated to the directory is " "inaccessible.\nNeeded directory truncation has failed. " "Giving up.\n")); (void) close(fd); exit(11); } emergencyEndOfDirectory(fd, secondToLast); } /* * truncAtCluster * Given a directory entry and a cluster number, search through * the cluster chain for the entry and make the cluster previous * to the given cluster in the chain the last cluster in the file. * The number of orphaned bytes is returned. For a chain that's * a directory we need to do some special handling, since we'll be * getting rid of the end of directory notice by truncating. */ static int64_t truncAtCluster(int fd, struct pcdir *entry, int32_t cluster) { uint32_t oldSize, newSize; int32_t prev, count, follow; int dir = (entry->pcd_attr & PCA_DIR); prev = 0; count = 0; follow = extractStartCluster(entry); while (follow != cluster && follow >= FIRST_CLUSTER && follow <= LastCluster) { prev = follow; count++; follow = nextInChain(follow); } if (follow != cluster) { /* * We didn't find the cluster they wanted to trunc at * anywhere in the entry's chain. So we'll leave the * entry alone, and return a negative value so they * can know something is wrong. */ return (-1); } if (Verbose) { (void) fprintf(stderr, gettext("Chain truncation at unit #%d\n"), cluster); } if (!dir) { oldSize = extractSize(entry); newSize = count * TheBIOSParameterBlock.bpb.sectors_per_cluster * TheBIOSParameterBlock.bpb.bytes_per_sector; if (newSize == 0) updateDirEnt_Start(entry, 0); } else { newSize = 0; } updateDirEnt_Size(entry, newSize); if (dir) { createNewEndOfDirectory(fd, entry, prev); } else if (prev != 0) { markLastInFAT(prev); } if (dir) { /* * We don't really know what the size of a directory is * but it is important for us to know if this truncation * results in an orphan with any size. The value we * return from this routine for a normal file is the * number of bytes left in the chain. For a directory * we can't be exact, and the caller doesn't really * expect us to be. For a directory the caller only * cares if there are zero bytes left or more than * zero bytes left. We'll return 1 to indicate * more than zero. */ if ((follow = nextInChain(follow)) != 0) return (1); else return (0); } /* * newSize should always be smaller than the old one, since * we are decreasing the number of clusters allocated to the file. */ return ((int64_t)oldSize - (int64_t)newSize); } static struct pcdir * updateOrphanedChainMetadata(int fd, struct pcdir *dp, int32_t endCluster, int isBad) { struct pcdir *ndp = NULL; int64_t remainder; char *newName = NULL; int chosenName; int dir = (dp->pcd_attr & PCA_DIR); /* * If the truncation fails, (which ought not to happen), * there's no need to go any further, we just return * a null value for the new directory entry pointer. */ remainder = truncAtCluster(fd, dp, endCluster); if (remainder < 0) return (ndp); if (!dir && isBad) { /* * Subtract out the bad cluster from the remaining size * We always assume the cluster being deleted from the * file is full size, but that might not be the case * for the last cluster of the file, so that is why * we check for negative remainder value. */ remainder -= TheBIOSParameterBlock.bpb.sectors_per_cluster * TheBIOSParameterBlock.bpb.bytes_per_sector; if (remainder < 0) remainder = 0; } /* * Build a new directory entry for the rest of the chain. * Later, if the user okays it, we'll link this entry into the * root directory. The new entry will start out as a * copy of the truncated entry. */ if ((remainder != 0) && ((newName = nextAvailableCHKName(&chosenName)) != NULL) && ((ndp = newDirEnt(dp)) != NULL)) { if (Verbose) { if (dir) (void) fprintf(stderr, gettext("Orphaned directory chain.\n")); else (void) fprintf(stderr, gettext("Orphaned chain, %u bytes.\n"), (uint32_t)remainder); } if (!dir) updateDirEnt_Size(ndp, (uint32_t)remainder); if (isBad) updateDirEnt_Start(ndp, nextInChain(endCluster)); else updateDirEnt_Start(ndp, endCluster); updateDirEnt_Name(ndp, newName); addEntryToCHKList(chosenName); } return (ndp); } /* * splitChain() * * split a cluster allocation chain into two cluster chains * around a given cluster (problemCluster). This results in two * separate directory entries; the original (dp), and one we hope * to create and return a pointer to to the caller (*newdp). * This second entry is the orphan chain, and it may end up in * the root directory as a FILEnnnn.CHK file. We also return the * starting cluster of the orphan chain to the caller (*orphanStart). */ void splitChain(int fd, struct pcdir *dp, int32_t problemCluster, struct pcdir **newdp, int32_t *orphanStart) { struct pcdir *ndp = NULL; int isBad = isMarkedBad(problemCluster); ndp = updateOrphanedChainMetadata(fd, dp, problemCluster, isBad); *newdp = ndp; clearInUse(problemCluster); if (isBad) { clearOrphan(problemCluster); *orphanStart = nextInChain(problemCluster); orphanChain(fd, *orphanStart, ndp); markBadInFAT(problemCluster); } else { *orphanStart = problemCluster; orphanChain(fd, problemCluster, ndp); } } /* * freeOrphan * * User has requested that an orphaned cluster chain be freed back * into the file area. */ static void freeOrphan(int32_t c) { int32_t n; /* * Free the directory entry we explicitly created for * the orphaned clusters. */ if (InUse[c - FIRST_CLUSTER]->dirent != NULL) free(InUse[c - FIRST_CLUSTER]->dirent); /* * Then mark the clusters themselves as available. */ do { n = nextInChain(c); markFreeInFAT(c); markFree(c); c = n; } while (c != 0); } /* * Rewrite the InUse field for a cluster chain. Can be used on a partial * chain if provided with a stopAtCluster. */ static void redoInUse(int fd, int32_t c, struct pcdir *ndp, int32_t stopAtCluster) { while (c && c != stopAtCluster) { clearInUse(c); (void) markInUse(fd, c, ndp, NULL, 0, VISIBLE, NULL); c = nextInChain(c); } } static struct pcdir * orphanDirEntLookup(int32_t clusterNum) { if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster) return (NULL); if (isInUse(clusterNum)) { return (InUse[clusterNum - FIRST_CLUSTER]->dirent); } else { return (NULL); } } static int32_t orphanSizeLookup(int32_t clusterNum) { /* silent failure for bogus clusters */ if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster) return (-1); if (isInUse(clusterNum)) { return (extractSize(InUse[clusterNum - FIRST_CLUSTER]->dirent)); } else { return (-1); } } /* * linkOrphan * * User has requested that an orphaned cluster chain be brought back * into the file system. So we have to make a new directory entry * in the root directory and point it at the cluster chain. */ static void linkOrphan(int fd, int32_t start) { struct pcdir *newEnt = NULL; struct pcdir *dp; if ((dp = orphanDirEntLookup(start)) != NULL) { newEnt = addRootDirEnt(fd, dp); } else { (void) printf(gettext("Re-link of orphaned chain failed." " Allocation units will remain orphaned.\n")); } /* * A cluster isn't really InUse() unless it is referenced, * so if newEnt is NULL here, we are in effect using markInUse() * to note that the cluster is NOT in use. */ redoInUse(fd, start, newEnt, 0); } /* * relinkCreatedOrphans * * While marking clusters as bad, we can create orphan cluster * chains. Since we were the ones doing the marking, we were able to * keep track of the orphans we created. Now we want to go through * all those chains and either get them back into the file system or * free them depending on the user's input. */ static void relinkCreatedOrphans(int fd) { int32_t c; for (c = FIRST_CLUSTER; c < LastCluster; c++) { if (isMarkedOrphan(c)) { if (OkayToRelink && askAboutRelink(c)) { linkOrphan(fd, c); } else if (askAboutFreeing(c)) { freeOrphan(c); } clearOrphan(c); } } } /* * relinkFATOrphans * * We want to find orphans not represented in the meta-data. * These are chains marked in the FAT as being in use but * not referenced anywhere by any directory entries. * We'll go through the whole FAT and mark the first cluster * in any such chain as an orphan. Then we can just use * the relinkCreatedOrphans routine to get them back into the * file system or free'ed depending on the user's input. */ static void relinkFATOrphans(int fd) { struct pcdir *ndp = NULL; int32_t cc, c, n; int32_t bpc, newSize; char *newName; int chosenName; for (c = FIRST_CLUSTER; c < LastCluster; c++) { if (freeInFAT(c) || badInFAT(c) || reservedInFAT(c) || isInUse(c)) continue; cc = 1; n = c; while (n = nextInChain(n)) cc++; bpc = TheBIOSParameterBlock.bpb.sectors_per_cluster * TheBIOSParameterBlock.bpb.bytes_per_sector; newSize = cc * bpc; if (((newName = nextAvailableCHKName(&chosenName)) != NULL) && ((ndp = newDirEnt(NULL)) != NULL)) { updateDirEnt_Size(ndp, newSize); updateDirEnt_Start(ndp, c); updateDirEnt_Name(ndp, newName); addEntryToCHKList(chosenName); } orphanChain(fd, c, ndp); } relinkCreatedOrphans(fd); } static void relinkOrphans(int fd) { relinkCreatedOrphans(fd); relinkFATOrphans(fd); } static void checkForFATLoop(int32_t clusterNum) { int32_t prev = clusterNum; int32_t follow; if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster) return; follow = nextInChain(clusterNum); while (follow != clusterNum && follow >= FIRST_CLUSTER && follow <= LastCluster) { prev = follow; follow = nextInChain(follow); } if (follow == clusterNum) { /* * We found a loop. Eradicate it by changing * the last cluster in the loop to be last * in the chain instead instead of pointing * back to the first cluster. */ markLastInFAT(prev); } } static void sharedChainError(int fd, int32_t clusterNum, struct pcdir *badEntry) { /* * If we have shared clusters, it is either because the * cluster somehow got assigned to multiple files and/or * because of a loop in the cluster chain. In either * case we want to truncate the offending file at the * cluster of contention. Then, we will want to run * through the remainder of the chain. If we find ourselves * back at the top, we will know there is a loop in the * FAT we need to remove. */ if (Verbose) (void) fprintf(stderr, gettext("Truncating chain due to duplicate allocation of " "unit %d.\n"), clusterNum); /* * Note that we don't orphan anything here, because the duplicate * part of the chain may be part of another valid chain. */ (void) truncAtCluster(fd, badEntry, clusterNum); checkForFATLoop(clusterNum); } void truncChainWithBadCluster(int fd, struct pcdir *dp, int32_t startCluster) { struct pcdir *orphanEntry; int32_t orphanStartCluster; int32_t c = startCluster; while (c != 0) { if (isMarkedBad(c)) { /* * splitChain() truncates the current guy and * then makes an orphan chain out of the remaining * clusters. When we come back from the split * we'll want to continue looking for bad clusters * in the orphan chain. */ splitChain(fd, dp, c, &orphanEntry, &orphanStartCluster); /* * There is a chance that we weren't able or weren't * required to make a directory entry for the * remaining clusters. In that case we won't go * on, because we couldn't make any more splits * anyway. */ if (orphanEntry == NULL) break; c = orphanStartCluster; dp = orphanEntry; continue; } c = nextInChain(c); } } int32_t nextInChain(int32_t currentCluster) { int32_t nextCluster; /* silent failure for bogus clusters */ if (currentCluster < FIRST_CLUSTER || currentCluster > LastCluster) return (0); /* * Look up FAT entry of next link in cluster chain, * if this one is the last one return 0 as the next link. */ nextCluster = readFATEntry(currentCluster); if (nextCluster < FIRST_CLUSTER || nextCluster > LastCluster) return (0); return (nextCluster); } /* * findImpactedCluster * * Called when someone modifies what they believe might be a cached * cluster entry, but when they only have a directory entry pointer * and not the cluster number. We have to go dig up what cluster * they are modifying. */ int32_t findImpactedCluster(struct pcdir *modified) { CachedCluster *loop; /* * Check to see if it's in the root directory first */ if (!IsFAT32 && ((uchar_t *)modified >= TheRootDir.bytes) && ((uchar_t *)modified < TheRootDir.bytes + RootDirSize)) return (FAKE_ROOTDIR_CLUST); loop = ClusterCache; while (loop) { if (((uchar_t *)modified >= loop->clusterData.bytes) && ((uchar_t *)modified < (loop->clusterData.bytes + BytesPerCluster))) { return (loop->clusterNum); } loop = loop->next; } /* * Guess it wasn't cached after all... */ return (0); } void writeClusterMods(int fd) { CachedCluster *loop = ClusterCache; while (loop) { if (loop->modified) writeCachedCluster(fd, loop); loop = loop->next; } } void squirrelPath(struct nameinfo *pathInfo, int32_t clusterNum) { /* silent failure for bogus clusters */ if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster) return; if (InUse[clusterNum - FIRST_CLUSTER] == NULL) return; InUse[clusterNum - FIRST_CLUSTER]->path = pathInfo; } int markInUse(int fd, int32_t clusterNum, struct pcdir *referencer, struct pcdir *longRef, int32_t longStartCluster, int isHiddenFile, ClusterInfo **template) { int alreadyMarked; ClusterInfo *cl; /* silent failure for bogus clusters */ if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster) return (CLINFO_NEWLY_ALLOCED); alreadyMarked = allocInUse(clusterNum, template); if ((alreadyMarked == CLINFO_PREVIOUSLY_ALLOCED) && (isInUse(clusterNum))) { sharedChainError(fd, clusterNum, referencer); return (CLINFO_PREVIOUSLY_ALLOCED); } cl = InUse[clusterNum - FIRST_CLUSTER]; /* * If Cl is newly allocated (refcnt <= 1) we must fill in the fields. * If Cl has different fields, we must clone it. */ if (cl->refcnt <= 1 || cl->dirent != referencer || cl->longent != longRef || cl->longEntStartClust != longStartCluster) { if (cl->refcnt > 1) cl = cloneClusterInfo(clusterNum); cl->dirent = referencer; cl->longent = longRef; cl->longEntStartClust = longStartCluster; if (isHiddenFile) cl->flags |= CLINFO_HIDDEN; /* * Return cl as the template to use for other clusters in * this file */ if (template) *template = cl; } return (CLINFO_NEWLY_ALLOCED); } void markClusterModified(int32_t clusterNum) { CachedCluster *c; if (clusterNum == FAKE_ROOTDIR_CLUST) { RootDirModified = 1; return; } /* silent failure for bogus clusters */ if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster) return; if (c = findClusterCacheEntry(clusterNum)) { c->modified = 1; } else { (void) fprintf(stderr, gettext("Unexpected internal error: " "Missing cache entry [%d]\n"), clusterNum); exit(10); } } /* * readCluster * caller wants to read cluster clusterNum. We should return * a pointer to the read data in "data", and fill in the number * of bytes read in "datasize". If shouldCache is non-zero * we should allocate cache space to the cluster, otherwise we * just return a pointer to a buffer we re-use whenever cacheing * is not requested. */ int readCluster(int fd, int32_t clusterNum, uchar_t **data, int32_t *datasize, int shouldCache) { uchar_t *newBuf; int rv; *data = NULL; if ((*data = findClusterDataInTheCache(clusterNum)) != NULL) { *datasize = BytesPerCluster; return (RDCLUST_GOOD); } rv = getCluster(fd, clusterNum, &newBuf, datasize); if (rv != RDCLUST_GOOD) return (rv); /* * Caller requested we NOT cache the data from this read. * So, we just return a pointer to the common data buffer. */ if (shouldCache == 0) { *data = newBuf; return (rv); } /* * Caller requested we cache the data from this read. * So, if we have some data, add it to the cache by * copying it out of the common buffer into new storage. */ if (*datasize > 0) *data = addToCache(clusterNum, newBuf, datasize); return (rv); } void findBadClusters(int fd) { int32_t clusterCount; int32_t datasize; uchar_t *data; BadClusterCount = 0; makeUseTable(); (void) printf(gettext("** Scanning allocation units\n")); for (clusterCount = FIRST_CLUSTER; clusterCount < LastCluster; clusterCount++) { if (readCluster(fd, clusterCount, &data, &datasize, RDCLUST_DONT_CACHE) < 0) { if (Verbose) (void) fprintf(stderr, gettext( "\nUnreadable allocation unit %d.\n"), clusterCount); markBad(clusterCount, data, datasize); } /* * Progress meter, display a '.' for every 1000 clusters * processed. We don't want to display this when * we are in verbose mode; verbose mode progress is * shown by displaying each file name as it is found. */ if (!Verbose && clusterCount % 1000 == 0) (void) printf("."); } (void) printf(gettext("..done\n")); } void scanAndFixMetadata(int fd) { /* * First we initialize a few things. */ makeUseTable(); getReadyToSearch(fd); createCHKNameList(fd); /* * Make initial scan, taking into account any effect that * the bad clusters we may have already discovered have * on meta-data. We may break up some cluster chains * during this period. The relinkCreatedOrphans() call * will then give the user the chance to recover stuff * we've created. */ (void) printf(gettext("** Scanning file system meta-data\n")); summarize(fd, NO_FAT_IN_SUMMARY); if (Verbose) printSummary(stderr); (void) printf(gettext("** Correcting any meta-data discrepancies\n")); relinkCreatedOrphans(fd); /* * Clear our usage table and go back over everything, this * time including looking for clusters floating free in the FAT. * This may include clusters the user chose to free during the * relink phase. */ makeUseTable(); summarize(fd, INCLUDE_FAT_IN_SUMMARY); relinkOrphans(fd); } void printSummary(FILE *outDest) { (void) fprintf(outDest, gettext("%llu bytes.\n"), (uint64_t) TotalClusters * TheBIOSParameterBlock.bpb.sectors_per_cluster * TheBIOSParameterBlock.bpb.bytes_per_sector); (void) fprintf(outDest, gettext("%llu bytes in bad sectors.\n"), (uint64_t) BadClusterCount * TheBIOSParameterBlock.bpb.sectors_per_cluster * TheBIOSParameterBlock.bpb.bytes_per_sector); (void) fprintf(outDest, gettext("%llu bytes in %d directories.\n"), (uint64_t) DirClusterCount * TheBIOSParameterBlock.bpb.sectors_per_cluster * TheBIOSParameterBlock.bpb.bytes_per_sector, DirCount); if (HiddenClusterCount) { (void) fprintf(outDest, gettext("%llu bytes in %d hidden files.\n"), (uint64_t)HiddenClusterCount * TheBIOSParameterBlock.bpb.sectors_per_cluster * TheBIOSParameterBlock.bpb.bytes_per_sector, HiddenFileCount); } (void) fprintf(outDest, gettext("%llu bytes in %d files.\n"), (uint64_t) FileClusterCount * TheBIOSParameterBlock.bpb.sectors_per_cluster * TheBIOSParameterBlock.bpb.bytes_per_sector, FileCount); (void) fprintf(outDest, gettext("%llu bytes free.\n"), (uint64_t)FreeClusterCount * TheBIOSParameterBlock.bpb.sectors_per_cluster * TheBIOSParameterBlock.bpb.bytes_per_sector); (void) fprintf(outDest, gettext("%d bytes per allocation unit.\n"), TheBIOSParameterBlock.bpb.sectors_per_cluster * TheBIOSParameterBlock.bpb.bytes_per_sector); (void) fprintf(outDest, gettext("%d total allocation units.\n"), TotalClusters); if (ReservedClusterCount) (void) fprintf(outDest, gettext("%d reserved allocation units.\n"), ReservedClusterCount); (void) fprintf(outDest, gettext("%d available allocation units.\n"), FreeClusterCount); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * fsck_pcfs -- routines for manipulating directories. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pcfs_common.h" #include "fsck_pcfs.h" extern int32_t HiddenClusterCount; extern int32_t FileClusterCount; extern int32_t DirClusterCount; extern int32_t HiddenFileCount; extern int32_t LastCluster; extern int32_t FileCount; extern int32_t BadCount; extern int32_t DirCount; extern int32_t FATSize; extern off64_t PartitionOffset; extern bpb_t TheBIOSParameterBlock; extern int ReadOnly; extern int IsFAT32; extern int Verbose; static uchar_t *CHKsList = NULL; ClusterContents TheRootDir; int32_t RootDirSize; int RootDirModified; int OkayToRelink = 1; /* * We have a bunch of routines for handling CHK names. A CHK name is * simply a file name of the form "FILEnnnn.CHK", where the n's are the * digits in the numbers from 1 to 9999. There are always four digits * used, leading zeros are added as necessary. * * We use CHK names to link orphaned cluster chains back into the file * system's root directory under an auspicious name so that the user * may be able to recover some of their data. * * We use these routines to ensure CHK names we use don't conflict * with any already present in the file system. */ static int hasCHKName(struct pcdir *dp) { return (dp->pcd_filename[CHKNAME_F] == 'F' && dp->pcd_filename[CHKNAME_I] == 'I' && dp->pcd_filename[CHKNAME_L] == 'L' && dp->pcd_filename[CHKNAME_E] == 'E' && isdigit(dp->pcd_filename[CHKNAME_THOUSANDS]) && isdigit(dp->pcd_filename[CHKNAME_HUNDREDS]) && isdigit(dp->pcd_filename[CHKNAME_TENS]) && isdigit(dp->pcd_filename[CHKNAME_ONES]) && dp->pcd_ext[CHKNAME_C] == 'C' && dp->pcd_ext[CHKNAME_H] == 'H' && dp->pcd_ext[CHKNAME_K] == 'K'); } void addEntryToCHKList(int chkNumber) { /* silent failure on bogus value */ if (chkNumber < 0 || chkNumber > MAXCHKVAL) return; CHKsList[chkNumber / NBBY] |= (1 << (chkNumber % NBBY)); } static void addToCHKList(struct pcdir *dp) { int chknum; chknum = 1000 * (dp->pcd_filename[CHKNAME_THOUSANDS] - '0'); chknum += 100 * (dp->pcd_filename[CHKNAME_HUNDREDS] - '0'); chknum += 10 * (dp->pcd_filename[CHKNAME_TENS] - '0'); chknum += (dp->pcd_filename[CHKNAME_ONES] - '0'); addEntryToCHKList(chknum); } static int inUseCHKName(int chkNumber) { return (CHKsList[chkNumber / NBBY] & (1 << (chkNumber % NBBY))); } static void appendToPath(struct pcdir *dp, char *thePath, int *theLen) { int i = 0; /* * Sometimes caller doesn't care about keeping track of the path */ if (thePath == NULL) return; /* * Prepend / */ if (*theLen < MAXPATHLEN) *(thePath + (*theLen)++) = '/'; /* * Print out the file name part, but only up to the first * space. */ while (*theLen < MAXPATHLEN && i < PCFNAMESIZE) { /* * When we start seeing spaces we assume that's the * end of the interesting characters in the name. */ if ((dp->pcd_filename[i] == ' ') || !(pc_validchar(dp->pcd_filename[i]))) break; *(thePath + (*theLen)++) = dp->pcd_filename[i++]; } /* * Leave now, if we don't have an extension (or room for one) */ if ((dp->pcd_ext[i] == ' ') || ((*theLen) >= MAXPATHLEN) || (!(pc_validchar(dp->pcd_ext[i])))) return; /* * Tack on the extension */ *(thePath + (*theLen)++) = '.'; i = 0; while ((*theLen < MAXPATHLEN) && (i < PCFEXTSIZE)) { if ((dp->pcd_ext[i] == ' ') || !(pc_validchar(dp->pcd_ext[i]))) break; *(thePath + (*theLen)++) = dp->pcd_ext[i++]; } } static void printName(FILE *outDest, struct pcdir *dp) { int i; for (i = 0; i < PCFNAMESIZE; i++) { if ((dp->pcd_filename[i] == ' ') || !(pc_validchar(dp->pcd_filename[i]))) break; (void) fprintf(outDest, "%c", dp->pcd_filename[i]); } (void) fprintf(outDest, "."); for (i = 0; i < PCFEXTSIZE; i++) { if (!(pc_validchar(dp->pcd_ext[i]))) break; (void) fprintf(outDest, "%c", dp->pcd_ext[i]); } } /* * sanityCheckSize * Make sure the size in the directory entry matches what is * actually allocated. If there is a mismatch, orphan all * the allocated clusters. Returns SIZE_MATCHED if everything matches * up, TRUNCATED to indicate truncation was necessary. */ static int sanityCheckSize(int fd, struct pcdir *dp, int32_t actualClusterCount, int isDir, int32_t startCluster, struct nameinfo *fullPathName, struct pcdir **orphanEntry) { uint32_t sizeFromDir; int32_t ignorei = 0; int64_t bpc; bpc = TheBIOSParameterBlock.bpb.sectors_per_cluster * TheBIOSParameterBlock.bpb.bytes_per_sector; sizeFromDir = extractSize(dp); if (isDir) { if (sizeFromDir == 0) return (SIZE_MATCHED); } else { if ((sizeFromDir > ((actualClusterCount - 1) * bpc)) && (sizeFromDir <= (actualClusterCount * bpc))) return (SIZE_MATCHED); } if (fullPathName != NULL) { fullPathName->references++; (void) fprintf(stderr, "%s\n", fullPathName->fullName); } squirrelPath(fullPathName, startCluster); (void) fprintf(stderr, gettext("Truncating chain due to incorrect size " "in directory. Size from directory = %u bytes,\n"), sizeFromDir); if (actualClusterCount == 0) { (void) fprintf(stderr, gettext("Zero bytes are allocated to the file.\n")); } else { (void) fprintf(stderr, gettext("Allocated size in range %llu - %llu bytes.\n"), ((actualClusterCount - 1) * bpc) + 1, (actualClusterCount * bpc)); } /* * Use splitChain() to make an orphan that is the entire allocation * chain. */ splitChain(fd, dp, startCluster, orphanEntry, &ignorei); return (TRUNCATED); } static int noteUsage(int fd, int32_t startAt, struct pcdir *dp, struct pcdir *lp, int32_t longEntryStartCluster, int isHidden, int isDir, struct nameinfo *fullPathName) { struct pcdir *orphanEntry; int32_t chain = startAt; int32_t count = 0; int savePathNextIteration = 0; int haveBad = 0; ClusterInfo *tmpl = NULL; while ((chain >= FIRST_CLUSTER) && (chain <= LastCluster)) { if ((markInUse(fd, chain, dp, lp, longEntryStartCluster, isHidden ? HIDDEN : VISIBLE, &tmpl)) != CLINFO_NEWLY_ALLOCED) break; count++; if (savePathNextIteration == 1) { savePathNextIteration = 0; if (fullPathName != NULL) fullPathName->references++; squirrelPath(fullPathName, chain); } if (isMarkedBad(chain)) { haveBad = 1; savePathNextIteration = 1; } if (isHidden) HiddenClusterCount++; else if (isDir) DirClusterCount++; else FileClusterCount++; chain = nextInChain(chain); } /* * Do a sanity check on the file size in the directory entry. * This may create an orphaned cluster chain. */ if (sanityCheckSize(fd, dp, count, isDir, startAt, fullPathName, &orphanEntry) == TRUNCATED) { /* * The pre-existing directory entry has been truncated, * so the chain associated with it no longer has any * bad clusters. Instead, the new orphan has them. */ if (haveBad > 0) { truncChainWithBadCluster(fd, orphanEntry, startAt); } haveBad = 0; } return (haveBad); } static void storeInfoAboutEntry(int fd, struct pcdir *dp, struct pcdir *ldp, int depth, int32_t longEntryStartCluster, char *fullPath, int *fullLen) { struct nameinfo *pathCopy; int32_t start; int haveBad; int hidden = (dp->pcd_attr & PCA_HIDDEN || dp->pcd_attr & PCA_SYSTEM); int dir = (dp->pcd_attr & PCA_DIR); int i; if (hidden) HiddenFileCount++; else if (dir) DirCount++; else FileCount++; appendToPath(dp, fullPath, fullLen); /* * Make a copy of the name at this point. We may want it to * note the original source of an orphaned cluster. */ if ((pathCopy = (struct nameinfo *)malloc(sizeof (struct nameinfo))) != NULL) { if ((pathCopy->fullName = (char *)malloc(*fullLen + 1)) != NULL) { pathCopy->references = 0; (void) strncpy(pathCopy->fullName, fullPath, *fullLen); pathCopy->fullName[*fullLen] = '\0'; } else { free(pathCopy); pathCopy = NULL; } } if (Verbose) { for (i = 0; i < depth; i++) (void) fprintf(stderr, " "); if (hidden) (void) fprintf(stderr, "["); else if (dir) (void) fprintf(stderr, "|_"); else (void) fprintf(stderr, gettext("(%06d) "), FileCount); printName(stderr, dp); if (hidden) (void) fprintf(stderr, "]"); (void) fprintf(stderr, gettext(", %u bytes, start cluster %d"), extractSize(dp), extractStartCluster(dp)); (void) fprintf(stderr, "\n"); } start = extractStartCluster(dp); haveBad = noteUsage(fd, start, dp, ldp, longEntryStartCluster, hidden, dir, pathCopy); if (haveBad > 0) { if (dir && pathCopy->fullName != NULL) { (void) fprintf(stderr, gettext("Adjusting for bad allocation units in " "the meta-data of:\n ")); (void) fprintf(stderr, pathCopy->fullName); (void) fprintf(stderr, "\n"); } truncChainWithBadCluster(fd, dp, start); } if ((pathCopy != NULL) && (pathCopy->references == 0)) { free(pathCopy->fullName); free(pathCopy); } } static void storeInfoAboutLabel(struct pcdir *dp) { /* * XXX eventually depth should be passed to this routine just * as it is with storeInfoAboutEntry(). If it isn't zero, then * we've got a bogus directory entry. */ if (Verbose) { (void) fprintf(stderr, gettext("** ")); printName(stderr, dp); (void) fprintf(stderr, gettext(" **\n")); } } static void searchChecks(struct pcdir *dp, int operation, char matchRequired, struct pcdir **found) { /* * We support these searching operations: * * PCFS_FIND_ATTR * look for the first file with a certain attribute * (e.g, find all hidden files) * PCFS_FIND_STATUS * look for the first file with a certain status * (e.g., the file has been marked deleted; making * its directory entry reusable) * PCFS_FIND_CHKS * look for all files with short names of the form * FILENNNN.CHK. These are the file names we give * to chains of orphaned clusters we relink into the * file system. This find facility allows us to seek * out all existing files of this naming form so that * we may create unique file names for new orphans. */ if (operation == PCFS_FIND_ATTR && dp->pcd_attr == matchRequired) { *found = dp; } else if (operation == PCFS_FIND_STATUS && dp->pcd_filename[0] == matchRequired) { *found = dp; } else if (operation == PCFS_FIND_CHKS && hasCHKName(dp)) { addToCHKList(dp); } } static void catalogEntry(int fd, struct pcdir *dp, struct pcdir *longdp, int32_t currentCluster, int depth, char *recordPath, int *pathLen) { if (dp->pcd_attr & PCA_LABEL) { storeInfoAboutLabel(dp); } else { storeInfoAboutEntry(fd, dp, longdp, depth, currentCluster, recordPath, pathLen); } } /* * visitNodes() * * This is the main workhouse routine for traversing pcfs metadata. * There isn't a lot to the metadata. Basically there is a root * directory somewhere (either in its own special place outside the * data area or in a data cluster). The root directory (and all other * directories) are filled with a number of fixed size entries. An * entry has the filename and extension, the file's attributes, the * file's size, and the starting data cluster of the storage allocated * to the file. To determine which clusters are assigned to the file, * you start at the starting cluster entry in the FAT, and follow the * chain of entries in the FAT. * * Arguments are: * fd * descriptor for accessing the raw file system data * currentCluster * original caller supplies the initial starting cluster, * subsequent recursive calls are made with updated * cluster numbers for the sub-directories. * dirData * pointer to the directory data bytes * dirDataLen * size of the whole buffer of data bytes (usually it is * the size of a cluster, but the root directory on * FAT12/16 is not necessarily the same size as a cluster). * depth * original caller should set it to zero (assuming they are * starting from the root directory). This number is used to * change the indentation of file names presented as debug info. * descend * boolean indicates if we should descend into subdirectories. * operation * what, if any, matching should be performed. * The PCFS_TRAVERSE_ALL operation is a depth first traversal * of all nodes in the metadata tree, that tracks all the * clusters in use (according to the meta-data, at least) * matchRequired * value to be matched (if any) * found * output parameter * used to return pointer to a directory entry that matches * the search requirement * original caller should pass in a pointer to a NULL pointer. * lastDirCluster * output parameter * if no match found, last cluster num of starting directory * dirEnd * output parameter * if no match found, return parameter stores pointer to where * new directory entry could be appended to existing directory * recordPath * output parameter * as files are discovered, and directories traversed, this * buffer is used to store the current full path name. * pathLen * output parameter * this is in the integer length of the current full path name. */ static void visitNodes(int fd, int32_t currentCluster, ClusterContents *dirData, int32_t dirDataLen, int depth, int descend, int operation, char matchRequired, struct pcdir **found, int32_t *lastDirCluster, struct pcdir **dirEnd, char *recordPath, int *pathLen) { struct pcdir *longdp = NULL; struct pcdir *dp; int32_t longStart; int withinLongName = 0; int saveLen = *pathLen; dp = dirData->dirp; /* * A directory entry where the first character of the name is * PCD_UNUSED indicates the end of the directory. */ while ((uchar_t *)dp < dirData->bytes + dirDataLen && dp->pcd_filename[0] != PCD_UNUSED) { /* * Handle the special case find operations. */ searchChecks(dp, operation, matchRequired, found); if (*found) break; /* * Are we looking at part of a long file name entry? * If so, we may need to note the start of the name. * We don't do any further processing of long file * name entries. * * We also skip deleted entries and the '.' and '..' * entries. */ if ((dp->pcd_attr & PCDL_LFN_BITS) == PCDL_LFN_BITS) { if (!withinLongName) { withinLongName++; longStart = currentCluster; longdp = dp; } dp++; continue; } else if ((dp->pcd_filename[0] == PCD_ERASED) || (dp->pcd_filename[0] == '.')) { /* * XXX - if we were within a long name, then * its existence is bogus, because it is not * attached to any real file. */ withinLongName = 0; dp++; continue; } withinLongName = 0; if (operation == PCFS_TRAVERSE_ALL) catalogEntry(fd, dp, longdp, longStart, depth, recordPath, pathLen); longdp = NULL; longStart = 0; if (dp->pcd_attr & PCA_DIR && descend == PCFS_VISIT_SUBDIRS) { traverseDir(fd, extractStartCluster(dp), depth + 1, descend, operation, matchRequired, found, lastDirCluster, dirEnd, recordPath, pathLen); if (*found) break; } dp++; *pathLen = saveLen; } if (*found) return; if ((uchar_t *)dp < dirData->bytes + dirDataLen) { /* * We reached the end of directory before the end of * our provided data (a cluster). That means this cluster * is the last one in this directory's chain. It also * means we've just looked at the last directory entry. */ *lastDirCluster = currentCluster; *dirEnd = dp; return; } /* * If there is more to the directory we'll go get it otherwise we * are done traversing this directory. */ if ((currentCluster == FAKE_ROOTDIR_CLUST) || (lastInFAT(currentCluster))) { *lastDirCluster = currentCluster; return; } else { traverseDir(fd, nextInChain(currentCluster), depth, descend, operation, matchRequired, found, lastDirCluster, dirEnd, recordPath, pathLen); *pathLen = saveLen; } } /* * traverseFromRoot() * For use with 12 and 16 bit FATs that have a root directory outside * of the file system. This is a general purpose routine that * can be used simply to visit all of the nodes in the metadata or * to find the first instance of something, e.g., the first directory * entry where the file is marked deleted. * * Inputs are described in the commentary for visitNodes() above. */ void traverseFromRoot(int fd, int depth, int descend, int operation, char matchRequired, struct pcdir **found, int32_t *lastDirCluster, struct pcdir **dirEnd, char *recordPath, int *pathLen) { visitNodes(fd, FAKE_ROOTDIR_CLUST, &TheRootDir, RootDirSize, depth, descend, operation, matchRequired, found, lastDirCluster, dirEnd, recordPath, pathLen); } /* * traverseDir() * For use with all FATs outside of the initial root directory on * 12 and 16 bit FAT file systems. This is a general purpose routine * that can be used simply to visit all of the nodes in the metadata or * to find the first instance of something, e.g., the first directory * entry where the file is marked deleted. * * Unique Input is: * startAt * starting cluster of the directory * * This is the cluster that is the first one in this directory. * We read it right away, so we can provide it as data to visitNodes(). * Note that we cache this cluster as we read it, because it is * metadata and we cache all metadata. By doing so, we can * keep pointers to directory entries for quickly moving around and * fixing up any problems we find. Of course if we get a big * filesystem with a huge amount of metadata we may be hosed, as * we'll likely run out of memory. * * I believe in the future this will have to be addressed. It * may be possible to do more of the processing of problems * within directories as they are cached, so that when memory * runs short we can free cached directories we are already * finished visiting. * * The remainder of inputs are described in visitNodes() comments. */ void traverseDir(int fd, int32_t startAt, int depth, int descend, int operation, char matchRequired, struct pcdir **found, int32_t *lastDirCluster, struct pcdir **dirEnd, char *recordPath, int *pathLen) { ClusterContents dirdata; int32_t dirdatasize = 0; if (startAt < FIRST_CLUSTER || startAt > LastCluster) return; if (readCluster(fd, startAt, &(dirdata.bytes), &dirdatasize, RDCLUST_DO_CACHE) != RDCLUST_GOOD) { (void) fprintf(stderr, gettext("Unable to get more directory entries!\n")); return; } if (operation == PCFS_TRAVERSE_ALL) { if (Verbose) (void) fprintf(stderr, gettext("Directory traversal enters " "allocation unit %d.\n"), startAt); } visitNodes(fd, startAt, &dirdata, dirdatasize, depth, descend, operation, matchRequired, found, lastDirCluster, dirEnd, recordPath, pathLen); } void createCHKNameList(int fd) { struct pcdir *ignorep1, *ignorep2; int32_t ignore32; char *ignorecp = NULL; char ignore = '\0'; int ignoreint = 0; ignorep1 = ignorep2 = NULL; if (!OkayToRelink || CHKsList != NULL) return; /* * Allocate an array to keep a bit map of the integer * values used in CHK names. */ if ((CHKsList = (uchar_t *)calloc(1, idivceil(MAXCHKVAL, NBBY))) == NULL) { OkayToRelink = 0; return; } /* * Search the root directory for all the files with names of * the form FILEXXXX.CHK. The root directory is an area * outside of the file space on FAT12 and FAT16 file systems. * On FAT32 file systems, the root directory is in a file * area cluster just like any other directory. */ if (!IsFAT32) { traverseFromRoot(fd, 0, PCFS_NO_SUBDIRS, PCFS_FIND_CHKS, ignore, &ignorep1, &ignore32, &ignorep2, ignorecp, &ignoreint); } else { DirCount++; traverseDir(fd, TheBIOSParameterBlock.bpb32.root_dir_clust, 0, PCFS_NO_SUBDIRS, PCFS_FIND_CHKS, ignore, &ignorep1, &ignore32, &ignorep2, ignorecp, &ignoreint); } } char * nextAvailableCHKName(int *chosen) { static char nameBuf[PCFNAMESIZE]; int i; if (!OkayToRelink) return (NULL); nameBuf[CHKNAME_F] = 'F'; nameBuf[CHKNAME_I] = 'I'; nameBuf[CHKNAME_L] = 'L'; nameBuf[CHKNAME_E] = 'E'; for (i = 1; i <= MAXCHKVAL; i++) { if (!inUseCHKName(i)) break; } if (i <= MAXCHKVAL) { nameBuf[CHKNAME_THOUSANDS] = '0' + (i / 1000); nameBuf[CHKNAME_HUNDREDS] = '0' + ((i % 1000) / 100); nameBuf[CHKNAME_TENS] = '0' + ((i % 100) / 10); nameBuf[CHKNAME_ONES] = '0' + (i % 10); *chosen = i; return (nameBuf); } else { (void) fprintf(stderr, gettext("Sorry, no names available for " "relinking orphan chains!\n")); OkayToRelink = 0; return (NULL); } } uint32_t extractSize(struct pcdir *dp) { uint32_t returnMe; read_32_bits((uchar_t *)&(dp->pcd_size), &returnMe); return (returnMe); } int32_t extractStartCluster(struct pcdir *dp) { uint32_t lo, hi; if (IsFAT32) { read_16_bits((uchar_t *)&(dp->un.pcd_scluster_hi), &hi); read_16_bits((uchar_t *)&(dp->pcd_scluster_lo), &lo); return ((int32_t)((hi << 16) | lo)); } else { read_16_bits((uchar_t *)&(dp->pcd_scluster_lo), &lo); return ((int32_t)lo); } } static struct pcdir * findAvailableRootDirEntSlot(int fd, int32_t *clusterWithSlot) { struct pcdir *deletedEntry = NULL; struct pcdir *appendPoint = NULL; char *ignorecp = NULL; int ignore = 0; *clusterWithSlot = 0; /* * First off, try to find an erased entry in the root * directory. The root directory is an area outside of the * file space on FAT12 and FAT16 file systems. On FAT32 file * systems, the root directory is in a file area cluster just * like any other directory. */ if (!IsFAT32) { traverseFromRoot(fd, 0, PCFS_NO_SUBDIRS, PCFS_FIND_STATUS, PCD_ERASED, &deletedEntry, clusterWithSlot, &appendPoint, ignorecp, &ignore); } else { DirCount++; traverseDir(fd, TheBIOSParameterBlock.bpb32.root_dir_clust, 0, PCFS_NO_SUBDIRS, PCFS_FIND_STATUS, PCD_ERASED, &deletedEntry, clusterWithSlot, &appendPoint, ignorecp, &ignore); } /* * If we found a deleted file in the directory we'll overwrite * that entry. */ if (deletedEntry) return (deletedEntry); /* * If there is room at the end of the existing directory, we * should place the new entry there. */ if (appendPoint) return (appendPoint); /* * XXX need to grow the directory */ return (NULL); } static void insertDirEnt(struct pcdir *slot, struct pcdir *entry, int32_t clusterWithSlot) { (void) memcpy(slot, entry, sizeof (struct pcdir)); markClusterModified(clusterWithSlot); } /* * Convert current UNIX time into a PCFS timestamp (which is in local time). * * Since the "seconds" field of that is only accurate to 2sec precision, * we allow for the optional (used only for creation times on FAT) "msec" * parameter that takes the fractional part. */ static void getNow(struct pctime *pctp, uchar_t *msec) { time_t now; struct tm tm; ushort_t tim, dat; /* * Disable daylight savings corrections - Solaris PCFS doesn't * support such conversions yet. Save timestamps in local time. */ daylight = 0; (void) time(&now); (void) localtime_r(&now, &tm); dat = (tm.tm_year - 80) << YEARSHIFT; dat |= tm.tm_mon << MONSHIFT; dat |= tm.tm_mday << DAYSHIFT; tim = tm.tm_hour << HOURSHIFT; tim |= tm.tm_min << MINSHIFT; tim |= (tm.tm_sec / 2) << SECSHIFT; /* * Sanity check. If we overflow the PCFS timestamp range * we set the time to 01/01/1980, 00:00:00 */ if (dat < 80 || dat > 227) dat = tim = 0; pctp->pct_date = LE_16(dat); pctp->pct_time = LE_16(tim); if (msec) *msec = (tm.tm_sec & 1) ? 100 : 0; } /* * FAT file systems store the following time information in a directory * entry: * timestamp member of "struct pcdir" * ====================================================================== * creation time pcd_crtime.pct_time * creation date pcd_crtime.pct_date * last access date pcd_ladate * last modify time pcd_mtime.pct_time * last modify date pcd_mtime.pct_date * * No access time is kept. */ static void updateDirEnt_CreatTime(struct pcdir *dp) { getNow(&dp->pcd_crtime, &dp->pcd_crtime_msec); markClusterModified(findImpactedCluster(dp)); } static void updateDirEnt_ModTimes(struct pcdir *dp) { timestruc_t ts; getNow(&dp->pcd_mtime, NULL); dp->pcd_ladate = dp->pcd_mtime.pct_date; dp->pcd_attr |= PCA_ARCH; markClusterModified(findImpactedCluster(dp)); } struct pcdir * addRootDirEnt(int fd, struct pcdir *new) { struct pcdir *added; int32_t inCluster; if ((added = findAvailableRootDirEntSlot(fd, &inCluster)) != NULL) { insertDirEnt(added, new, inCluster); return (added); } return (NULL); } /* * FAT12 and FAT16 have a root directory outside the normal file space, * so we have separate routines for finding and reading the root directory. */ static off64_t seekRootDirectory(int fd) { off64_t seekto; /* * The RootDir immediately follows the FATs, which in * turn immediately follow the reserved sectors. */ seekto = (off64_t)TheBIOSParameterBlock.bpb.resv_sectors * TheBIOSParameterBlock.bpb.bytes_per_sector + (off64_t)FATSize * TheBIOSParameterBlock.bpb.num_fats + (off64_t)PartitionOffset; if (Verbose) (void) fprintf(stderr, gettext("Seeking root directory @%lld.\n"), seekto); return (lseek64(fd, seekto, SEEK_SET)); } void getRootDirectory(int fd) { ssize_t bytesRead; if (TheRootDir.bytes != NULL) return; else if ((TheRootDir.bytes = (uchar_t *)malloc(RootDirSize)) == NULL) { mountSanityCheckFails(); perror(gettext("No memory for a copy of the root directory")); (void) close(fd); exit(8); } if (seekRootDirectory(fd) < 0) { mountSanityCheckFails(); perror(gettext("Cannot seek to RootDir")); (void) close(fd); exit(8); } if (Verbose) (void) fprintf(stderr, gettext("Reading root directory.\n")); if ((bytesRead = read(fd, TheRootDir.bytes, RootDirSize)) != RootDirSize) { mountSanityCheckFails(); if (bytesRead < 0) { perror(gettext("Cannot read a RootDir")); } else { (void) fprintf(stderr, gettext("Short read of RootDir\n")); } (void) close(fd); exit(8); } if (Verbose) { (void) fprintf(stderr, gettext("Dump of root dir's first 256 bytes.\n")); header_for_dump(); dump_bytes(TheRootDir.bytes, 256); } } void writeRootDirMods(int fd) { ssize_t bytesWritten; if (!TheRootDir.bytes) { (void) fprintf(stderr, gettext("Internal error: No Root directory to write\n")); (void) close(fd); exit(12); } if (!RootDirModified) { if (Verbose) { (void) fprintf(stderr, gettext("No root directory changes need to " "be written.\n")); } return; } if (ReadOnly) return; if (Verbose) (void) fprintf(stderr, gettext("Writing root directory.\n")); if (seekRootDirectory(fd) < 0) { perror(gettext("Cannot write the RootDir (seek failed)")); (void) close(fd); exit(12); } if ((bytesWritten = write(fd, TheRootDir.bytes, RootDirSize)) != RootDirSize) { if (bytesWritten < 0) { perror(gettext("Cannot write the RootDir")); } else { (void) fprintf(stderr, gettext("Short write of root directory\n")); } (void) close(fd); exit(12); } RootDirModified = 0; } struct pcdir * newDirEnt(struct pcdir *copyme) { struct pcdir *ndp; if ((ndp = (struct pcdir *)calloc(1, sizeof (struct pcdir))) == NULL) { (void) fprintf(stderr, gettext("Out of memory to create a " "new directory entry!\n")); return (ndp); } if (copyme) (void) memcpy(ndp, copyme, sizeof (struct pcdir)); ndp->pcd_ext[CHKNAME_C] = 'C'; ndp->pcd_ext[CHKNAME_H] = 'H'; ndp->pcd_ext[CHKNAME_K] = 'K'; updateDirEnt_CreatTime(ndp); updateDirEnt_ModTimes(ndp); return (ndp); } void updateDirEnt_Size(struct pcdir *dp, uint32_t newSize) { uchar_t *p = (uchar_t *)&(dp->pcd_size); store_32_bits(&p, newSize); markClusterModified(findImpactedCluster(dp)); } void updateDirEnt_Start(struct pcdir *dp, int32_t newStart) { uchar_t *p = (uchar_t *)&(dp->pcd_scluster_lo); store_16_bits(&p, newStart & 0xffff); if (IsFAT32) { p = (uchar_t *)&(dp->un.pcd_scluster_hi); store_16_bits(&p, newStart >> 16); } markClusterModified(findImpactedCluster(dp)); } void updateDirEnt_Name(struct pcdir *dp, char *newName) { int i; for (i = 0; i < PCFNAMESIZE; i++) { if (*newName) dp->pcd_filename[i] = *newName++; else dp->pcd_filename[i] = ' '; } markClusterModified(findImpactedCluster(dp)); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1999 by Sun Microsystems, Inc. * All rights reserved. */ /* * fsck_pcfs -- routines for manipulating the FAT. */ #include #include #include #include #include #include #include #include #include "pcfs_common.h" #include "fsck_pcfs.h" extern int32_t BytesPerCluster; extern int32_t TotalClusters; extern int32_t LastCluster; extern off64_t FirstClusterOffset; extern off64_t PartitionOffset; extern bpb_t TheBIOSParameterBlock; extern int ReadOnly; extern int IsFAT32; extern int Verbose; static uchar_t *TheFAT; static int FATRewriteNeeded = 0; int32_t FATSize; short FATEntrySize; static off64_t seekFAT(int fd) { off64_t seekto; /* * The FAT(s) immediately follows the reserved sectors. */ seekto = TheBIOSParameterBlock.bpb.resv_sectors * TheBIOSParameterBlock.bpb.bytes_per_sector + PartitionOffset; return (lseek64(fd, seekto, SEEK_SET)); } void getFAT(int fd) { ssize_t bytesRead; if (TheFAT != NULL) { return; } else if ((TheFAT = (uchar_t *)malloc(FATSize)) == NULL) { mountSanityCheckFails(); perror(gettext("No memory for a copy of the FAT")); (void) close(fd); exit(7); } if (seekFAT(fd) < 0) { mountSanityCheckFails(); perror(gettext("Cannot seek to FAT")); (void) close(fd); exit(7); } if (Verbose) (void) fprintf(stderr, gettext("Reading FAT\n")); if ((bytesRead = read(fd, TheFAT, FATSize)) != FATSize) { mountSanityCheckFails(); if (bytesRead < 0) { perror(gettext("Cannot read a FAT")); } else { (void) fprintf(stderr, gettext("Short read of FAT.")); } (void) close(fd); exit(7); } /* * XXX - might want to read the other copies of the FAT * for comparison and/or to use if the first one seems hosed. */ if (Verbose) { (void) fprintf(stderr, gettext("Dump of FAT's first 32 bytes.\n")); header_for_dump(); dump_bytes(TheFAT, 32); } } void writeFATMods(int fd) { ssize_t bytesWritten; if (TheFAT == NULL) { (void) fprintf(stderr, gettext("Internal error: No FAT to write\n")); (void) close(fd); exit(11); } if (!FATRewriteNeeded) { if (Verbose) { (void) fprintf(stderr, gettext("No FAT changes need to be written.\n")); } return; } if (ReadOnly) return; if (Verbose) (void) fprintf(stderr, gettext("Writing FAT\n")); if (seekFAT(fd) < 0) { perror(gettext("Cannot seek to FAT")); (void) close(fd); exit(11); } if ((bytesWritten = write(fd, TheFAT, FATSize)) != FATSize) { if (bytesWritten < 0) { perror(gettext("Cannot write FAT")); } else { (void) fprintf(stderr, gettext("Short write of FAT.")); } (void) close(fd); exit(11); } FATRewriteNeeded = 0; } /* * checkFAT32CleanBit() * Return non-zero if the bit indicating proper Windows shutdown has * been set. */ int checkFAT32CleanBit(int fd) { getFAT(fd); return (TheFAT[WIN_SHUTDOWN_STATUS_BYTE] & WIN_SHUTDOWN_BIT_MASK); } static uchar_t * findClusterEntryInFAT(int32_t currentCluster) { int32_t idx; if (FATEntrySize == 32) { idx = currentCluster * 4; } else if (FATEntrySize == 16) { idx = currentCluster * 2; } else { idx = currentCluster + currentCluster/2; } return (TheFAT + idx); } /* * {read,write}FATentry * For the 16 and 32 bit FATs these routines are relatively easy * to follow. * * 12 bit FATs are kind of strange, though. The magic index for * 12 bit FATS computed below, 1.5 * clusterNum, is a * simplification that there are 8 bits in a byte, so you need * 1.5 bytes per entry. * * It's easiest to think about FAT12 entries in pairs: * * --------------------------------------------- * | mid1 | low1 | low2 | high1 | high2 | mid2 | * --------------------------------------------- * * Each box in the diagram represents a nibble (4 bits) of a FAT * entry. A FAT entry is made up of three nibbles. So if you * look closely, you'll see that first byte of the pair of * entries contains the low and middle nibbles of the first * entry. The second byte has the low nibble of the second entry * and the high nibble of the first entry. Those two bytes alone * are enough to read the first entry. The second FAT entry is * finished out by the last nibble pair. */ int32_t readFATEntry(int32_t currentCluster) { int32_t value; uchar_t *ep; ep = findClusterEntryInFAT(currentCluster); if (FATEntrySize == 32) { read_32_bits(ep, (uint32_t *)&value); } else if (FATEntrySize == 16) { read_16_bits(ep, (uint32_t *)&value); /* * Convert 16 bit entry to 32 bit if we are * into the reserved or higher values. */ if (value >= PCF_RESCLUSTER) value |= 0xFFF0000; } else { value = 0; if (currentCluster & 1) { /* * Odd numbered cluster */ value = (((unsigned int)*ep++ & 0xf0) >> 4); value += (*ep << 4); } else { value = *ep++; value += ((*ep & 0x0f) << 8); } /* * Convert 12 bit entry to 32 bit if we are * into the reserved or higher values. */ if (value >= PCF_12BCLUSTER) value |= 0xFFFF000; } return (value); } void writeFATEntry(int32_t currentCluster, int32_t value) { uchar_t *ep; FATRewriteNeeded = 1; ep = findClusterEntryInFAT(currentCluster); if (FATEntrySize == 32) { store_32_bits(&ep, value); } else if (FATEntrySize == 16) { store_16_bits(&ep, value); } else { if (currentCluster & 1) { /* * Odd numbered cluster */ *ep = (*ep & 0x0f) | ((value << 4) & 0xf0); ep++; *ep = (value >> 4) & 0xff; } else { *ep++ = value & 0xff; *ep = (*ep & 0xf0) | ((value >> 8) & 0x0f); } } } /* * reservedInFAT - Is this cluster marked in the reserved range? * The range from PCF_RESCLUSTER32 to PCF_BADCLUSTER32 - 1, * have been reserved by Microsoft. No cluster should be * marked with these; they are effectively invalid cluster values. */ int reservedInFAT(int32_t clusterNum) { int32_t e; e = readFATEntry(clusterNum); return (e >= PCF_RESCLUSTER32 && e < PCF_BADCLUSTER32); } /* * badInFAT - Is this cluster marked as bad? I.e., is it inaccessible? */ int badInFAT(int32_t clusterNum) { return (readFATEntry(clusterNum) == PCF_BADCLUSTER32); } /* * lastInFAT - Is this cluster marked as free? I.e., is it available * for use? */ int freeInFAT(int32_t clusterNum) { return (readFATEntry(clusterNum) == PCF_FREECLUSTER); } /* * lastInFAT - Is this cluster the last in its cluster chain? */ int lastInFAT(int32_t clusterNum) { return (readFATEntry(clusterNum) == PCF_LASTCLUSTER32); } /* * markLastInFAT - Mark this cluster as the last in its cluster chain. */ void markLastInFAT(int32_t clusterNum) { writeFATEntry(clusterNum, PCF_LASTCLUSTER32); } void markFreeInFAT(int32_t clusterNum) { writeFATEntry(clusterNum, PCF_FREECLUSTER); } void markBadInFAT(int32_t clusterNum) { writeFATEntry(clusterNum, PCF_BADCLUSTER32); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1999,2001 by Sun Microsystems, Inc. * All rights reserved. * Copyright 2024 MNX Cloud, Inc. */ /* * fsck_pcfs -- main routines. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "getresponse.h" #include "pcfs_common.h" #include "fsck_pcfs.h" #include "pcfs_bpb.h" size_t bpsec = MINBPS; int32_t BytesPerCluster; int32_t TotalClusters; int32_t LastCluster; off64_t FirstClusterOffset; off64_t PartitionOffset; bpb_t TheBIOSParameterBlock; /* * {Output,Input}Image are the file names where we should write the * checked fs image and from which we should read the initial fs. * The image capability is designed for debugging purposes. */ static char *OutputImage = NULL; static char *InputImage = NULL; static int WritableOnly = 0; /* -o w, check writable fs' only */ static int Mflag = 0; /* -m, sanity check if fs is mountable */ static int Preen = 0; /* -o p, preen; non-interactive */ /* * By default be quick; skip verify reads. * If the user wants more exhaustive checking, * they should run with the -o v option. */ static int Quick = 1; int ReadOnly = 0; int IsFAT32 = 0; int Verbose = 0; bool AlwaysYes = false; /* -y or -Y, assume a yes answer to all questions */ bool AlwaysNo = false; /* -n or -N, assume a no answer to all questions */ extern ClusterContents TheRootDir; /* * Function definitions */ static void passOne(int fd) { if (!Quick) findBadClusters(fd); scanAndFixMetadata(fd); } static void writeBackChanges(int fd) { writeFATMods(fd); if (!IsFAT32) writeRootDirMods(fd); writeClusterMods(fd); } static void tryOpen(int *fd, char *openMe, int oflag, int exitOnFailure) { int saveError; if ((*fd = open(openMe, oflag)) < 0) { if (exitOnFailure == RETURN_ON_OPEN_FAILURE) return; saveError = errno; mountSanityCheckFails(); (void) fprintf(stderr, "%s: ", openMe); (void) fprintf(stderr, strerror(saveError)); (void) fprintf(stderr, "\n"); exit(1); } } static void doOpen(int *inFD, int *outFD, char *name, char *outName) { if (ReadOnly) { tryOpen(inFD, name, O_RDONLY, EXIT_ON_OPEN_FAILURE); *outFD = -1; } else { tryOpen(inFD, name, O_RDWR, RETURN_ON_OPEN_FAILURE); if (*inFD < 0) { if (errno != EACCES || WritableOnly) { int saveError = errno; mountSanityCheckFails(); (void) fprintf(stderr, gettext("%s: "), name); (void) fprintf(stderr, strerror(saveError)); (void) fprintf(stderr, "\n"); exit(2); } else { tryOpen(inFD, name, O_RDONLY, EXIT_ON_OPEN_FAILURE); AlwaysYes = false; AlwaysNo = true; ReadOnly = 1; *outFD = -1; } } else { *outFD = *inFD; } } if (outName != NULL) { tryOpen(outFD, outName, (O_RDWR | O_CREAT), EXIT_ON_OPEN_FAILURE); } (void) printf("** %s %s\n", name, ReadOnly ? gettext("(NO WRITE)") : ""); } static void openFS(char *special, int *inFD, int *outFD) { struct stat dinfo; char *actualDisk = NULL; char *suffix = NULL; int rv; if (Verbose) (void) fprintf(stderr, gettext("Opening file system.\n")); if (InputImage == NULL) { actualDisk = stat_actual_disk(special, &dinfo, &suffix); /* * Destination exists, now find more about it. */ if (!(S_ISCHR(dinfo.st_mode))) { mountSanityCheckFails(); (void) fprintf(stderr, gettext("\n%s: device name must be a " "character special device.\n"), actualDisk); exit(2); } } else { actualDisk = InputImage; } doOpen(inFD, outFD, actualDisk, OutputImage); rv = get_media_sector_size(*inFD, &bpsec); if (rv != 0) { (void) fprintf(stderr, gettext("error detecting device sector size: %s\n"), strerror(rv)); exit(2); } if (!is_sector_size_valid(bpsec)) { (void) fprintf(stderr, gettext("unsupported sector size: %zu\n"), bpsec); exit(2); } if (suffix) { if ((PartitionOffset = findPartitionOffset(*inFD, bpsec, suffix)) < 0) { mountSanityCheckFails(); (void) fprintf(stderr, gettext("Unable to find logical drive %s\n"), suffix); exit(2); } else if (Verbose) { (void) fprintf(stderr, gettext("Partition starts at offset %lld\n"), PartitionOffset); } } else { PartitionOffset = 0; } } void usage(void) { (void) fprintf(stderr, gettext("pcfs Usage: fsck -F pcfs [-o v|p|w] special-file\n")); exit(1); } static char *LegalOpts[] = { #define VFLAG 0 "v", #define PFLAG 1 "p", #define WFLAG 2 "w", #define DFLAG 3 "d", #define IFLAG 4 "i", #define OFLAG 5 "o", NULL }; static void parseSubOptions(char *optsstr) { char *value; int c; while (*optsstr != '\0') { switch (c = getsubopt(&optsstr, LegalOpts, &value)) { case VFLAG: Quick = 0; break; case PFLAG: Preen++; break; case WFLAG: WritableOnly++; break; case DFLAG: Verbose++; break; case IFLAG: if (value == NULL) { missing_arg(LegalOpts[c]); } else { InputImage = value; } break; case OFLAG: if (value == NULL) { missing_arg(LegalOpts[c]); } else { OutputImage = value; } break; default: bad_arg(value); break; } } } static void sanityCheckOpts(void) { if (WritableOnly && ReadOnly) { (void) fprintf(stderr, gettext("-w option may not be used with the -n " "or -m options\n")); exit(4); } } static void confirmMountable(char *special, int fd) { char *printName; int okayToMount = 1; printName = InputImage ? InputImage : special; if (!IsFAT32) { /* make sure we can at least read the root directory */ getRootDirectory(fd); if (TheRootDir.bytes == NULL) okayToMount = 0; } else { /* check the bit designed into FAT32 for this purpose */ okayToMount = checkFAT32CleanBit(fd); } if (okayToMount) { (void) fprintf(stderr, gettext("pcfs fsck: sanity check: %s okay\n"), printName); exit(0); } else { (void) fprintf(stderr, gettext("pcfs fsck: sanity check: %s needs checking\n"), printName); exit(32); } } void mountSanityCheckFails(void) { if (Mflag) { (void) fprintf(stderr, gettext("pcfs fsck: sanity check failed: ")); } } /* * preenBail * Routine that other routines can call if they would go into a * state where they need user input. They can send an optional * message string to be printed before the exit. Caller should * send a NULL string if they don't have an exit message. */ void preenBail(char *outString) { /* * If we are running in the 'preen' mode, we got here because * we reached a situation that would require user intervention. * We have no choice but to bail at this point. */ if (Preen) { if (outString) (void) printf("%s", outString); (void) printf(gettext("FILE SYSTEM FIX REQUIRES USER " "INTERVENTION; RUN fsck MANUALLY.\n")); exit(36); } } int main(int argc, char *argv[]) { char *string; int ifd, ofd; int c; (void) setlocale(LC_ALL, ""); #if !defined(TEXT_DOMAIN) #define TEXT_DOMAIN "SYS_TEST" #endif (void) textdomain(TEXT_DOMAIN); if (init_yes() < 0) errx(2, gettext(ERR_MSG_INIT_YES), strerror(errno)); if (argc < 2) usage(); while ((c = getopt(argc, argv, "F:VYNynmo:")) != EOF) { switch (c) { case 'F': string = optarg; if (strcmp(string, "pcfs") != 0) usage(); break; case 'V': { char *opt_text; int opt_count; (void) printf(gettext("fsck -F pcfs ")); for (opt_count = 1; opt_count < argc; opt_count++) { opt_text = argv[opt_count]; if (opt_text) (void) printf(" %s ", opt_text); } (void) printf("\n"); fini_yes(); exit(0); } break; case 'N': case 'n': AlwaysYes = false; AlwaysNo = true; ReadOnly = 1; break; case 'Y': case 'y': AlwaysYes = true; AlwaysNo = false; break; case 'm': Mflag++; ReadOnly = 1; break; case 'o': string = optarg; parseSubOptions(string); break; } } sanityCheckOpts(); if (InputImage == NULL && (optind < 0 || optind >= argc)) usage(); openFS(argv[optind], &ifd, &ofd); readBPB(ifd); /* * -m mountable fs check. This call will not return. */ if (Mflag) confirmMountable(argv[optind], ifd); /* * Pass 1: Find any bad clusters and adjust the FAT and directory * entries accordingly */ passOne(ifd); /* * XXX - future passes? * Ideas: * Data relocation for bad clusters with partial read success? * Syncing backup FAT copies with main copy? * Syncing backup root sector for FAT32? */ /* * No problems if we made it this far. */ printSummary(stdout); writeBackChanges(ofd); fini_yes(); return (0); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1999,2000 by Sun Microsystems, Inc. * All rights reserved. * Copyright 2024 MNX Cloud, Inc. */ #ifndef _FSCK_PCFS_H #define _FSCK_PCFS_H /* * Structures used by the pcfs file system checker. */ #ifdef __cplusplus extern "C" { #endif #include /* * The root directory of FAT12/16 file systems doesn't sit in * a cluster. */ #define FAKE_ROOTDIR_CLUST -1 /* * The first available cluster number for a FAT fs is always the same, 2. */ #define FIRST_CLUSTER 2 #define RETURN_ON_OPEN_FAILURE 0 #define EXIT_ON_OPEN_FAILURE 1 #define NO_FAT_IN_SUMMARY 0 #define INCLUDE_FAT_IN_SUMMARY 1 #define RDCLUST_DONT_CACHE 0 #define RDCLUST_DO_CACHE 1 /* * Return values for sanityCheckSize() */ #define SIZE_MATCHED 0 #define TRUNCATED 1 #define RDCLUST_MAX_RETRY 3 #define RDCLUST_GOOD 0 #define RDCLUST_FAIL -1 #define RDCLUST_MEMERR -2 #define RDCLUST_BADINPUT -3 typedef union clustDataTypes { struct pcdir *dirp; uchar_t *bytes; } ClusterContents; struct cached { int32_t clusterNum; ClusterContents clusterData; short modified; struct cached *next; }; typedef struct cached CachedCluster; struct nameinfo { char *fullName; int references; }; /* * This structure is shared between all structures belonging to * a single file. The refcnt is a 24 bit integer, that should be * sufficient for 4GB files, even when someone uses 256 byte clusters * (4K is the typical cluster size, 512 bytes is probably the minimum) * The inefficiency of using a bit field is compensated by the memory * savings and prevented paging on large filesystems. */ struct clinfo { struct pcdir *dirent; union { struct clinfo *_nextfree; struct pcdir *_longent; } _unionelem; int32_t longEntStartClust; int refcnt:24; uint_t flags:8; uchar_t *saved; struct nameinfo *path; }; /* * #define dirent conflicts with other dirent uses, so we used the * second element instead of the first one one as union for the free * list */ #define longent _unionelem._longent #define nextfree _unionelem._nextfree typedef struct clinfo ClusterInfo; /* * Return values for allocInUse */ #define CLINFO_PREVIOUSLY_ALLOCED 1 #define CLINFO_NEWLY_ALLOCED 0 #define CLINFO_BAD 0x1 #define CLINFO_ORPHAN 0x2 #define CLINFO_HIDDEN 0x4 /* * Traversal operations for wandering the file system metadata */ #define PCFS_NO_SUBDIRS 0 #define PCFS_VISIT_SUBDIRS 1 #define PCFS_TRAVERSE_ALL 1 /* visit all nodes */ #define PCFS_FIND_ATTR 2 /* search for matching attribute */ #define PCFS_FIND_STATUS 3 /* search for same status */ #define PCFS_FIND_CHKS 4 /* find FILENNNN.CHK files */ /* * Booleans for markInUse, whether or not file is marked hidden. */ #define VISIBLE 0 #define HIDDEN 1 /* * Indices for various parts of the FILEnnnn.CHK name */ #define CHKNAME_F 0 #define CHKNAME_I 1 #define CHKNAME_L 2 #define CHKNAME_E 3 #define CHKNAME_THOUSANDS 4 #define CHKNAME_HUNDREDS 5 #define CHKNAME_TENS 6 #define CHKNAME_ONES 7 #define CHKNAME_C 0 #define CHKNAME_H 1 #define CHKNAME_K 2 /* * Largest value that will fit into our lost+found naming scheme of * FILEnnnn.CHK. */ #define MAXCHKVAL 9999 extern size_t bpsec; extern bool AlwaysYes; /* assume a yes answer to all questions */ extern bool AlwaysNo; /* assume a no answer to all questions */ /* * Function prototypes */ extern struct pcdir *addRootDirEnt(int fd, struct pcdir *copyme); extern struct pcdir *newDirEnt(struct pcdir *copyme); extern int32_t extractStartCluster(struct pcdir *dp); extern int32_t findImpactedCluster(struct pcdir *modified); extern int32_t readFATEntry(int32_t currentCluster); extern uint32_t extractSize(struct pcdir *dp); extern int32_t nextInChain(int32_t currentCluster); extern char *nextAvailableCHKName(int *chosen); extern void truncChainWithBadCluster(int fd, struct pcdir *dp, int32_t startCluster); extern void mountSanityCheckFails(void); extern void markClusterModified(int32_t clusterNum); extern void scanAndFixMetadata(int fd); extern void updateDirEnt_Start(struct pcdir *dp, int32_t newStart); extern void addEntryToCHKList(int chkNumber); extern void createCHKNameList(int fd); extern void updateDirEnt_Name(struct pcdir *dp, char *newName); extern void updateDirEnt_Size(struct pcdir *dp, uint32_t newSize); extern void getRootDirectory(int fd); extern void writeClusterMods(int fd); extern void writeRootDirMods(int fd); extern void traverseFromRoot(int fd, int depth, int descend, int operation, char matchRequired, struct pcdir **found, int32_t *lastDirCluster, struct pcdir **dirEnd, char *recordPath, int *pathLen); extern void findBadClusters(int fd); extern void markFreeInFAT(int32_t clusterNum); extern void markLastInFAT(int32_t clusterNum); extern void writeFATEntry(int32_t currentCluster, int32_t value); extern void markBadInFAT(int32_t clusterNum); extern void printSummary(FILE *outDest); extern void squirrelPath(struct nameinfo *pathInfo, int32_t clusterNum); extern void usingCHKName(void *nameCookie); extern void writeFATMods(int fd); extern void traverseDir(int fd, int32_t startAt, int depth, int descend, int operation, char matchRequired, struct pcdir **found, int32_t *lastDirCluster, struct pcdir **dirEnd, char *recordPath, int *pathLen); extern void splitChain(int fd, struct pcdir *dp, int32_t problemCluster, struct pcdir **newdp, int32_t *orphanStart); extern void preenBail(char *outString); extern void readBPB(int fd); extern void getFAT(int fd); extern int checkFAT32CleanBit(int fd); extern int reservedInFAT(int32_t clusterNum); extern int isMarkedBad(int32_t clusterNum); extern int readCluster(int fd, int32_t clusterNum, uchar_t **data, int32_t *datasize, int shouldCache); extern int freeInFAT(int32_t clusterNum); extern int lastInFAT(int32_t clusterNum); extern int markInUse(int fd, int32_t clusterNum, struct pcdir *referencer, struct pcdir *longRef, int32_t longStartCluster, int isHidden, ClusterInfo **template); extern int badInFAT(int32_t clusterNum); #ifdef __cplusplus } #endif #endif /* _FSCK_PCFS_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1999 by Sun Microsystems, Inc. * All rights reserved. */ /* * fsck_pcfs -- inject.c * Debugging routine that will insert random errors into the reads, * so that you'll actually end up with some bad clusters. */ #include #include #include extern ssize_t _read(int fildes, void *buf, size_t nbyte); ssize_t read(int fildes, void *buf, size_t nbyte) { static int count = 0; if ((count++ >= 263 && count <= 267) || (count >= 381 && count <= 385) || (count >= 1014 && count <= 1019) || (count >= 1119 && count <= 1123) || (count >= 1888 && count <= 1892)) { errno = EIO; return (-1); } return (_read(fildes, buf, nbyte)); } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2006 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # FSTYP_VERS=1 FSTYPE= pcfs LIBPROG= fstyp.so.${FSTYP_VERS} include ../../../../lib/Makefile.lib include ../../Makefile.fstype # There should be a mapfile here MAPFILES = CFLAGS += $(C_PICFLAGS) DYNLIB= $(LIBPROG) LDLIBS += -lnvpair -lc # # Override PMAP dependency # PMAP= # # Rules for making shared objects out of .c files. Works well if # we have a one-to-one mapping. Applies in all cases so far. # .SUFFIXES: .so.${FSTYP_VERS} ${SUFFIXES} .c.so.${FSTYP_VERS}: $(CC) $(CPPFLAGS) $(CFLAGS) $(DYNFLAGS) $(GSHARED) -o $@ $< $(LDLIBS) $(POST_PROCESS_SO) SRCS= ${LIBPROG:%.so.$(FSTYP_VERS)=%.c} CPPFLAGS += -DFSTYP_VERS=${FSTYP_VERS} \ -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 .KEEP_STATE: all: $(LIBPROG) install: all $(RM) $(ROOTLIBFSTYPE)/fstyp $(LN) $(ROOTUSRSBIN)/fstyp $(ROOTLIBFSTYPE)/fstyp cstyle: $(CSTYLE) $(SRCS) clean: $(RM) $(LIBPROG) clobber: clean /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T * All Rights Reserved * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright 2024 MNX Cloud, Inc. */ /* * Portions of this source code were derived from Berkeley 4.3 BSD * under license from the Regents of the University of California. */ /* * libfstyp module for pcfs */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define PC_LABEL_SIZE 11 /* for the PCDL_IS_LFN macro */ int enable_long_filenames = 1; struct fstyp_fat16_bs { uint8_t f_drvnum; uint8_t f_reserved1; uint8_t f_bootsig; uint8_t f_volid[4]; uint8_t f_label[11]; uint8_t f_typestring[8]; }; struct fstyp_fat32_bs { uint32_t f_fatlength; uint16_t f_flags; uint8_t f_major; uint8_t f_minor; uint32_t f_rootcluster; uint16_t f_infosector; uint16_t f_backupboot; uint8_t f_reserved2[12]; uint8_t f_drvnum; uint8_t f_reserved1; uint8_t f_bootsig; uint8_t f_volid[4]; uint8_t f_label[11]; uint8_t f_typestring[8]; }; typedef struct fstyp_pcfs { int fd; off_t offset; nvlist_t *attr; struct bootsec bs; struct fstyp_fat16_bs bs16; struct fstyp_fat32_bs bs32; ushort_t bps; int fattype; char volume_label[PC_LABEL_SIZE + 1]; /* parameters derived or calculated per FAT spec */ ulong_t FATSz; ulong_t TotSec; ulong_t RootDirSectors; ulong_t FirstDataSector; ulong_t DataSec; ulong_t CountOfClusters; } fstyp_pcfs_t; /* We should eventually make the structs "packed" so these won't be needed */ #define PC_BPSEC(h) ltohs((h)->bs.bps[0]) #define PC_RESSEC(h) ltohs((h)->bs.res_sec[0]) #define PC_NROOTENT(h) ltohs((h)->bs.rdirents[0]) #define PC_NSEC(h) ltohs((h)->bs.numsect[0]) #define PC_DRVNUM(h) (FSTYP_IS_32(h) ? (h)->bs32.f_drvnum : \ (h)->bs16.f_drvnum) #define PC_VOLID(a) (FSTYP_IS_32(h) ? ltohi((h)->bs32.f_volid[0]) : \ ltohi((h)->bs16.f_volid[0])) #define PC_LABEL_ADDR(a) (FSTYP_IS_32(h) ? \ &((h)->bs32.f_label[0]) : &((h)->bs16.f_label[0])) #define FSTYP_IS_32(h) ((h)->fattype == 32) #define FSTYP_MAX_CLUSTER_SIZE (64 * 1024) /* though officially 32K */ #define FSTYP_MAX_DIR_SIZE (65536 * 32) static int read_bootsec(fstyp_pcfs_t *h); static int valid_media(fstyp_pcfs_t *h); static int well_formed(fstyp_pcfs_t *h); static void calculate_parameters(fstyp_pcfs_t *h); static void determine_fattype(fstyp_pcfs_t *h); static void get_label(fstyp_pcfs_t *h); static void get_label_16(fstyp_pcfs_t *h); static void get_label_32(fstyp_pcfs_t *h); static int next_cluster_32(fstyp_pcfs_t *h, int n); static boolean_t dir_find_label(fstyp_pcfs_t *h, struct pcdir *d, int nent); static int is_pcfs(fstyp_pcfs_t *h); static int dumpfs(fstyp_pcfs_t *h, FILE *fout, FILE *ferr); static int get_attr(fstyp_pcfs_t *h); int fstyp_mod_init(int fd, off_t offset, fstyp_mod_handle_t *handle); void fstyp_mod_fini(fstyp_mod_handle_t handle); int fstyp_mod_ident(fstyp_mod_handle_t handle); int fstyp_mod_get_attr(fstyp_mod_handle_t handle, nvlist_t **attrp); int fstyp_mod_dump(fstyp_mod_handle_t handle, FILE *fout, FILE *ferr); int fstyp_mod_init(int fd, off_t offset, fstyp_mod_handle_t *handle) { struct fstyp_pcfs *h; if ((h = calloc(1, sizeof (struct fstyp_pcfs))) == NULL) { return (FSTYP_ERR_NOMEM); } h->fd = fd; h->offset = offset; *handle = (fstyp_mod_handle_t)h; return (0); } void fstyp_mod_fini(fstyp_mod_handle_t handle) { struct fstyp_pcfs *h = (struct fstyp_pcfs *)handle; if (h->attr == NULL) { nvlist_free(h->attr); h->attr = NULL; } free(h); } int fstyp_mod_ident(fstyp_mod_handle_t handle) { struct fstyp_pcfs *h = (struct fstyp_pcfs *)handle; return (is_pcfs(h)); } int fstyp_mod_get_attr(fstyp_mod_handle_t handle, nvlist_t **attrp) { struct fstyp_pcfs *h = (struct fstyp_pcfs *)handle; int error; if (h->attr == NULL) { if (nvlist_alloc(&h->attr, NV_UNIQUE_NAME_TYPE, 0)) { return (FSTYP_ERR_NOMEM); } if ((error = get_attr(h)) != 0) { nvlist_free(h->attr); h->attr = NULL; return (error); } } *attrp = h->attr; return (0); } int fstyp_mod_dump(fstyp_mod_handle_t handle, FILE *fout, FILE *ferr) { struct fstyp_pcfs *h = (struct fstyp_pcfs *)handle; return (dumpfs(h, fout, ferr)); } /* * Read in boot sector. Convert into host endianness where possible. */ static int read_bootsec(fstyp_pcfs_t *h) { struct dk_minfo dkminfo; char *buf; size_t size = PC_SECSIZE; if (ioctl(h->fd, DKIOCGMEDIAINFO, &dkminfo) != -1) { if (dkminfo.dki_lbsize != 0) size = dkminfo.dki_lbsize; } buf = malloc(size); if (buf == NULL) return (FSTYP_ERR_NOMEM); (void) lseek(h->fd, h->offset, SEEK_SET); if (read(h->fd, buf, size) != (ssize_t)size) { free(buf); return (FSTYP_ERR_IO); } bcopy(buf, &h->bs, sizeof (h->bs)); bcopy(buf + sizeof (struct bootsec), &h->bs16, sizeof (h->bs16)); bcopy(buf + sizeof (struct bootsec), &h->bs32, sizeof (h->bs32)); free(buf); h->bs.fatsec = ltohs(h->bs.fatsec); h->bs.spt = ltohs(h->bs.spt); h->bs.nhead = ltohs(h->bs.nhead); h->bs.hiddensec = ltohi(h->bs.hiddensec); h->bs.totalsec = ltohi(h->bs.totalsec); h->bs32.f_fatlength = ltohi(h->bs32.f_fatlength); h->bs32.f_flags = ltohs(h->bs32.f_flags); h->bs32.f_rootcluster = ltohi(h->bs32.f_rootcluster); h->bs32.f_infosector = ltohs(h->bs32.f_infosector); h->bs32.f_backupboot = ltohs(h->bs32.f_backupboot); h->bps = PC_BPSEC(h); return (0); } static int valid_media(fstyp_pcfs_t *h) { switch (h->bs.mediadesriptor) { case MD_FIXED: case SS8SPT: case DS8SPT: case SS9SPT: case DS9SPT: case DS18SPT: case DS9_15SPT: return (1); default: return (0); } } static int well_formed(fstyp_pcfs_t *h) { int fatmatch; if (h->bs16.f_bootsig == 0x29) { fatmatch = ((h->bs16.f_typestring[0] == 'F' && h->bs16.f_typestring[1] == 'A' && h->bs16.f_typestring[2] == 'T') && (h->bs.fatsec > 0) && ((PC_NSEC(h) == 0 && h->bs.totalsec > 0) || PC_NSEC(h) > 0)); } else if (h->bs32.f_bootsig == 0x29) { fatmatch = ((h->bs32.f_typestring[0] == 'F' && h->bs32.f_typestring[1] == 'A' && h->bs32.f_typestring[2] == 'T') && (h->bs.fatsec == 0 && h->bs32.f_fatlength > 0) && ((PC_NSEC(h) == 0 && h->bs.totalsec > 0) || PC_NSEC(h) > 0)); } else { fatmatch = (PC_NSEC(h) > 0 && h->bs.fatsec > 0); } return (fatmatch && h->bps > 0 && h->bps % 512 == 0 && h->bs.spcl > 0 && PC_RESSEC(h) >= 1 && h->bs.nfat > 0); } static void calculate_parameters(fstyp_pcfs_t *h) { if (PC_NSEC(h) != 0) { h->TotSec = PC_NSEC(h); } else { h->TotSec = h->bs.totalsec; } if (h->bs.fatsec != 0) { h->FATSz = h->bs.fatsec; } else { h->FATSz = h->bs32.f_fatlength; } if ((h->bps == 0) || (h->bs.spcl == 0)) { return; } h->RootDirSectors = ((PC_NROOTENT(h) * 32) + (h->bps - 1)) / h->bps; h->FirstDataSector = PC_RESSEC(h) + h->bs.nfat * h->FATSz + h->RootDirSectors; h->DataSec = h->TotSec - h->FirstDataSector; h->CountOfClusters = h->DataSec / h->bs.spcl; } static void determine_fattype(fstyp_pcfs_t *h) { if (h->CountOfClusters == 0) { h->fattype = 0; return; } if (h->CountOfClusters < 4085) { h->fattype = 12; } else if (h->CountOfClusters < 65525) { h->fattype = 16; } else { h->fattype = 32; } } static void get_label(fstyp_pcfs_t *h) { /* * Use label from the boot sector by default. * Can overwrite later with the one from root directory. */ (void) memcpy(h->volume_label, PC_LABEL_ADDR(h), PC_LABEL_SIZE); h->volume_label[PC_LABEL_SIZE] = '\0'; if (h->fattype == 0) { return; } else if (FSTYP_IS_32(h)) { get_label_32(h); } else { get_label_16(h); } } /* * Get volume label from the root directory entry. * In FAT12/16 the root directory is of fixed size. * It immediately follows the FATs */ static void get_label_16(fstyp_pcfs_t *h) { ulong_t FirstRootDirSecNum; int secsize; off_t offset; uint8_t buf[PC_SECSIZE * 4]; int i; int nent, resid; if ((secsize = h->bps) > sizeof (buf)) { return; } FirstRootDirSecNum = PC_RESSEC(h) + h->bs.nfat * h->bs.fatsec; offset = h->offset + FirstRootDirSecNum * secsize; resid = PC_NROOTENT(h); for (i = 0; i < h->RootDirSectors; i++) { (void) lseek(h->fd, offset, SEEK_SET); if (read(h->fd, buf, secsize) != secsize) { return; } nent = secsize / sizeof (struct pcdir); if (nent > resid) { nent = resid; } if (dir_find_label(h, (struct pcdir *)buf, nent)) { return; } resid -= nent; offset += PC_SECSIZE; } } /* * Get volume label from the root directory entry. * In FAT32 root is a usual directory, a cluster chain. * It starts at BPB_RootClus. */ static void get_label_32(fstyp_pcfs_t *h) { off_t offset; int clustersize; int n; ulong_t FirstSectorofCluster; uint8_t *buf; int nent; int cnt = 0; clustersize = h->bs.spcl * h->bps; if ((clustersize == 0) || (clustersize > FSTYP_MAX_CLUSTER_SIZE) || ((buf = calloc(1, clustersize)) == NULL)) { return; } for (n = h->bs32.f_rootcluster; n != 0; n = next_cluster_32(h, n)) { FirstSectorofCluster = (n - 2) * h->bs.spcl + h->FirstDataSector; offset = h->offset + FirstSectorofCluster * h->bps; (void) lseek(h->fd, offset, SEEK_SET); if (read(h->fd, buf, clustersize) != clustersize) { break; } nent = clustersize / sizeof (struct pcdir); if (dir_find_label(h, (struct pcdir *)buf, nent)) { break; } if (++cnt > FSTYP_MAX_DIR_SIZE / clustersize) { break; } } free(buf); } /* * Get a FAT entry pointing to the next file cluster */ int next_cluster_32(fstyp_pcfs_t *h, int n) { uint8_t buf[PC_SECSIZE]; ulong_t ThisFATSecNum; ulong_t ThisFATEntOffset; off_t offset; uint32_t val; int next = 0; ThisFATSecNum = PC_RESSEC(h) + (n * 4) / h->bps; ThisFATEntOffset = (n * 4) % h->bps; offset = h->offset + ThisFATSecNum * h->bps; (void) lseek(h->fd, offset, SEEK_SET); if (read(h->fd, buf, sizeof (buf)) == sizeof (buf)) { val = buf[ThisFATEntOffset] & 0x0fffffff; next = ltohi(val); } return (next); } /* * Given an array of pcdir structs, find one containing volume label. */ static boolean_t dir_find_label(fstyp_pcfs_t *h, struct pcdir *d, int nent) { int i; for (i = 0; i < nent; i++, d++) { if (PCDL_IS_LFN(d)) continue; if ((d->pcd_filename[0] != PCD_UNUSED) && (d->pcd_filename[0] != PCD_ERASED) && ((d->pcd_attr & (PCA_LABEL | PCA_DIR)) == PCA_LABEL) && (d->un.pcd_scluster_hi == 0) && (d->pcd_scluster_lo == 0)) { (void) memcpy(h->volume_label, d->pcd_filename, PC_LABEL_SIZE); h->volume_label[PC_LABEL_SIZE] = '\0'; return (B_TRUE); } } return (B_FALSE); } static int is_pcfs(fstyp_pcfs_t *h) { int error; if ((error = read_bootsec(h)) != 0) { return (error); } if (!valid_media(h)) { return (FSTYP_ERR_NO_MATCH); } if (!well_formed(h)) { return (FSTYP_ERR_NO_MATCH); } calculate_parameters(h); determine_fattype(h); get_label(h); return (0); } static int dumpfs(fstyp_pcfs_t *h, FILE *fout, FILE *ferr __unused) { /* * If fat type was not detected, then the other data is * likely bogus. */ if (h->fattype == 0) return (FSTYP_ERR_NO_MATCH); (void) fprintf(fout, "Filesystem type: FAT%d\n", h->fattype); (void) fprintf(fout, "Bytes Per Sector %d\t\tSectors Per Cluster %d\n", h->bps, h->bs.spcl); (void) fprintf(fout, "Reserved Sectors %d\t\tNumber of FATs %d\n", (unsigned short)PC_RESSEC(h), h->bs.nfat); (void) fprintf(fout, "Root Dir Entries %d\t\tNumber of Sectors %d\n", (unsigned short)PC_NROOTENT(h), h->TotSec); (void) fprintf(fout, "Sectors Per FAT %d\t\tSectors Per Track %d\n", h->FATSz, h->bs.spt); (void) fprintf(fout, "Number of Heads %d\t\tNumber Hidden Sectors %d\n", h->bs.nhead, h->bs.hiddensec); (void) fprintf(fout, "Volume ID: 0x%x\n", PC_VOLID(h)); (void) fprintf(fout, "Volume Label: %s\n", h->volume_label); (void) fprintf(fout, "Drive Number: 0x%x\n", PC_DRVNUM(h)); (void) fprintf(fout, "Media Type: 0x%x ", h->bs.mediadesriptor); switch (h->bs.mediadesriptor) { case MD_FIXED: (void) fprintf(fout, "\"Fixed\" Disk\n"); break; case SS8SPT: (void) fprintf(fout, "Single Sided, 8 Sectors Per Track\n"); break; case DS8SPT: (void) fprintf(fout, "Double Sided, 8 Sectors Per Track\n"); break; case SS9SPT: (void) fprintf(fout, "Single Sided, 9 Sectors Per Track\n"); break; case DS9SPT: (void) fprintf(fout, "Double Sided, 9 Sectors Per Track\n"); break; case DS18SPT: (void) fprintf(fout, "Double Sided, 18 Sectors Per Track\n"); break; case DS9_15SPT: (void) fprintf(fout, "Double Sided, 9-15 Sectors Per Track\n"); break; default: (void) fprintf(fout, "Unknown Media Type\n"); } return (0); } #define ADD_STRING(h, name, value) \ if (nvlist_add_string(h->attr, name, value) != 0) { \ return (FSTYP_ERR_NOMEM); \ } #define ADD_UINT32(h, name, value) \ if (nvlist_add_uint32(h->attr, name, value) != 0) { \ return (FSTYP_ERR_NOMEM); \ } #define ADD_UINT64(h, name, value) \ if (nvlist_add_uint64(h->attr, name, value) != 0) { \ return (FSTYP_ERR_NOMEM); \ } #define ADD_BOOL(h, name, value) \ if (nvlist_add_boolean_value(h->attr, name, value) != 0) { \ return (FSTYP_ERR_NOMEM); \ } static int get_attr(fstyp_pcfs_t *h) { char s[64]; ADD_UINT32(h, "bytes_per_sector", h->bps); ADD_UINT32(h, "sectors_per_cluster", h->bs.spcl); ADD_UINT32(h, "reserved_sectors", PC_RESSEC(h)); ADD_UINT32(h, "fats", h->bs.nfat); ADD_UINT32(h, "root_entry_count", PC_NROOTENT(h)); ADD_UINT32(h, "total_sectors_16", PC_NSEC(h)); ADD_UINT32(h, "media", h->bs.mediadesriptor); ADD_UINT32(h, "fat_size_16", h->bs.fatsec); ADD_UINT32(h, "sectors_per_track", h->bs.spt); ADD_UINT32(h, "heads", h->bs.nhead); ADD_UINT32(h, "hidden_sectors", h->bs.hiddensec); ADD_UINT32(h, "total_sectors_32", h->bs.totalsec); ADD_UINT32(h, "drive_number", PC_DRVNUM(h)); ADD_UINT32(h, "volume_id", PC_VOLID(h)); ADD_STRING(h, "volume_label", h->volume_label); if (FSTYP_IS_32(h)) { ADD_UINT32(h, "fat_size_32", h->bs32.f_fatlength); } ADD_UINT32(h, "total_sectors", h->TotSec); ADD_UINT32(h, "fat_size", h->FATSz); ADD_UINT32(h, "count_of_clusters", h->CountOfClusters); ADD_UINT32(h, "fat_entry_size", h->fattype); ADD_BOOL(h, "gen_clean", B_TRUE); if (PC_VOLID(a) != 0) { (void) snprintf(s, sizeof (s), "%08x", PC_VOLID(a)); ADD_STRING(h, "gen_guid", s); } (void) snprintf(s, sizeof (s), "%d", h->fattype); ADD_STRING(h, "gen_version", s); ADD_STRING(h, "gen_volume_label", h->volume_label); return (0); } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (the "License"). You may not use this file except in compliance # with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright (c) 1998 by Sun Microsystems, Inc. # All rights reserved. # Copyright 2024 MNX Cloud, Inc. # FSTYPE= pcfs LIBPROG= mkfs ATTMK= $(LIBPROG) include ../../Makefile.fstype OBJS= mkfs_main.o pcfs_common.o getresponse.o SRCS= mkfs_main.c ../common/pcfs_common.c SRCS += $(SRC)/common/util/getresponse.c LDLIBS += -lefi # for messaging catalog # POFILE= mkfs.po catalog: $(POFILE) CPPFLAGS += -D_LARGEFILE64_SOURCE CPPFLAGS += -I../common CPPFLAGS += -I$(SRC)/common/util CPPFLAGS += -I$(SRC)/uts/common # Hammerhead: Use amd64 directory (i386 removed in 64-bit only build) CPPFLAGS += -I$(SRC)/cmd/boot/installboot/amd64 $(NOT_RELEASE_BUILD)CPPFLAGS += -DDEBUG $(LIBPROG): $(OBJS) $(LINK.c) -o $@ $(OBJS) $(LDLIBS) $(POST_PROCESS) clean: $(RM) $(OBJS) $(POFILE).i messages.po $(POFILE): $(RM) $@ $(COMPILE.cpp) $(SRCS) > $(POFILE).i $(XGETTEXT) $(XGETFLAGS) $(POFILE).i sed "/^domain/d" messages.po > $@ $(RM) $(POFILE).i messages.po %.o : ../common/%.c $(COMPILE.c) $(OUTPUT_OPTION) $< $(POST_PROCESS_O) %.o : $(SRC)/common/util/%.c $(COMPILE.c) $(OUTPUT_OPTION) $< $(POST_PROCESS_O) /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright (c) 2011 Gary Mills * Copyright 2024 MNX Cloud, Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "getresponse.h" #include "pcfs_bpb.h" #include "pcfs_common.h" /* Drop gettext for debug build, so we can catch format errors. */ #ifdef DEBUG #define gettext(x) x #endif /* * mkfs (for pcfs) * * Install a boot block, FAT, and (if desired) the first resident * of the new fs. * * XXX -- floppy opens need O_NDELAY? */ #define IN_RANGE(n, x, y) (((n) >= (x)) && ((n) <= (y))) #define DEFAULT_LABEL "NONAME" /* * Extended boot signature. This byte indicates that VOLID, VOLLAB and * FILSYSTYPE fields are present. [fatgen103, pages 11 and 12]. */ #define BOOTSIG 0x29 /* Exit codes. */ #define ERR_USAGE 1 #define ERR_OS 2 /* open fail, stat fail etc */ #define ERR_INVALID 3 /* Validation failed */ #define ERR_FAIL 4 /* IO error, no memory etc */ #define ERR_USER 5 /* User input */ #define ERR_INVAL 6 /* Invalid data */ static char *BootBlkFn = "/boot/pmbr"; static char *DiskName = NULL; static char *FirstFn = NULL; static char *Label = DEFAULT_LABEL; static char Firstfileattr = 0x20; static int Outputtofile = 0; static int SunBPBfields = 0; static int GetFsParams = 0; static int Fatentsize = 0; static int Imagesize = 3; static int Notreally = 0; static int Verbose = 0; static int MakeFAT32 = 0; /* * If there is an FDISK entry for the device where we're about to * make the file system, we ought to make a file system that has the * same size FAT as the FDISK table claims. We track the size FDISK * thinks in this variable. */ static int FdiskFATsize = 0; static int GetSize = 1; /* Unless we're given as arg, must look it up */ static ulong_t TotSize; /* Total size of FS in # of sectors */ static int GetSPC = 1; /* Unless we're given as arg, must calculate */ static ulong_t SecPerClust; /* # of sectors per cluster */ static int GetOffset = 1; /* Unless we're given as arg, must look it up */ static ulong_t RelOffset; /* Relative start sector (hidden sectors) */ static int GetSPT = 1; /* Unless we're given as arg, must look it up */ static ushort_t SecPerTrk; /* # of sectors per track */ static int GetTPC = 1; /* Unless we're given as arg, must look it up */ static ushort_t TrkPerCyl; /* # of tracks per cylinder */ static int GetResrvd = 1; /* Unless we're given as arg, must calculate */ static int Resrvd; /* Number of reserved sectors */ static int GetBPF = 1; /* Unless we're given as arg, must calculate */ static int BitsPerFAT; /* Total size of FS in # of sectors */ static ulong_t TotalClusters; /* Computed total number of clusters */ /* * Unless we are told otherwise, we should use fdisk table for non-diskettes. */ static int DontUseFdisk = 0; /* * Function prototypes */ #ifdef _BIG_ENDIAN static void swap_pack_grabsebpb(bpb_t *wbpb, struct _boot_sector *bsp); static void swap_pack_bpb32cpy(struct _boot_sector32 *bsp, bpb_t *wbpb); static void swap_pack_sebpbcpy(struct _boot_sector *bsp, bpb_t *wbpb); static void swap_pack_bpbcpy(struct _boot_sector *bsp, bpb_t *wbpb); #endif static uchar_t *build_rootdir(bpb_t *wbpb, char *ffn, int fffd, ulong_t ffsize, pc_cluster32_t ffstart, ulong_t *rdirsize); static uchar_t *build_fat(bpb_t *wbpb, struct fat_od_fsi *fsinfop, ulong_t *fatsize, char *ffn, int *fffd, ulong_t *ffsize, pc_cluster32_t *ffstartclust); static void compare_existing_with_computed(int fd, char *suffix, bpb_t *wbpb, int *prtsize, int *prtspc, int *prtbpf, int *prtnsect, int *prtntrk, int *prtfdisk, int *prthidden, int *prtrsrvd, int *dashos); static void print_reproducing_command(int fd, char *actualdisk, char *suffix, bpb_t *wbpb); static void compute_file_area_size(bpb_t *wbpb); static void write_fat32_bootstuff(int fd, boot_sector_t *bsp, bpb_t *wbpb, struct fat_od_fsi *fsinfop, off64_t seekto); static void sanity_check_options(int argc, int optind); static void compute_cluster_size(bpb_t *wbpb); static void find_fixed_details(int fd, bpb_t *wbpb); static void dirent_fname_fill(struct pcdir *dep, char *fn); static void floppy_bpb_fillin(bpb_t *wbpb, int diam, int hds, int spt); static void read_existing_bpb(int fd, bpb_t *wbpb); static void warn_funky_fatsize(void); static void warn_funky_floppy(void); static void dirent_time_fill(struct pcdir *dep); static void parse_suboptions(char *optsstr); static void write_bootsects(int fd, boot_sector_t *bsp, bpb_t *wbpb, struct fat_od_fsi *fsinfop, off64_t seekto); static void fill_bpb_sizes(bpb_t *wbpb, struct ipart part[], int partno, off64_t offset); static void set_fat_string(bpb_t *wbpb, int fatsize); static void partn_lecture(char *dn); static void lookup_floppy(struct fd_char *fdchar, bpb_t *wbpb); static void label_volume(char *lbl, bpb_t *wbpb); static void mark_cluster(uchar_t *fatp, pc_cluster32_t clustnum, uint32_t value); static void dashm_bail(int fd); static void write_rest(bpb_t *wbpb, char *efn, int dfd, int sfd, int remaining); static void write_fat(int fd, off64_t seekto, char *fn, char *lbl, char *ffn, bpb_t *wbpb); static int prepare_image_file(const char *fn, bpb_t *wbpb); static int verify_bootblkfile(char *fn, boot_sector_t *bs); static int open_and_examine(char *dn, bpb_t *wbpb); static int verify_firstfile(char *fn, ulong_t *filesize); static int lookup_FAT_size(uchar_t partid); static int open_and_seek(const char *dn, bpb_t *wbpb, off64_t *seekto); static int warn_mismatch(char *desc, char *src, int expect, int assigned); static void copy_bootblk(char *fn, boot_sector_t *bootsect); static int parse_drvnum(char *pn); static bool seek_nofdisk(int fd, bpb_t *wbpb, off64_t *seekto); static bool ask_nicely(int bits, char *special); static bool seek_partn(int fd, char *pn, bpb_t *wbpb, off64_t *seekto); /* * usage * * Display usage message and exit. */ void usage(void) { (void) fprintf(stderr, gettext("pcfs usage: mkfs [-F FSType] [-V] [-m] " "[-o specific_options] special\n")); (void) fprintf(stderr, gettext(" -V: print this command line and return\n" " -m: dump command line used to create a FAT on this media\n" "\t(other options are ignored if this option is chosen).\n" " -o: pcfs_specific_options:\n" "\t'pcfs_specific_options' is a comma separated list\n" "\tincluding one or more of the following options:\n" "\t N,v,r,h,s,b=label,B=filename,i=filename,\n" "\t spc=n,fat=n,nsect=n,ntrack=n,nofdisk,size=n,\n" "\t reserve=n,hidden=n\n\n")); (void) fprintf(stderr, gettext("'Special' should specify a raw diskette " "or raw fixed disk device. \"Fixed\"\n" "disks (which include high-capacity removable " "media such as Zip disks)\n" "may be further qualified with a logical " "drive specifier.\n" "Examples are: /dev/rdiskette and " "/dev/rdsk/c0t0d0p0:c\n")); exit(ERR_USAGE); } static bool ask_nicely(int bits, char *special) { /* * 4228473 - No way to non-interactively make a pcfs filesystem * * If we don't have an input TTY, or we aren't really doing * anything, then don't ask questions. Assume a yes answer * to any questions we would ask. */ if (Notreally || !isatty(fileno(stdin))) return (true); (void) printf( gettext("Construct a new FAT%d file system on %s: (y/n)? "), bits, special); (void) fflush(stdout); return (yes()); } /* * parse_drvnum * Convert a partition name into a drive number. */ static int parse_drvnum(char *pn) { int drvnum; /* * Determine logical drive to seek after. */ if (strlen(pn) == 1 && *pn >= 'c' && *pn <= 'z') { drvnum = *pn - 'c' + 1; } else if (*pn >= '0' && *pn <= '9') { char *d; int v, m, c; v = 0; d = pn; while (*d && *d >= '0' && *d <= '9') { c = strlen(d); m = 1; while (--c) m *= 10; v += m * (*d - '0'); d++; } if (*d || v > 24) { (void) fprintf(stderr, gettext("%s: bogus logical drive specification.\n"), pn); return (-1); } drvnum = v; } else if (strcmp(pn, "boot") == 0) { drvnum = 99; } else { (void) fprintf(stderr, gettext("%s: bogus logical drive specification.\n"), pn); return (-1); } return (drvnum); } /* * Define some special logical drives we use. */ #define BOOT_PARTITION_DRIVE 99 #define PRIMARY_DOS_DRIVE 1 /* * isDosDrive() * Boolean function. Give it the systid field for an fdisk partition * and it decides if that's a systid that describes a DOS drive. We * use systid values defined in sys/dktp/fdisk.h. */ static int isDosDrive(uchar_t checkMe) { return ((checkMe == DOSOS12) || (checkMe == DOSOS16) || (checkMe == DOSHUGE) || (checkMe == FDISK_WINDOWS) || (checkMe == FDISK_EXT_WIN) || (checkMe == FDISK_FAT95) || (checkMe == DIAGPART)); } /* * isDosExtended() * Boolean function. Give it the systid field for an fdisk partition * and it decides if that's a systid that describes an extended DOS * partition. */ static int isDosExtended(uchar_t checkMe) { return ((checkMe == EXTDOS) || (checkMe == FDISK_EXTLBA)); } /* * isBootPart() * Boolean function. Give it the systid field for an fdisk partition * and it decides if that's a systid that describes a Solaris boot * partition. */ static int isBootPart(uchar_t checkMe) { return (checkMe == X86BOOT); } static int warn_mismatch(char *desc, char *src, int expect, int assigned) { if (expect == assigned) return (assigned); /* * 4228473 - No way to non-interactively make a pcfs filesystem * * If we don't have an input TTY, or we aren't really doing * anything, then don't ask questions. Assume a yes answer * to any questions we would ask. */ if (Notreally || !isatty(fileno(stdin))) { (void) printf(gettext("WARNING: User supplied %s is %d," "\nbut value obtained from the %s is %d.\n" "Using user supplied value.\n"), desc, assigned, src, expect); return (assigned); } (void) printf(gettext("User supplied %s is %d." "\nThe value obtained from the %s is %d.\n"), desc, assigned, src, expect); (void) printf( gettext("Continue with value given on command line (y/n)? ")); (void) fflush(stdout); if (yes()) return (assigned); else exit(ERR_USER); /*NOTREACHED*/ } static void fill_fat32_bpb(bpb_t *wbpb) { /* * ExtFlags means (according to MSDN BPB (FAT32) document) * * Bit 8 indicates info written to the active FAT is written * to all copies of the FAT. (I think they mean bit 7, with * numbering starting at 0) * * Lowest 4 bits of field are the 0 based FAT number of the * Active FAT. (only meaningful if bit 8 is set) * * Field contains combination of these values: * * VALUE DESCRIPTION * BGBPB_F_ActiveFATMsk Mask for low four bits * (0x000F) * BGBPB_F_NoFATMirror If set FAT mirroring disabled. * (0x0080) If clear, FAT mirroring enabled. * * We set the value based on what I've seen on all the FAT32 drives * I've seen created by Windows. * */ wbpb->bpb32.ext_flags = 0x0; /* * No real explanation of the fs_vers file in the BPB doc. The * high byte is supposed to be the major version and the low the * minor version. Again I set according to what I've seen on Windows. */ wbpb->bpb32.fs_vers_lo = '\0'; wbpb->bpb32.fs_vers_hi = '\0'; /* * The convention appears to be to place the fs info sector * immediately after the boot sector, and that the backup boot * sector should be at sector 6. (based on what I see with * Windows) */ wbpb->bpb32.fsinfosec = 1; wbpb->bpb32.backupboot = 6; } static void fill_bpb_sizes(bpb_t *wbpb, struct ipart part[], int partno, off64_t offset) { ulong_t usesize; if (GetFsParams || GetSize) { usesize = ltohi(part[partno].numsect); if (Verbose) { (void) printf( gettext("Partition size (from FDISK table) " "= %lu sectors.\n"), usesize); } } else { usesize = warn_mismatch( gettext("length of partition (in sectors)"), gettext("FDISK table"), ltohi(part[partno].numsect), TotSize); } if (GetFsParams) { TotSize = usesize; } else { if (usesize > 0xffff) wbpb->bpb.sectors_in_volume = 0; else wbpb->bpb.sectors_in_volume = usesize; wbpb->bpb.sectors_in_logical_volume = usesize; } wbpb->bpb.hidden_sectors = offset; if (GetFsParams) { RelOffset = offset; } else { wbpb->sunbpb.bs_offset_high = offset >> 16; wbpb->sunbpb.bs_offset_low = offset & 0xFFFF; } } /* * lookup_FAT_size * * Given the FDISK partition file system identifier, return the * expected FAT size for the partition. */ static int lookup_FAT_size(uchar_t partid) { int rval; switch (partid) { case DOSOS12: rval = 12; break; case DOSOS16: case DOSHUGE: case FDISK_FAT95: case X86BOOT: rval = 16; break; case FDISK_WINDOWS: case FDISK_EXT_WIN: rval = 32; break; case EXTDOS: case FDISK_EXTLBA: default: rval = -1; break; } return (rval); } /* * seek_partn * * Seek to the beginning of the partition where we need to install * the new FAT. Zero return for any error, but print error * messages here. */ static bool seek_partn(int fd, char *pn, bpb_t *wbpb, off64_t *seekto) { struct ipart part[FD_NUMPART]; struct mboot extmboot; struct mboot mb; diskaddr_t xstartsect; off64_t nextseek = 0; off64_t lastseek = 0; int logicalDriveCount = 0; int extendedPart = -1; int primaryPart = -1; int bootPart = -1; uint32_t xnumsect = 0; int drvnum; int driveIndex; int i; /* * Count of drives in the current extended partition's * FDISK table, and indexes of the drives themselves. */ int extndDrives[FD_NUMPART]; int numDrives = 0; /* * Count of drives (beyond primary) in master boot record's * FDISK table, and indexes of the drives themselves. */ int extraDrives[FD_NUMPART]; int numExtraDrives = 0; if ((drvnum = parse_drvnum(pn)) < 0) return (false); if (read(fd, &mb, sizeof (mb)) != sizeof (mb)) { (void) fprintf(stderr, gettext("Couldn't read a Master Boot Record?!\n")); return (false); } if (ltohs(mb.signature) != BOOTSECSIG) { (void) fprintf(stderr, gettext("Bad Sig on master boot record!\n")); return (false); } *seekto = 0; /* * Copy partition table into memory */ (void) memcpy(part, mb.parts, sizeof (part)); /* * Get a summary of what is in the Master FDISK table. * Normally we expect to find one partition marked as a DOS drive. * This partition is the one Windows calls the primary dos partition. * If the machine has any logical drives then we also expect * to find a partition marked as an extended DOS partition. * * Sometimes we'll find multiple partitions marked as DOS drives. * The Solaris fdisk program allows these partitions * to be created, but Windows fdisk no longer does. We still need * to support these, though, since Windows does. We also need to fix * our fdisk to behave like the Windows version. * * It turns out that some off-the-shelf media have *only* an * Extended partition, so we need to deal with that case as * well. * * Only a single (the first) Extended or Boot Partition will * be recognized. Any others will be ignored. */ for (i = 0; i < FD_NUMPART; i++) { if (isDosDrive(part[i].systid)) { if (primaryPart < 0) { logicalDriveCount++; primaryPart = i; } else { extraDrives[numExtraDrives++] = i; } continue; } if ((extendedPart < 0) && isDosExtended(part[i].systid)) { extendedPart = i; continue; } if ((bootPart < 0) && isBootPart(part[i].systid)) { bootPart = i; continue; } } if (drvnum == BOOT_PARTITION_DRIVE) { if (bootPart < 0) { (void) fprintf(stderr, gettext("No boot partition found on drive\n")); return (false); } if ((*seekto = ltohi(part[bootPart].relsect)) == 0) { (void) fprintf(stderr, gettext("Bogus FDISK entry? " "A boot partition starting\nat sector 0 would " "collide with the FDISK table!\n")); return (false); } fill_bpb_sizes(wbpb, part, bootPart, *seekto); *seekto *= wbpb->bpb.bytes_per_sector; FdiskFATsize = lookup_FAT_size(part[bootPart].systid); if (Verbose) (void) printf(gettext("Boot partition's offset: " "Sector %llx.\n"), *seekto / wbpb->bpb.bytes_per_sector); if (lseek64(fd, *seekto, SEEK_SET) < 0) { (void) fprintf(stderr, gettext("Partition %s: "), pn); perror(""); return (false); } return (true); } if (drvnum == PRIMARY_DOS_DRIVE && primaryPart >= 0) { if ((*seekto = ltohi(part[primaryPart].relsect)) == 0) { (void) fprintf(stderr, gettext("Bogus FDISK entry? " "A partition starting\nat sector 0 would " "collide with the FDISK table!\n")); return (false); } fill_bpb_sizes(wbpb, part, primaryPart, *seekto); *seekto *= wbpb->bpb.bytes_per_sector; FdiskFATsize = lookup_FAT_size(part[primaryPart].systid); if (Verbose) (void) printf(gettext("Partition's offset: " "Sector %llx.\n"), *seekto / wbpb->bpb.bytes_per_sector); if (lseek64(fd, *seekto, SEEK_SET) < 0) { (void) fprintf(stderr, gettext("Partition %s: "), pn); perror(""); return (false); } return (true); } /* * We are not looking for the C: drive (or there was no primary * drive found), so we had better have an extended partition or * extra drives in the Master FDISK table. */ if ((extendedPart < 0) && (numExtraDrives == 0)) { (void) fprintf(stderr, gettext("No such logical drive " "(missing extended partition entry)\n")); return (false); } if (extendedPart >= 0) { nextseek = xstartsect = ltohi(part[extendedPart].relsect); xnumsect = ltohi(part[extendedPart].numsect); do { /* * If the seek would not cause us to change * position on the drive, then we're out of * extended partitions to examine. */ if (nextseek == lastseek) break; logicalDriveCount += numDrives; /* * Seek the next extended partition, and find * logical drives within it. */ if (lseek64(fd, nextseek * wbpb->bpb.bytes_per_sector, SEEK_SET) < 0 || read(fd, &extmboot, sizeof (extmboot)) != sizeof (extmboot)) { perror(gettext("Unable to read extended " "partition record")); return (false); } (void) memcpy(part, extmboot.parts, sizeof (part)); lastseek = nextseek; if (ltohs(extmboot.signature) != MBB_MAGIC) { (void) fprintf(stderr, gettext("Bad signature on " "extended partition\n")); return (false); } /* * Count up drives, and track where the next * extended partition is in case we need it. We * are expecting only one extended partition. If * there is more than one we'll only go to the * first one we see, but warn about ignoring. */ numDrives = 0; for (i = 0; i < FD_NUMPART; i++) { if (isDosDrive(part[i].systid)) { extndDrives[numDrives++] = i; continue; } else if (isDosExtended(part[i].systid)) { if (nextseek != lastseek) { /* * Already found an extended * partition in this table. */ (void) fprintf(stderr, gettext("WARNING: " "Ignoring unexpected " "additional extended " "partition")); continue; } nextseek = xstartsect + ltohi(part[i].relsect); continue; } } } while (drvnum > logicalDriveCount + numDrives); if (drvnum <= logicalDriveCount + numDrives) { /* * The number of logical drives we've found thus * far is enough to get us to the one we were * searching for. */ driveIndex = logicalDriveCount + numDrives - drvnum; *seekto = ltohi(part[extndDrives[driveIndex]].relsect) + lastseek; if (*seekto == lastseek) { (void) fprintf(stderr, gettext("Bogus FDISK entry? A logical " "drive starting at\nsector 0x%llx would " "collide with the\nFDISK information in " "that sector.\n"), *seekto); return (false); } else if (*seekto <= xstartsect || *seekto >= (xstartsect + xnumsect)) { (void) fprintf(stderr, gettext("Bogus FDISK entry? " "Logical drive start sector (0x%llx)\n" "not within extended partition! " "(Expected in range 0x%llx - 0x%llx)\n"), *seekto, xstartsect + 1, xstartsect + xnumsect - 1); return (false); } fill_bpb_sizes(wbpb, part, extndDrives[driveIndex], *seekto); *seekto *= wbpb->bpb.bytes_per_sector; FdiskFATsize = lookup_FAT_size( part[extndDrives[driveIndex]].systid); if (Verbose) (void) printf(gettext("Partition's offset: " "Sector 0x%llx.\n"), *seekto/wbpb->bpb.bytes_per_sector); if (lseek64(fd, *seekto, SEEK_SET) < 0) { (void) fprintf(stderr, gettext("Partition %s: "), pn); perror(""); return (false); } return (true); } else { /* * We ran out of extended dos partition * drives. The only hope now is to go * back to extra drives defined in the master * fdisk table. But we overwrote that table * already, so we must load it in again. */ logicalDriveCount += numDrives; (void) memcpy(part, mb.parts, sizeof (part)); } } /* * Still haven't found the drive, is it an extra * drive defined in the main FDISK table? */ if (drvnum <= logicalDriveCount + numExtraDrives) { driveIndex = logicalDriveCount + numExtraDrives - drvnum; *seekto = ltohi(part[extraDrives[driveIndex]].relsect); if (*seekto == 0) { (void) fprintf(stderr, gettext("Bogus FDISK entry? " "A partition starting\nat sector 0 would " "collide with the FDISK table!\n")); return (false); } fill_bpb_sizes(wbpb, part, extraDrives[driveIndex], *seekto); *seekto *= wbpb->bpb.bytes_per_sector; FdiskFATsize = lookup_FAT_size(part[extraDrives[driveIndex]].systid); if (Verbose) (void) printf(gettext("Partition's offset: " "Sector %llx.\n"), *seekto / wbpb->bpb.bytes_per_sector); if (lseek64(fd, *seekto, SEEK_SET) < 0) { (void) fprintf(stderr, gettext("Partition %s: "), pn); perror(""); return (false); } return (true); } (void) fprintf(stderr, gettext("No such logical drive\n")); return (false); } /* * seek_nofdisk * * User is asking us to trust them that they know best. * We basically won't do much seeking here, the only seeking we'll do * is if the 'hidden' parameter was given. */ static bool seek_nofdisk(int fd, bpb_t *wbpb, off64_t *seekto) { if (TotSize > 0xffff) wbpb->bpb.sectors_in_volume = 0; else wbpb->bpb.sectors_in_volume = (short)TotSize; wbpb->bpb.sectors_in_logical_volume = TotSize; *seekto = RelOffset * wbpb->bpb.bytes_per_sector; wbpb->bpb.hidden_sectors = RelOffset; wbpb->sunbpb.bs_offset_high = RelOffset >> 16; wbpb->sunbpb.bs_offset_low = RelOffset & 0xFFFF; if (Verbose) (void) printf(gettext("Requested offset: Sector %llx.\n"), *seekto/wbpb->bpb.bytes_per_sector); if (lseek64(fd, *seekto, SEEK_SET) < 0) { (void) fprintf(stderr, gettext("User specified start sector %lu"), RelOffset); perror(""); return (false); } return (true); } /* * set_fat_string * * Fill in the type string of the FAT */ static void set_fat_string(bpb_t *wbpb, int fatsize) { if (fatsize == 12) { (void) strncpy((char *)wbpb->ebpb.type, FAT12_TYPE_STRING, strlen(FAT12_TYPE_STRING)); } else if (fatsize == 16) { (void) strncpy((char *)wbpb->ebpb.type, FAT16_TYPE_STRING, strlen(FAT16_TYPE_STRING)); } else { (void) strncpy((char *)wbpb->ebpb.type, FAT32_TYPE_STRING, strlen(FAT32_TYPE_STRING)); } } /* * prepare_image_file * * Open the file that will hold the image (as opposed to the image * being written to the boot sector of an actual disk). */ static int prepare_image_file(const char *fn, bpb_t *wbpb) { int fd; char zerobyte = '\0'; if ((fd = open(fn, O_RDWR | O_CREAT | O_EXCL, 0666)) < 0) { perror(fn); exit(ERR_OS); } if (Imagesize == 5) { /* Disk image of a 1.2M floppy */ wbpb->bpb.sectors_in_volume = 2 * 80 * 15; wbpb->bpb.sectors_in_logical_volume = 2 * 80 * 15; wbpb->bpb.sectors_per_track = 15; wbpb->bpb.heads = 2; wbpb->bpb.media = 0xF9; wbpb->bpb.num_root_entries = 224; wbpb->bpb.sectors_per_cluster = 1; wbpb->bpb.sectors_per_fat = 7; } else { /* Disk image of a 1.44M floppy */ wbpb->bpb.sectors_in_volume = 2 * 80 * 18; wbpb->bpb.sectors_in_logical_volume = 2 * 80 * 18; wbpb->bpb.sectors_per_track = 18; wbpb->bpb.heads = 2; wbpb->bpb.media = 0xF0; wbpb->bpb.num_root_entries = 224; wbpb->bpb.sectors_per_cluster = 1; wbpb->bpb.sectors_per_fat = 9; } /* * Make a holey file, with length the exact * size of the floppy image. */ if (lseek(fd, (wbpb->bpb.sectors_in_volume * MINBPS)-1, SEEK_SET) < 0) { (void) close(fd); perror(fn); exit(ERR_OS); } if (write(fd, &zerobyte, 1) != 1) { (void) close(fd); perror(fn); exit(ERR_OS); } if (lseek(fd, 0, SEEK_SET) < 0) { (void) close(fd); perror(fn); exit(ERR_OS); } Fatentsize = 12; /* Size of fat entry in bits */ set_fat_string(wbpb, Fatentsize); wbpb->ebpb.phys_drive_num = 0; wbpb->sunbpb.bs_offset_high = 0; wbpb->sunbpb.bs_offset_low = 0; return (fd); } /* * partn_lecture * * Give a brief sermon on dev_name user should pass to * the program from the command line. * */ static void partn_lecture(char *dn) { (void) fprintf(stderr, gettext("\nDevice %s was assumed to be a diskette.\n" "A diskette specific operation failed on this device.\n" "If the device is a hard disk, provide the name of " "the full physical disk,\n" "and qualify that name with a logical drive specifier.\n\n" "Hint: the device is usually something similar to\n\n" "/dev/rdsk/c0d0p0 or /dev/rdsk/c0t0d0p0 (x86)\n" "/dev/rdsk/c0t5d0s2 (sparc)\n\n" "The drive specifier is appended to the device name." " For example:\n\n" "/dev/rdsk/c0t5d0s2:c or /dev/rdsk/c0d0p0:boot\n\n"), dn); } static void warn_funky_floppy(void) { (void) fprintf(stderr, gettext("Use the 'nofdisk' option to create file systems\n" "on non-standard floppies.\n\n")); exit(ERR_FAIL); } static void warn_funky_fatsize(void) { (void) fprintf(stderr, gettext("Non-standard FAT size requested for floppy.\n" "The 'nofdisk' option must be used to\n" "override the 12 bit floppy default.\n\n")); exit(ERR_FAIL); } static void floppy_bpb_fillin(bpb_t *wbpb, int diam, int hds, int spt) { switch (diam) { case 3: switch (hds) { case 2: switch (spt) { case 9: wbpb->bpb.media = 0xF9; wbpb->bpb.num_root_entries = 112; wbpb->bpb.sectors_per_cluster = 2; wbpb->bpb.sectors_per_fat = 3; break; case 18: wbpb->bpb.media = 0xF0; wbpb->bpb.num_root_entries = 224; wbpb->bpb.sectors_per_cluster = 1; wbpb->bpb.sectors_per_fat = 9; break; case 36: wbpb->bpb.media = 0xF0; wbpb->bpb.num_root_entries = 240; wbpb->bpb.sectors_per_cluster = 2; wbpb->bpb.sectors_per_fat = 9; break; default: (void) fprintf(stderr, gettext("Unknown diskette parameters! " "3.5'' diskette with %d heads " "and %d sectors/track.\n"), hds, spt); warn_funky_floppy(); } break; case 1: default: (void) fprintf(stderr, gettext("Unknown diskette parameters! " "3.5'' diskette with %d heads "), hds); warn_funky_floppy(); } break; case 5: switch (hds) { case 2: switch (spt) { case 15: wbpb->bpb.media = 0xF9; wbpb->bpb.num_root_entries = 224; wbpb->bpb.sectors_per_cluster = 1; wbpb->bpb.sectors_per_fat = 7; break; case 9: wbpb->bpb.media = 0xFD; wbpb->bpb.num_root_entries = 112; wbpb->bpb.sectors_per_cluster = 2; wbpb->bpb.sectors_per_fat = 2; break; case 8: wbpb->bpb.media = 0xFF; wbpb->bpb.num_root_entries = 112; wbpb->bpb.sectors_per_cluster = 1; wbpb->bpb.sectors_per_fat = 2; break; default: (void) fprintf(stderr, gettext("Unknown diskette parameters! " "5.25'' diskette with %d heads " "and %d sectors/track.\n"), hds, spt); warn_funky_floppy(); } break; case 1: switch (spt) { case 9: wbpb->bpb.media = 0xFC; wbpb->bpb.num_root_entries = 64; wbpb->bpb.sectors_per_cluster = 1; wbpb->bpb.sectors_per_fat = 2; break; case 8: wbpb->bpb.media = 0xFE; wbpb->bpb.num_root_entries = 64; wbpb->bpb.sectors_per_cluster = 1; wbpb->bpb.sectors_per_fat = 1; break; default: (void) fprintf(stderr, gettext("Unknown diskette parameters! " "5.25'' diskette with %d heads " "and %d sectors/track.\n"), hds, spt); warn_funky_floppy(); } break; default: (void) fprintf(stderr, gettext("Unknown diskette parameters! " "5.25'' diskette with %d heads."), hds); warn_funky_floppy(); } break; default: (void) fprintf(stderr, gettext("\nUnknown diskette type. Only know about " "5.25'' and 3.5'' diskettes.\n")); warn_funky_floppy(); } } /* * lookup_floppy * * Look up a media descriptor byte and other crucial BPB values * based on floppy characteristics. */ static void lookup_floppy(struct fd_char *fdchar, bpb_t *wbpb) { ulong_t tsize; ulong_t cyls, spt, hds, diam; cyls = fdchar->fdc_ncyl; diam = fdchar->fdc_medium; spt = fdchar->fdc_secptrack; hds = fdchar->fdc_nhead; tsize = cyls * hds * spt; if (GetFsParams) TotSize = tsize; if (GetSize) { wbpb->bpb.sectors_in_logical_volume = tsize; } else { wbpb->bpb.sectors_in_logical_volume = warn_mismatch( gettext("length of partition (in sectors)"), gettext("FDIOGCHAR call"), tsize, TotSize); } wbpb->bpb.sectors_in_volume = (short)wbpb->bpb.sectors_in_logical_volume; if (GetSPT) { wbpb->bpb.sectors_per_track = spt; } else { wbpb->bpb.sectors_per_track = warn_mismatch( gettext("sectors per track"), gettext("FDIOGCHAR call"), spt, SecPerTrk); spt = wbpb->bpb.sectors_per_track; } if (GetTPC) { wbpb->bpb.heads = hds; } else { wbpb->bpb.heads = warn_mismatch( gettext("number of heads"), gettext("FDIOGCHAR call"), hds, TrkPerCyl); hds = wbpb->bpb.heads; } Fatentsize = 12; /* Size of fat entry in bits */ if (!GetBPF && BitsPerFAT != Fatentsize) { warn_funky_fatsize(); } set_fat_string(wbpb, Fatentsize); wbpb->ebpb.phys_drive_num = 0; wbpb->bpb.hidden_sectors = 0; wbpb->sunbpb.bs_offset_high = 0; wbpb->sunbpb.bs_offset_low = 0; floppy_bpb_fillin(wbpb, diam, hds, spt); } /* * compute_cluster_size * * Compute an acceptable sectors/cluster value. * * Based on values from the Hardware White Paper * from Microsoft. * "Microsoft Extensible Firmware Initiative * FAT32 File System Specification * FAT: General Overview of On-Disk Format" * * Version 1.03, December 6, 2000 * */ static void compute_cluster_size(bpb_t *wbpb) { ulong_t volsize; ulong_t spc; ulong_t rds, scale, tmpval1, tmpval2; ulong_t fatsz; int newfat = 16; #define FAT12_MAX_CLUSTERS 0x0FF4 #define FAT16_MAX_CLUSTERS 0xFFF4 #define FAT32_MAX_CLUSTERS 0x0FFFFFF0 #define FAT32_SUGGESTED_NCLUST 0x400000 /* compute volume size in sectors. */ volsize = wbpb->bpb.sectors_in_volume ? wbpb->bpb.sectors_in_volume : wbpb->bpb.sectors_in_logical_volume; volsize -= wbpb->bpb.resv_sectors; if (GetSPC) { /* * User indicated what sort of FAT to create, * make sure it is valid with the given size * and compute an SPC value. */ if (!MakeFAT32) { /* FAT16 */ /* volsize is in sectors */ if (volsize < FAT12_MAX_CLUSTERS) { (void) fprintf(stderr, gettext("Requested size is too " "small for FAT16.\n")); exit(ERR_FAIL); } /* SPC must be a power of 2 */ for (spc = 1; spc <= 64; spc = spc * 2) { if (volsize < spc * FAT16_MAX_CLUSTERS) break; } if (volsize > (spc * FAT16_MAX_CLUSTERS)) { (void) fprintf(stderr, gettext("Requested size is too " "large for FAT16.\n")); exit(ERR_FAIL); } } else { /* FAT32 */ /* volsize is in sectors */ if (volsize <= FAT16_MAX_CLUSTERS) { (void) fprintf(stderr, gettext("Requested size is too " "small for FAT32.\n")); exit(ERR_FAIL); } /* SPC must be a power of 2 */ for (spc = 1; spc <= 64; spc = spc * 2) { if (volsize < (spc * FAT32_SUGGESTED_NCLUST)) break; } if (volsize > (spc * FAT32_MAX_CLUSTERS)) { (void) fprintf(stderr, gettext("Requested size is too " "large for FAT32.\n")); exit(ERR_FAIL); } } } else { /* * User gave the SPC as an explicit option, * make sure it will work with the requested * volume size. */ int nclust; spc = SecPerClust; nclust = volsize / spc; if (nclust <= FAT16_MAX_CLUSTERS && MakeFAT32) { (void) fprintf(stderr, gettext("Requested size is too " "small for FAT32.\n")); exit(ERR_FAIL); } if (!MakeFAT32) { /* Determine if FAT12 or FAT16 */ if (nclust < FAT12_MAX_CLUSTERS) newfat = 12; else if (nclust < FAT16_MAX_CLUSTERS) newfat = 16; else { (void) fprintf(stderr, gettext("Requested size is too " "small for FAT32.\n")); exit(ERR_FAIL); } } } /* * RootDirSectors = ((BPB_RootEntCnt * 32) + * (BPB_BytsPerSec - 1)) / BPB_BytsPerSec; */ rds = ((wbpb->bpb.num_root_entries * 32) + (wbpb->bpb.bytes_per_sector - 1)) / wbpb->bpb.bytes_per_sector; if (GetBPF) { if (MakeFAT32) Fatentsize = 32; else Fatentsize = newfat; } else { Fatentsize = BitsPerFAT; if (Fatentsize == 12 && (volsize - rds) >= DOS_F12MAXC * spc) { /* * If we don't have an input TTY, or we aren't * really doing anything, then don't ask * questions. Assume a yes answer to any * questions we would ask. */ if (Notreally || !isatty(fileno(stdin))) { (void) printf( gettext("Volume too large for 12 bit FAT," " increasing to 16 bit FAT size.\n")); (void) fflush(stdout); Fatentsize = 16; } else { (void) printf( gettext("Volume too large for a 12 bit FAT.\n" "Increase to 16 bit FAT " "and continue (y/n)? ")); (void) fflush(stdout); if (yes()) Fatentsize = 16; else exit(ERR_USER); } } } wbpb->bpb.sectors_per_cluster = spc; if (!GetFsParams && FdiskFATsize < 0) { (void) printf( gettext("Cannot verify chosen/computed FAT " "entry size (%d bits) with FDISK table.\n" "FDISK table has an unknown file system " "type for this device. Giving up...\n"), Fatentsize); exit(ERR_INVAL); } else if (!GetFsParams && FdiskFATsize && FdiskFATsize != Fatentsize) { (void) printf( gettext("Chosen/computed FAT entry size (%d bits) " "does not match FDISK table (%d bits).\n"), Fatentsize, FdiskFATsize); (void) printf( gettext("Use -o fat=%d to build a FAT " "that matches the FDISK entry.\n"), FdiskFATsize); exit(ERR_INVAL); } set_fat_string(wbpb, Fatentsize); /* * Compute the FAT sizes according to algorithm from Microsoft: * * RootDirSectors = ((BPB_RootEntCnt * 32) + * (BPB_BytsPerSec - 1)) / BPB_BytsPerSec; * TmpVal1 = DskSize - (BPB_ResvdSecCnt + RootDirSectors); * TmpVal2 = (256 * BPB_SecPerClus) + BPB_NumFATs; * If (FATType == FAT32) * TmpVal2 = TmpVal2 / 2; * FATSz = (TMPVal1 + (TmpVal2 - 1)) / TmpVal2; * If (FATType == FAT32) { * BPB_FATSz16 = 0; * BPB_FATSz32 = FATSz; * } else { * BPB_FATSz16 = LOWORD(FATSz); * // there is no BPB_FATSz32 in a FAT16 BPB * } * * The comment from Microsoft [fatgen103, page 21] is that we should * not think too much about this algorithm and that it works. * However, they neglected to mention, it does work with a 512B sector * size. When using different sector sizes we need to change the * scale factor from 256. Apparently the scale factor is actually * meant to be half of the sector size. */ scale = wbpb->bpb.bytes_per_sector / 2; tmpval1 = volsize - (wbpb->bpb.resv_sectors + rds); tmpval2 = (scale * wbpb->bpb.sectors_per_cluster) + wbpb->bpb.num_fats; if (Fatentsize == 32) tmpval2 = tmpval2 / 2; fatsz = (tmpval1 + (tmpval2 - 1)) / tmpval2; /* Compute a sector/fat figure */ switch (Fatentsize) { case 32: wbpb->bpb.sectors_per_fat = 0; wbpb->bpb32.big_sectors_per_fat = fatsz; if (Verbose) (void) printf("%s: Sectors per FAT32 = %d\n", __func__, wbpb->bpb32.big_sectors_per_fat); break; case 12: default: /* 16 bit FAT */ wbpb->bpb.sectors_per_fat = (ushort_t)(fatsz & 0x0000FFFF); if (Verbose) (void) printf("%s: Sectors per FAT16 = %d\n", __func__, wbpb->bpb.sectors_per_fat); break; } } static void find_fixed_details(int fd, bpb_t *wbpb) { struct dk_geom dginfo; /* * Look up the last remaining bits of info we need * that is specific to the hard drive using a disk ioctl. */ if (GetSPT || GetTPC) { if (ioctl(fd, DKIOCG_VIRTGEOM, &dginfo) == -1 && ioctl(fd, DKIOCG_PHYGEOM, &dginfo) == -1 && ioctl(fd, DKIOCGGEOM, &dginfo) == -1) { (void) close(fd); perror( gettext("Drive geometry lookup (need " "tracks/cylinder and/or sectors/track")); exit(ERR_OS); } } wbpb->bpb.heads = (GetTPC ? dginfo.dkg_nhead : TrkPerCyl); wbpb->bpb.sectors_per_track = (GetSPT ? dginfo.dkg_nsect : SecPerTrk); if (Verbose) { if (GetTPC) { (void) printf( gettext("DKIOCG determined number of heads = %d\n"), dginfo.dkg_nhead); } if (GetSPT) { (void) printf( gettext("DKIOCG determined sectors per track" " = %d\n"), dginfo.dkg_nsect); } } /* * XXX - MAY need an additional flag (or flags) to set media * and physical drive number fields. That in the case of weird * floppies that have to go through 'nofdisk' route for formatting. */ wbpb->bpb.media = 0xF8; if (MakeFAT32) wbpb->bpb.num_root_entries = 0; else wbpb->bpb.num_root_entries = 512; wbpb->ebpb.phys_drive_num = 0x80; compute_cluster_size(wbpb); } static void compute_file_area_size(bpb_t *wbpb) { int FATSz; int TotSec; int DataSec; int RootDirSectors = ((wbpb->bpb.num_root_entries * 32) + (wbpb->bpb.bytes_per_sector - 1)) / wbpb->bpb.bytes_per_sector; if (wbpb->bpb.sectors_per_fat) { /* * Good old FAT12 or FAT16 */ FATSz = wbpb->bpb.sectors_per_fat; TotSec = wbpb->bpb.sectors_in_volume; } else { /* * FAT32 */ FATSz = wbpb->bpb32.big_sectors_per_fat; TotSec = wbpb->bpb.sectors_in_logical_volume; } DataSec = TotSec - (wbpb->bpb.resv_sectors + (wbpb->bpb.num_fats * FATSz) + RootDirSectors); /* * Now change sectors to clusters */ TotalClusters = DataSec / wbpb->bpb.sectors_per_cluster; if (Verbose) (void) printf(gettext("Disk has a file area of %lu " "allocation units,\neach with %d sectors = %lu " "bytes.\n"), TotalClusters, wbpb->bpb.sectors_per_cluster, TotalClusters * wbpb->bpb.sectors_per_cluster * wbpb->bpb.bytes_per_sector); } #ifdef _BIG_ENDIAN /* * swap_pack_{bpb,bpb32,sebpb}cpy * * If not on an x86 we assume the structures making up the bpb * were not packed and that longs and shorts need to be byte swapped * (we've kept everything in host order up until now). A new architecture * might not need to swap or might not need to pack, in which case * new routines will have to be written. Of course if an architecture * supports both packing and little-endian host order, it can follow the * same path as the x86 code. */ static void swap_pack_bpbcpy(struct _boot_sector *bsp, bpb_t *wbpb) { uchar_t *fillp; fillp = (uchar_t *)&(bsp->bs_filler[ORIG_BPB_START_INDEX]); store_16_bits(&fillp, wbpb->bpb.bytes_per_sector); *fillp++ = wbpb->bpb.sectors_per_cluster; store_16_bits(&fillp, wbpb->bpb.resv_sectors); *fillp++ = wbpb->bpb.num_fats; store_16_bits(&fillp, wbpb->bpb.num_root_entries); store_16_bits(&fillp, wbpb->bpb.sectors_in_volume); *fillp++ = wbpb->bpb.media; store_16_bits(&fillp, wbpb->bpb.sectors_per_fat); store_16_bits(&fillp, wbpb->bpb.sectors_per_track); store_16_bits(&fillp, wbpb->bpb.heads); store_32_bits(&fillp, wbpb->bpb.hidden_sectors); store_32_bits(&fillp, wbpb->bpb.sectors_in_logical_volume); *fillp++ = wbpb->ebpb.phys_drive_num; *fillp++ = wbpb->ebpb.reserved; *fillp++ = wbpb->ebpb.ext_signature; store_32_bits(&fillp, wbpb->ebpb.volume_id); (void) strncpy((char *)fillp, (char *)wbpb->ebpb.volume_label, 11); fillp += 11; (void) strncpy((char *)fillp, (char *)wbpb->ebpb.type, 8); } static void swap_pack_bpb32cpy(struct _boot_sector32 *bsp, bpb_t *wbpb) { uchar_t *fillp; int r; fillp = (uchar_t *)&(bsp->bs_filler[ORIG_BPB_START_INDEX]); store_16_bits(&fillp, wbpb->bpb.bytes_per_sector); *fillp++ = wbpb->bpb.sectors_per_cluster; store_16_bits(&fillp, wbpb->bpb.resv_sectors); *fillp++ = wbpb->bpb.num_fats; store_16_bits(&fillp, wbpb->bpb.num_root_entries); store_16_bits(&fillp, wbpb->bpb.sectors_in_volume); *fillp++ = wbpb->bpb.media; store_16_bits(&fillp, wbpb->bpb.sectors_per_fat); store_16_bits(&fillp, wbpb->bpb.sectors_per_track); store_16_bits(&fillp, wbpb->bpb.heads); store_32_bits(&fillp, wbpb->bpb.hidden_sectors); store_32_bits(&fillp, wbpb->bpb.sectors_in_logical_volume); store_32_bits(&fillp, wbpb->bpb32.big_sectors_per_fat); store_16_bits(&fillp, wbpb->bpb32.ext_flags); *fillp++ = wbpb->bpb32.fs_vers_lo; *fillp++ = wbpb->bpb32.fs_vers_hi; store_32_bits(&fillp, wbpb->bpb32.root_dir_clust); store_16_bits(&fillp, wbpb->bpb32.fsinfosec); store_16_bits(&fillp, wbpb->bpb32.backupboot); for (r = 0; r < 6; r++) store_16_bits(&fillp, wbpb->bpb32.reserved[r]); *fillp++ = wbpb->ebpb.phys_drive_num; *fillp++ = wbpb->ebpb.reserved; *fillp++ = wbpb->ebpb.ext_signature; store_32_bits(&fillp, wbpb->ebpb.volume_id); (void) strncpy((char *)fillp, (char *)wbpb->ebpb.volume_label, 11); fillp += 11; (void) strncpy((char *)fillp, (char *)wbpb->ebpb.type, 8); } static void swap_pack_sebpbcpy(struct _boot_sector *bsp, bpb_t *wbpb) { uchar_t *fillp; fillp = bsp->bs_sun_bpb; store_16_bits(&fillp, wbpb->sunbpb.bs_offset_high); store_16_bits(&fillp, wbpb->sunbpb.bs_offset_low); } static void swap_pack_grabsebpb(bpb_t *wbpb, struct _boot_sector *bsp) { uchar_t *grabp; grabp = bsp->bs_sun_bpb; ((uchar_t *)&(wbpb->sunbpb.bs_offset_high))[1] = *grabp++; ((uchar_t *)&(wbpb->sunbpb.bs_offset_high))[0] = *grabp++; ((uchar_t *)&(wbpb->sunbpb.bs_offset_low))[1] = *grabp++; ((uchar_t *)&(wbpb->sunbpb.bs_offset_low))[0] = *grabp++; } #endif /* ! _BIG_ENDIAN */ static void dashm_bail(int fd) { (void) fprintf(stderr, gettext("This media does not appear to be " "formatted with a FAT file system.\n")); (void) close(fd); exit(ERR_INVAL); } /* * read_existing_bpb * * Grab the first sector, which we think is a bios parameter block. * If it looks bad, bail. Otherwise fill in the parameter struct * fields that matter. */ static void read_existing_bpb(int fd, bpb_t *wbpb) { boot_sector_t ubpb; size_t bps; bps = wbpb->bpb.bytes_per_sector; if (read(fd, ubpb.buf, bps) < (ssize_t)bps) { perror(gettext("Read BIOS parameter block " "from previously formatted media")); (void) close(fd); exit(ERR_INVAL); } if (ltohs(ubpb.mb.signature) != BOOTSECSIG) { dashm_bail(fd); } #ifdef _LITTLE_ENDIAN (void) memcpy(&(wbpb->bpb), &(ubpb.bs.bs_front.bs_bpb), sizeof (wbpb->bpb)); (void) memcpy(&(wbpb->ebpb), &(ubpb.bs.bs_ebpb), sizeof (wbpb->ebpb)); #else swap_pack_grabbpb(wbpb, &(ubpb.bs)); #endif if (SunBPBfields) { #ifdef _LITTLE_ENDIAN (void) memcpy(&(wbpb->sunbpb), &(ubpb.bs.bs_sebpb), sizeof (wbpb->sunbpb)); #else swap_pack_grabsebpb(wbpb, &(ubpb.bs)); #endif } if (!is_sector_size_valid(wbpb->bpb.bytes_per_sector)) { (void) close(fd); err(ERR_INVAL, gettext("Invalid bytes/sector (%u): must be 512, 1024, " "2048 or 4096\n"), wbpb->bpb.bytes_per_sector); } bps = wbpb->bpb.bytes_per_sector; if (!(ISP2(wbpb->bpb.sectors_per_cluster) && IN_RANGE(wbpb->bpb.sectors_per_cluster, 1, 128))) { (void) fprintf(stderr, gettext("Bogus sectors per cluster value.\n")); (void) fprintf(stderr, gettext("The device name may be missing a " "logical drive specifier.\n")); (void) close(fd); exit(ERR_INVAL); } if (wbpb->bpb.sectors_per_fat == 0) { #ifdef _LITTLE_ENDIAN (void) memcpy(&(wbpb->bpb32), &(ubpb.bs32.bs_bpb32), sizeof (wbpb->bpb32)); #else swap_pack_grab32bpb(wbpb, &(ubpb.bs)); #endif compute_file_area_size(wbpb); if ((wbpb->bpb32.big_sectors_per_fat * bps / 4) >= TotalClusters) { MakeFAT32 = 1; } else { dashm_bail(fd); } } else { compute_file_area_size(wbpb); } } /* * compare_existing_with_computed * * We use this function when we the user specifies the -m option. * We compute and look up things like we would if they had asked * us to make the fs, and compare that to what's already layed down * in the existing fs. If there's a difference we can tell them what * options to specify in order to reproduce their existing layout. * Note that they still may not get an exact duplicate, because we * don't, for example, preserve their existing boot code. We think * we've got all the fields that matter covered, though. * * XXX - We're basically ignoring sbpb at this point. I'm unsure * if we'll ever care about those fields, in terms of the -m option. */ static void compare_existing_with_computed(int fd, char *suffix, bpb_t *wbpb, int *prtsize, int *prtspc, int *prtbpf, int *prtnsect, int *prtntrk, int *prtfdisk, int *prthidden, int *prtrsrvd, int *dashos) { struct dk_geom dginfo; struct fd_char fdchar; bpb_t compare; int fd_ioctl_worked = 0; int fatents; /* * For all non-floppy cases we expect to find a 16-bit FAT */ int expectfatsize = 16; compare = *wbpb; if (!suffix) { if (ioctl(fd, FDIOGCHAR, &fdchar) != -1) { expectfatsize = 12; fd_ioctl_worked++; } } if (fd_ioctl_worked) { #ifdef sparc fdchar.fdc_medium = 3; #endif GetSize = GetSPT = GetSPC = GetTPC = GetBPF = 1; lookup_floppy(&fdchar, &compare); if (compare.bpb.heads != wbpb->bpb.heads) { (*prtntrk)++; (*dashos)++; } if (compare.bpb.sectors_per_track != wbpb->bpb.sectors_per_track) { (*prtnsect)++; (*dashos)++; } } else { int dk_ioctl_worked = 1; if (!suffix) { (*prtfdisk)++; (*prtsize)++; *dashos += 2; } if (ioctl(fd, DKIOCG_VIRTGEOM, &dginfo) == -1 && ioctl(fd, DKIOCG_PHYGEOM, &dginfo) == -1 && ioctl(fd, DKIOCGGEOM, &dginfo) == -1) { *prtnsect = *prtntrk = 1; *dashos += 2; dk_ioctl_worked = 0; } if (dk_ioctl_worked) { if (dginfo.dkg_nhead != wbpb->bpb.heads) { (*prtntrk)++; (*dashos)++; } if (dginfo.dkg_nsect != wbpb->bpb.sectors_per_track) { (*prtnsect)++; (*dashos)++; } } GetBPF = GetSPC = 1; compute_cluster_size(&compare); } if (!*prtfdisk && TotSize != wbpb->bpb.sectors_in_volume && TotSize != wbpb->bpb.sectors_in_logical_volume) { (*dashos)++; (*prtsize)++; } if (compare.bpb.sectors_per_cluster != wbpb->bpb.sectors_per_cluster) { (*dashos)++; (*prtspc)++; } if (compare.bpb.hidden_sectors != wbpb->bpb.hidden_sectors) { (*dashos)++; (*prthidden)++; } if (compare.bpb.resv_sectors != wbpb->bpb.resv_sectors) { (*dashos)++; (*prtrsrvd)++; } /* * Compute approximate Fatentsize. It's approximate because the * size of the FAT may not be exactly a multiple of the number of * clusters. It should be close, though. */ if (MakeFAT32) { Fatentsize = 32; (*dashos)++; (*prtbpf)++; } else { fatents = wbpb->bpb.sectors_per_fat * wbpb->bpb.bytes_per_sector * 2 / 3; if (fatents >= TotalClusters && wbpb->ebpb.type[4] == '2') Fatentsize = 12; else Fatentsize = 16; if (Fatentsize != expectfatsize) { (*dashos)++; (*prtbpf)++; } } } static void print_reproducing_command(int fd, char *actualdisk, char *suffix, bpb_t *wbpb) { int needcomma = 0; int prthidden = 0; int prtrsrvd = 0; int prtfdisk = 0; int prtnsect = 0; int prtntrk = 0; int prtsize = 0; int prtbpf = 0; int prtspc = 0; int dashos = 0; int ll, i; compare_existing_with_computed(fd, suffix, wbpb, &prtsize, &prtspc, &prtbpf, &prtnsect, &prtntrk, &prtfdisk, &prthidden, &prtrsrvd, &dashos); /* * Print out the command line they can use to reproduce the * file system. */ (void) printf("mkfs -F pcfs"); ll = MIN(11, (int)strlen((char *)wbpb->ebpb.volume_label)); /* * First, eliminate trailing spaces. Now compare the name against * our default label. If there's a match we don't need to print * any label info. */ i = ll; while (wbpb->ebpb.volume_label[--i] == ' ') ; ll = i; if (ll == strlen(DEFAULT_LABEL) - 1) { char cmpbuf[11]; (void) strcpy(cmpbuf, DEFAULT_LABEL); for (i = ll; i >= 0; i--) { if (cmpbuf[i] != toupper((int)(wbpb->ebpb.volume_label[i]))) { break; } } if (i < 0) ll = i; } if (ll >= 0) { (void) printf(" -o "); (void) printf("b=\""); for (i = 0; i <= ll; i++) { (void) printf("%c", wbpb->ebpb.volume_label[i]); } (void) printf("\""); needcomma++; } else if (dashos) { (void) printf(" -o "); } #define NEXT_DASH_O dashos--; needcomma++; continue while (dashos) { if (needcomma) { (void) printf(","); needcomma = 0; } if (prtfdisk) { (void) printf("nofdisk"); prtfdisk--; NEXT_DASH_O; } if (prtsize) { (void) printf("size=%u", wbpb->bpb.sectors_in_volume ? wbpb->bpb.sectors_in_volume : wbpb->bpb.sectors_in_logical_volume); prtsize--; NEXT_DASH_O; } if (prtnsect) { (void) printf("nsect=%d", wbpb->bpb.sectors_per_track); prtnsect--; NEXT_DASH_O; } if (prtspc) { (void) printf("spc=%d", wbpb->bpb.sectors_per_cluster); prtspc--; NEXT_DASH_O; } if (prtntrk) { (void) printf("ntrack=%d", wbpb->bpb.heads); prtntrk--; NEXT_DASH_O; } if (prtbpf) { (void) printf("fat=%d", Fatentsize); prtbpf--; NEXT_DASH_O; } if (prthidden) { (void) printf("hidden=%u", wbpb->bpb.hidden_sectors); prthidden--; NEXT_DASH_O; } if (prtrsrvd) { (void) printf("reserve=%d", wbpb->bpb.resv_sectors); prtrsrvd--; NEXT_DASH_O; } } (void) printf(" %s%c%c\n", actualdisk, suffix ? ':' : '\0', suffix ? *suffix : '\0'); } /* * open_and_examine * * Open the requested 'dev_name'. Seek to point where * we'd expect to find boot sectors, etc., based on any ':partition' * attachments to the dev_name. * * Examine the fields of any existing boot sector and display best * approximation of how this fs could be reproduced with this command. */ static int open_and_examine(char *dn, bpb_t *wbpb) { struct stat di; off64_t ignored; char *actualdisk = NULL; char *suffix = NULL; int fd, rv; size_t ssize; if (Verbose) (void) printf(gettext("Opening destination device/file.\n")); actualdisk = stat_actual_disk(dn, &di, &suffix); /* * Destination exists, now find more about it. */ if (!(S_ISCHR(di.st_mode))) { (void) fprintf(stderr, gettext("\n%s: device name must be a " "character special device.\n"), actualdisk); exit(ERR_OS); } else if ((fd = open(actualdisk, O_RDWR)) < 0) { perror(actualdisk); exit(ERR_OS); } /* * Get the media sector size. */ rv = get_media_sector_size(fd, &ssize); if (rv != 0) { int e = errno; (void) close(fd); errc(ERR_OS, e, gettext("failed to obtain sector size for %s"), actualdisk); } if (!is_sector_size_valid(ssize)) { (void) close(fd); err(ERR_OS, gettext("Invalid bytes/sector (%zu): must be 512, 1024, " "2048 or 4096\n"), ssize); } wbpb->bpb.bytes_per_sector = ssize; /* * Find appropriate partition if we were requested to do so. */ if (suffix && !(seek_partn(fd, suffix, wbpb, &ignored))) { (void) close(fd); exit(ERR_OS); } read_existing_bpb(fd, wbpb); print_reproducing_command(fd, actualdisk, suffix, wbpb); return (fd); } /* * getdiskinfo * * Extracts information about disk path in dn. We need to return both a * file descriptor and the device's suffix. * Secondarily, we need to detect the FAT type and size when dealing with * GPT partitions. */ static void getdiskinfo(const char *dn, char **actualdisk, char **suffix) { struct stat di; int rv, fd, reserved; dk_gpt_t *gpt = NULL; *actualdisk = stat_actual_disk(dn, &di, suffix); /* * Destination exists, now find more about it. */ if (!(S_ISCHR(di.st_mode))) { (void) fprintf(stderr, gettext("Device name must indicate a " "character special device: %s\n"), *actualdisk); exit(ERR_OS); } else if ((fd = open(*actualdisk, O_RDWR)) < 0) { err(ERR_OS, "%s: failed to open disk device %s", __func__, *actualdisk); } rv = efi_alloc_and_read(fd, &gpt); /* * We should see only VT_EINVAL, VT_EIO and VT_ERROR. * VT_EINVAL is for the case there is no GPT label. * VT_ERROR will happen if device does no support the ioctl, so * we will exit only in case of VT_EIO and unknown value of rv. */ if (rv < 0 && rv != VT_EINVAL && rv != VT_ERROR) { switch (rv) { case VT_EIO: (void) fprintf(stderr, gettext("IO Error reading EFI label\n")); break; default: (void) fprintf(stderr, gettext("Unknown Error %d reading EFI label\n"), rv); break; } (void) close(fd); exit(ERR_OS); } if (rv >= 0) { DontUseFdisk = 1; if (*suffix != NULL) { (void) fprintf(stderr, gettext("Can not use drive specifier \"%s\" with " "GPT partitioning.\n"), *suffix); efi_free(gpt); (void) close(fd); exit(ERR_OS); } /* Can not use whole disk, 7 is GPT minor node "wd" */ if (rv == 7) { (void) fprintf(stderr, gettext("Device name must indicate a " "partition: %s\n"), *actualdisk); efi_free(gpt); (void) close(fd); exit(ERR_OS); } if (GetSize == 1) { TotSize = gpt->efi_parts[rv].p_size; GetSize = 0; } if (GetBPF == 1) { if (GetResrvd == 1) { /* FAT32 has 32 reserved sectors */ reserved = 32; } else { reserved = Resrvd; } /* * The type of FAT is determined by the size of * the partition - reserved sectors. * The calculation is based on logic used in * compute_cluster_size() and therefore we will not * get into error situation when * compute_cluster_size() will be called. */ if (TotSize - reserved < FAT16_MAX_CLUSTERS) { if (GetResrvd == 1) reserved = 1; if (TotSize - reserved < FAT12_MAX_CLUSTERS) { int spc; MakeFAT32 = 0; Fatentsize = 12; /* * compute sectors per cluster * for fat12 */ for (spc = 1; spc <= 64; spc = spc * 2) { if (TotSize - reserved < spc * FAT12_MAX_CLUSTERS) break; } if (GetSPC == 1) { GetSPC = 0; SecPerClust = spc; } } else { MakeFAT32 = 0; Fatentsize = 16; } } else { MakeFAT32 = 1; Fatentsize = 32; Resrvd = reserved; GetResrvd = 0; } } efi_free(gpt); } (void) close(fd); } static void prepare_wbpb(const char *dn, bpb_t *wbpb, char **actualdisk, char **suffix) { /* * We hold these truths to be self evident, all BPBs we create * will have these values in these fields. */ wbpb->bpb.num_fats = 2; /* Set value for prepare_image_file() */ wbpb->bpb.bytes_per_sector = MINBPS; /* Collect info about device */ if (!Outputtofile) getdiskinfo(dn, actualdisk, suffix); /* * Assign or use supplied numbers for hidden and * reserved sectors in the file system. */ if (GetResrvd) if (MakeFAT32) wbpb->bpb.resv_sectors = 32; else wbpb->bpb.resv_sectors = 1; else wbpb->bpb.resv_sectors = Resrvd; wbpb->ebpb.ext_signature = BOOTSIG; /* Magic number for modern format */ wbpb->ebpb.volume_id = 0; if (MakeFAT32) fill_fat32_bpb(wbpb); } /* * open_and_seek * * Open the requested 'dev_name'. Seek to point where * we'll write boot sectors, etc., based on any ':partition' * attachments to the dev_name. * * By the time we are finished here, the entire BPB will be * filled in, excepting the volume label. */ static int open_and_seek(const char *dn, bpb_t *wbpb, off64_t *seekto) { struct fd_char fdchar; struct dk_geom dg; char *actualdisk = NULL; char *suffix = NULL; size_t size = 0; int fd, rv; if (Verbose) (void) printf(gettext("Opening destination device/file.\n")); prepare_wbpb(dn, wbpb, &actualdisk, &suffix); /* * If all output goes to a simple file, call a routine to setup * that scenario. Otherwise, try to find the device. */ if (Outputtofile) return (prepare_image_file(dn, wbpb)); fd = open(actualdisk, O_RDWR); if (fd < 0) { err(ERR_OS, "Failed to open disk device %s", actualdisk); } /* * Check the media sector size */ rv = get_media_sector_size(fd, &size); if (rv != 0) { int e = errno; (void) close(fd); errc(ERR_OS, e, gettext("Failed to obtain sector size for %s"), actualdisk); } if (!is_sector_size_valid(size)) { (void) close(fd); err(ERR_OS, gettext("Invalid bytes/sector (%zu): must be 512, 1024, " "2048 or 4096\n"), size); } /* record sector size */ wbpb->bpb.bytes_per_sector = size; /* * Sanity check. If we've been provided a partition-specifying * suffix, we shouldn't also have been told to ignore the * fdisk table. */ if (DontUseFdisk && suffix) { (void) fprintf(stderr, gettext("Using 'nofdisk' option precludes " "appending logical drive\nspecifier " "to the device name.\n")); goto err_out; } /* * Find appropriate partition if we were requested to do so. */ if (suffix != NULL && !(seek_partn(fd, suffix, wbpb, seekto))) goto err_out; if (suffix == NULL) { /* * We have one of two possibilities. Chances are we have * a floppy drive. But the user may be trying to format * some weird drive that we don't know about and is supplying * all the important values. In that case, they should have set * the 'nofdisk' flag. * * If 'nofdisk' isn't set, do a floppy-specific ioctl to * get the remainder of our info. If the ioctl fails, we have * a good idea that they aren't really on a floppy. In that * case, they should have given us a partition specifier. */ if (DontUseFdisk) { if (!(seek_nofdisk(fd, wbpb, seekto))) goto err_out; find_fixed_details(fd, wbpb); } else if (ioctl(fd, FDIOGCHAR, &fdchar) == -1) { /* * It is possible that we are trying to use floppy * specific FDIOGCHAR ioctl on USB floppy. Since sd * driver, by which USB floppy is handled, doesn't * support it, we can try to use disk DKIOCGGEOM ioctl * to retrieve data we need. sd driver itself * determines floppy disk by number of blocks * (<=0x1000), then it sets geometry to 80 cylinders, * 2 heads. * * Note that DKIOCGGEOM cannot supply us with type * of media (e.g. 3.5" or 5.25"). We will set it to * 3 (3.5") which is most probable value. */ if (errno == ENOTTY) { if (ioctl(fd, DKIOCGGEOM, &dg) != -1 && dg.dkg_ncyl == 80 && dg.dkg_nhead == 2) { fdchar.fdc_ncyl = dg.dkg_ncyl; fdchar.fdc_medium = 3; fdchar.fdc_secptrack = dg.dkg_nsect; fdchar.fdc_nhead = dg.dkg_nhead; lookup_floppy(&fdchar, wbpb); } else { partn_lecture(actualdisk); goto err_out; } } } else { #ifdef sparc fdchar.fdc_medium = 3; #endif lookup_floppy(&fdchar, wbpb); } } else { find_fixed_details(fd, wbpb); } return (fd); err_out: (void) close(fd); exit(ERR_OS); } /* * verify_bootblkfile * * We were provided with the name of a file containing the bootblk * to install. Verify it has a valid boot sector as best we can. Any * errors and we return a bad file descriptor. Otherwise we fill up the * provided buffer with the boot sector, return the file * descriptor for later use and leave the file pointer just * past the boot sector part of the boot block file. */ static int verify_bootblkfile(char *fn, boot_sector_t *bs) { struct stat fi; int bsfd = -1; if (stat(fn, &fi)) { perror(fn); } else if (fi.st_size != MINBPS) { (void) fprintf(stderr, gettext("%s: File size does not fit for a boot sector.\n"), fn); } else if ((bsfd = open(fn, O_RDONLY)) < 0) { perror(fn); } else if (read(bsfd, bs->buf, MINBPS) < MINBPS) { (void) close(bsfd); bsfd = -1; perror(gettext("Boot block read")); } else { if ((bs->bs.bs_signature[0] != (BOOTSECSIG & 0xFF) && bs->bs.bs_signature[1] != ((BOOTSECSIG >> 8) & 0xFF)) || #ifdef _LITTLE_ENDIAN (bs->bs.bs_front.bs_jump_code[0] != OPCODE1 && bs->bs.bs_front.bs_jump_code[0] != OPCODE2) #else (bs->bs.bs_jump_code[0] != OPCODE1 && bs->bs.bs_jump_code[0] != OPCODE2) #endif /* CSTYLED */ ) { (void) close(bsfd); bsfd = -1; (void) fprintf(stderr, gettext("Boot block (%s) bogus.\n"), fn); } bs->bs.bs_front.bs_oem_name[0] = 'M'; bs->bs.bs_front.bs_oem_name[1] = 'S'; bs->bs.bs_front.bs_oem_name[2] = 'W'; bs->bs.bs_front.bs_oem_name[3] = 'I'; bs->bs.bs_front.bs_oem_name[4] = 'N'; bs->bs.bs_front.bs_oem_name[5] = '4'; bs->bs.bs_front.bs_oem_name[6] = '.'; bs->bs.bs_front.bs_oem_name[7] = '1'; /* * As we are storing Partition Boot Record, unset * pmbr built in stage2 lba and size. * We do this to stop mdb disk_label module to * try to interpret it. */ if (*((uint64_t *)(bs->buf + STAGE1_STAGE2_LBA)) == 256 && *((uint16_t *)(bs->buf + STAGE1_STAGE2_SIZE)) == 1) { *((uint64_t *)(bs->buf + STAGE1_STAGE2_LBA)) = 0; *((uint16_t *)(bs->buf + STAGE1_STAGE2_SIZE)) = 0; } } return (bsfd); } /* * verify_firstfile * * We were provided with the name of a file to be the first file * installed on the disk. We just need to verify it exists and * find out how big it is. If it doesn't exist, we print a warning * message about how the file wasn't found. We don't exit fatally, * though, rather we return a size of 0 and the FAT will be built * without installing any first file. They can then presumably * install the correct first file by hand. */ static int verify_firstfile(char *fn, ulong_t *filesize) { struct stat fi; int fd = -1; *filesize = 0; if (stat(fn, &fi) || (fd = open(fn, O_RDONLY)) < 0) { perror(fn); (void) fprintf(stderr, gettext("Could not access requested file. It will not\n" "be installed in the new file system.\n")); } else { *filesize = fi.st_size; } return (fd); } /* * label_volume * * Fill in BPB with volume label. */ static void label_volume(char *lbl, bpb_t *wbpb) { int ll, i; /* Put a volume label into our BPB. */ if (!lbl) lbl = DEFAULT_LABEL; ll = MIN(11, (int)strlen(lbl)); for (i = 0; i < ll; i++) { wbpb->ebpb.volume_label[i] = toupper(lbl[i]); } for (; i < 11; i++) { wbpb->ebpb.volume_label[i] = ' '; } } static void copy_bootblk(char *fn, boot_sector_t *bootsect) { int bsfd = -1; if (Verbose) (void) printf(gettext("Request to install boot " "block file %s.\n"), fn); /* * Sanity check that block. */ bsfd = verify_bootblkfile(fn, bootsect); if (bsfd < 0) { exit(ERR_INVALID); } (void) close(bsfd); } /* * mark_cluster * * This routine fills a FAT entry with the value supplied to it as an * argument. The fatp argument is assumed to be a pointer to the FAT's * 0th entry. The clustnum is the cluster entry that should be updated. * The value is the new value for the entry. */ static void mark_cluster(uchar_t *fatp, pc_cluster32_t clustnum, uint32_t value) { uchar_t *ep; ulong_t idx; idx = (Fatentsize == 32) ? clustnum * 4 : (Fatentsize == 16) ? clustnum * 2 : clustnum + clustnum/2; ep = fatp + idx; if (Fatentsize == 32) { store_32_bits(&ep, value); } else if (Fatentsize == 16) { store_16_bits(&ep, value); } else { if (clustnum & 1) { *ep = (*ep & 0x0f) | ((value << 4) & 0xf0); ep++; *ep = (value >> 4) & 0xff; } else { *ep++ = value & 0xff; *ep = (*ep & 0xf0) | ((value >> 8) & 0x0f); } } } static uchar_t * build_fat(bpb_t *wbpb, struct fat_od_fsi *fsinfop, ulong_t *fatsize, char *ffn, int *fffd, ulong_t *ffsize, pc_cluster32_t *ffstartclust) { pc_cluster32_t nextfree, ci; uchar_t *fatp; ushort_t numclust, numsect; int remclust; /* Alloc space for a FAT and then null it out. */ if (Verbose) { (void) printf(gettext("BUILD FAT.\n%d sectors per fat.\n"), wbpb->bpb.sectors_per_fat ? wbpb->bpb.sectors_per_fat : wbpb->bpb32.big_sectors_per_fat); } if (MakeFAT32) { *fatsize = wbpb->bpb.bytes_per_sector * wbpb->bpb32.big_sectors_per_fat; } else { *fatsize = wbpb->bpb.bytes_per_sector * wbpb->bpb.sectors_per_fat; } fatp = calloc(1, *fatsize); if (fatp == NULL) { perror(gettext("FAT table alloc")); exit(ERR_FAIL); } /* Build in-memory FAT */ *fatp = wbpb->bpb.media; *(fatp + 1) = 0xFF; *(fatp + 2) = 0xFF; if (Fatentsize == 16) { *(fatp + 3) = 0xFF; } else if (Fatentsize == 32) { *(fatp + 3) = 0x0F; *(fatp + 4) = 0xFF; *(fatp + 5) = 0xFF; *(fatp + 6) = 0xFF; *(fatp + 7) = 0x0F; } /* * Keep track of clusters used. */ remclust = TotalClusters; nextfree = 2; /* * Get info on first file to install, if any. */ if (ffn) *fffd = verify_firstfile(ffn, ffsize); /* * Reserve a cluster for the root directory on a FAT32. */ if (MakeFAT32) { mark_cluster(fatp, nextfree, PCF_LASTCLUSTER32); wbpb->bpb32.root_dir_clust = nextfree++; remclust--; } /* * Compute and preserve number of clusters for first file. */ if (*fffd >= 0) { *ffstartclust = nextfree; numsect = idivceil(*ffsize, wbpb->bpb.bytes_per_sector); numclust = idivceil(numsect, wbpb->bpb.sectors_per_cluster); if (numclust > remclust) { (void) fprintf(stderr, gettext("Requested first file too large to be\n" "installed in the new file system.\n")); (void) close(*fffd); *fffd = -1; goto finish; } if (Verbose) (void) printf(gettext("Reserving %d first file " "cluster(s).\n"), numclust); for (ci = 0; (int)ci < (int)(numclust-1); ci++, nextfree++) mark_cluster(fatp, nextfree, nextfree + 1); mark_cluster(fatp, nextfree++, MakeFAT32 ? PCF_LASTCLUSTER32 : PCF_LASTCLUSTER); remclust -= numclust; } finish: if (Verbose) { (void) printf(gettext("First sector of FAT")); header_for_dump(); dump_bytes(fatp, wbpb->bpb.bytes_per_sector); } (void) memset(fsinfop, 0, sizeof (*fsinfop)); fsinfop->fsi_leadsig = LE_32(FSI_LEADSIG); fsinfop->fsi_strucsig = LE_32(FSI_STRUCSIG); fsinfop->fsi_trailsig = LE_32(FSI_TRAILSIG); fsinfop->fsi_incore.fs_free_clusters = LE_32(remclust); fsinfop->fsi_incore.fs_next_free = LE_32(nextfree); return (fatp); } static void dirent_time_fill(struct pcdir *dep) { struct timeval tv; struct tm *tp; ushort_t dostime; ushort_t dosday; (void) gettimeofday(&tv, (struct timezone *)0); tp = localtime(&tv.tv_sec); /* get the time & day into DOS format */ dostime = tp->tm_sec / 2; dostime |= tp->tm_min << 5; dostime |= tp->tm_hour << 11; dosday = tp->tm_mday; dosday |= (tp->tm_mon + 1) << 5; dosday |= (tp->tm_year - 80) << 9; dep->pcd_mtime.pct_time = htols(dostime); dep->pcd_mtime.pct_date = htols(dosday); } static void dirent_label_fill(struct pcdir *dep, char *fn) { int nl, i; /* * We spread the volume label across both the NAME and EXT fields */ nl = MIN(PCFNAMESIZE, strlen(fn)); for (i = 0; i < nl; i++) { dep->pcd_filename[i] = toupper(fn[i]); } if (i < PCFNAMESIZE) { for (; i < PCFNAMESIZE; i++) dep->pcd_filename[i] = ' '; for (i = 0; i < PCFEXTSIZE; i++) dep->pcd_ext[i] = ' '; return; } nl = MIN(PCFEXTSIZE, strlen(fn) - PCFNAMESIZE); for (i = 0; i < nl; i++) dep->pcd_ext[i] = toupper(fn[i + PCFNAMESIZE]); if (i < PCFEXTSIZE) { for (; i < PCFEXTSIZE; i++) dep->pcd_ext[i] = ' '; } } static void dirent_fname_fill(struct pcdir *dep, char *fn) { char *fname, *fext; int nl, i; if ((fname = strrchr(fn, '/')) != NULL) { fname++; } else { fname = fn; } if ((fext = strrchr(fname, '.')) != NULL) { fext++; } else { fext = ""; } fname = strtok(fname, "."); nl = MIN(PCFNAMESIZE, (int)strlen(fname)); for (i = 0; i < nl; i++) { dep->pcd_filename[i] = toupper(fname[i]); } for (; i < PCFNAMESIZE; i++) { dep->pcd_filename[i] = ' '; } nl = MIN(PCFEXTSIZE, (int)strlen(fext)); for (i = 0; i < nl; i++) { dep->pcd_ext[i] = toupper(fext[i]); } for (; i < PCFEXTSIZE; i++) { dep->pcd_ext[i] = ' '; } } static uchar_t * build_rootdir(bpb_t *wbpb, char *ffn, int fffd, ulong_t ffsize, pc_cluster32_t ffstart, ulong_t *rdirsize) { struct pcdir *rootdirp; struct pcdir *entry; /* * Build a root directory. It will have at least one entry, * the volume label and a second if the first file was defined. */ if (MakeFAT32) { /* * We devote an entire cluster to the root * directory on FAT32. */ *rdirsize = wbpb->bpb.sectors_per_cluster * wbpb->bpb.bytes_per_sector; } else { *rdirsize = wbpb->bpb.num_root_entries * sizeof (struct pcdir); } if ((rootdirp = (struct pcdir *)malloc(*rdirsize)) == NULL) { perror(gettext("Root directory allocation")); exit(ERR_FAIL); } else { entry = rootdirp; (void) memset((char *)rootdirp, 0, *rdirsize); } /* Create directory entry for first file, if there is one */ if (fffd >= 0) { dirent_fname_fill(entry, ffn); entry->pcd_attr = Firstfileattr; dirent_time_fill(entry); entry->pcd_scluster_lo = htols(ffstart); if (MakeFAT32) { ffstart = ffstart >> 16; entry->un.pcd_scluster_hi = htols(ffstart); } entry->pcd_size = htoli(ffsize); entry++; } /* Create directory entry for volume label, if there is one */ if (Label != NULL) { dirent_label_fill(entry, Label); entry->pcd_attr = PCA_ARCH | PCA_LABEL; dirent_time_fill(entry); entry->pcd_scluster_lo = 0; if (MakeFAT32) { entry->un.pcd_scluster_hi = 0; } entry->pcd_size = 0; entry++; } if (Verbose) { (void) printf(gettext("First two directory entries")); header_for_dump(); dump_bytes((uchar_t *)rootdirp, 2 * sizeof (struct pcdir)); } return ((uchar_t *)rootdirp); } /* * write_rest * * Write all the bytes from the current file pointer to end of file * in the source file out to the destination file. The writes should * be padded to whole clusters with 0's if necessary. */ static void write_rest(bpb_t *wbpb, char *efn, int dfd, int sfd, int remaining) { char *buf; ushort_t numsect, numclust; ushort_t wnumsect, s; int doneread = 0; int rstat; size_t size; size = wbpb->bpb.bytes_per_sector; buf = malloc(size); if (buf == NULL) { perror(efn); return; } /* * Compute number of clusters required to contain remaining bytes. */ numsect = idivceil(remaining, size); numclust = idivceil(numsect, wbpb->bpb.sectors_per_cluster); wnumsect = numclust * wbpb->bpb.sectors_per_cluster; for (s = 0; s < wnumsect; s++) { if (!doneread) { if ((rstat = read(sfd, buf, size)) < 0) { perror(efn); doneread = 1; rstat = 0; } else if (rstat == 0) { doneread = 1; } (void) memset(&(buf[rstat]), 0, size - rstat); } if (write(dfd, buf, size) != (ssize_t)size) { (void) fprintf(stderr, gettext("Copying ")); perror(efn); } } free(buf); } static void write_fat32_bootstuff(int fd, boot_sector_t *bsp, bpb_t *wbpb, struct fat_od_fsi *fsinfop, off64_t seekto) { char *buf = NULL; size_t size = wbpb->bpb.bytes_per_sector; if (size != MINBPS && !Notreally) { buf = calloc(1, size); if (buf == NULL) { perror(gettext("FS info buffer alloc")); exit(ERR_FAIL); } (void) memcpy(buf, fsinfop, sizeof (*fsinfop)); } else { buf = (char *)fsinfop; } if (Verbose) { (void) printf(gettext("Dump of the fs info sector")); header_for_dump(); dump_bytes((uchar_t *)fsinfop, sizeof (*fsinfop)); } if (!Notreally) { /* * FAT32's have an FS info sector, then a backup of the boot * sector, and a modified backup of the FS Info sector. */ if (write(fd, buf, size) != (ssize_t)size) { perror(gettext("FS info sector write")); exit(ERR_FAIL); } if (lseek64(fd, seekto + wbpb->bpb32.backupboot * size, SEEK_SET) < 0) { (void) close(fd); perror(gettext("Boot sector backup seek")); exit(ERR_FAIL); } if (write(fd, bsp->buf, size) != (ssize_t)size) { perror(gettext("Boot sector backup write")); exit(ERR_FAIL); } } /* * Second copy of fs info sector is modified to have "don't know" * as the number of free clusters */ fsinfop = (struct fat_od_fsi *)buf; fsinfop->fsi_incore.fs_next_free = LE_32(FSINFO_UNKNOWN); if (Verbose) { (void) printf(gettext("Dump of the backup fs info sector")); header_for_dump(); dump_bytes((uchar_t *)fsinfop, sizeof (*fsinfop)); } if (!Notreally) { if (write(fd, buf, size) != (ssize_t)size) { perror(gettext("FS info sector backup write")); exit(ERR_FAIL); } } if (size != MINBPS && !Notreally) free(buf); } static void write_bootsects(int fd, boot_sector_t *bsp, bpb_t *wbpb, struct fat_od_fsi *fsinfop, off64_t seekto) { if (MakeFAT32) { /* Copy our BPB into bootsec structure */ #ifdef _LITTLE_ENDIAN (void) memcpy(&(bsp->bs32.bs_front.bs_bpb), &(wbpb->bpb), sizeof (wbpb->bpb)); (void) memcpy(&(bsp->bs32.bs_bpb32), &(wbpb->bpb32), sizeof (wbpb->bpb32)); (void) memcpy(&(bsp->bs32.bs_ebpb), &(wbpb->ebpb), sizeof (wbpb->ebpb)); #else swap_pack_bpb32cpy(&(bsp->bs32), wbpb); #endif } else { /* Copy our BPB into bootsec structure */ #ifdef _LITTLE_ENDIAN (void) memcpy(&(bsp->bs.bs_front.bs_bpb), &(wbpb->bpb), sizeof (wbpb->bpb)); (void) memcpy(&(bsp->bs.bs_ebpb), &(wbpb->ebpb), sizeof (wbpb->ebpb)); #else swap_pack_bpbcpy(&(bsp->bs), wbpb); #endif /* Copy SUN BPB extensions into bootsec structure */ if (SunBPBfields) { #ifdef _LITTLE_ENDIAN (void) memcpy(&(bsp->bs.bs_sebpb), &(wbpb->sunbpb), sizeof (wbpb->sunbpb)); #else swap_pack_sebpbcpy(&(bsp->bs), wbpb); #endif } } /* Write boot sector */ if (!Notreally && write(fd, bsp->buf, wbpb->bpb.bytes_per_sector) != (ssize_t)wbpb->bpb.bytes_per_sector) { perror(gettext("Boot sector write")); exit(ERR_FAIL); } if (Verbose) { (void) printf(gettext("Dump of the boot sector")); header_for_dump(); dump_bytes(bsp->buf, MINBPS); } if (MakeFAT32) write_fat32_bootstuff(fd, bsp, wbpb, fsinfop, seekto); } static void write_fat(int fd, off64_t seekto, char *fn, char *lbl, char *ffn, bpb_t *wbpb) { struct fat_od_fsi fsinfo; pc_cluster32_t ffsc; boot_sector_t bootsect; uchar_t *fatp, *rdirp; ulong_t fatsize, rdirsize, ffsize; int fffd = -1; compute_file_area_size(wbpb); /* boot sector structure size is always 512B */ copy_bootblk(fn, &bootsect); label_volume(lbl, wbpb); if (Verbose) (void) printf(gettext("Building FAT.\n")); fatp = build_fat(wbpb, &fsinfo, &fatsize, ffn, &fffd, &ffsize, &ffsc); write_bootsects(fd, &bootsect, wbpb, &fsinfo, seekto); if (lseek64(fd, seekto + (wbpb->bpb.bytes_per_sector * wbpb->bpb.resv_sectors), SEEK_SET) < 0) { (void) close(fd); perror(gettext("Seek to end of reserved sectors")); exit(ERR_FAIL); } /* Write FAT */ if (Verbose) (void) printf(gettext("Writing FAT(s). %lu bytes times %u.\n"), fatsize, wbpb->bpb.num_fats); if (!Notreally) { for (uint_t nf = 0; nf < wbpb->bpb.num_fats; nf++) { ssize_t wb; wb = write(fd, fatp, fatsize); if (wb != (ssize_t)fatsize) { perror(gettext("FAT write")); exit(ERR_FAIL); } else { if (Verbose) (void) printf( gettext("Wrote %zd bytes\n"), wb); } } } free(fatp); if (Verbose) (void) printf(gettext("Building root directory.\n")); rdirp = build_rootdir(wbpb, ffn, fffd, ffsize, ffsc, &rdirsize); /* * In non FAT32, root directory exists outside of the file area */ if (Verbose) (void) printf(gettext("Writing root directory. %lu bytes.\n"), rdirsize); if (MakeFAT32) { if (lseek64(fd, seekto + wbpb->bpb.bytes_per_sector * wbpb->bpb.resv_sectors + wbpb->bpb.num_fats * fatsize + wbpb->bpb.bytes_per_sector * wbpb->bpb.sectors_per_cluster * (wbpb->bpb32.root_dir_clust - 2), SEEK_SET) < 0) { (void) close(fd); perror(gettext("Seek to end of reserved sectors")); exit(ERR_FAIL); } } if (!Notreally) { if (write(fd, rdirp, rdirsize) != rdirsize) { perror(gettext("Root directory write")); exit(ERR_FAIL); } } free(rdirp); /* * Now write anything that needs to be in the file space. */ if (fffd >= 0) { if (Verbose) (void) printf(gettext("Writing first file.\n")); if (!Notreally) write_rest(wbpb, ffn, fd, fffd, ffsize); } } static char *LegalOpts[] = { #define NFLAG 0 "N", #define VFLAG 1 "v", #define RFLAG 2 "r", #define HFLAG 3 "h", #define SFLAG 4 "s", #define SUNFLAG 5 "S", #define LABFLAG 6 "b", #define BTRFLAG 7 "B", #define INITFLAG 8 "i", #define SZFLAG 9 "size", #define SECTFLAG 10 "nsect", #define TRKFLAG 11 "ntrack", #define SPCFLAG 12 "spc", #define BPFFLAG 13 "fat", #define FFLAG 14 "f", #define DFLAG 15 "d", #define NOFDISKFLAG 16 "nofdisk", #define RESRVFLAG 17 "reserve", #define HIDDENFLAG 18 "hidden", NULL }; static void parse_suboptions(char *optsstr) { char *value; int c; while (*optsstr != '\0') { switch (c = getsubopt(&optsstr, LegalOpts, &value)) { case NFLAG: Notreally++; break; case VFLAG: Verbose++; break; case RFLAG: Firstfileattr |= 0x01; break; case HFLAG: Firstfileattr |= 0x02; break; case SFLAG: Firstfileattr |= 0x04; break; case SUNFLAG: SunBPBfields = 1; break; case LABFLAG: if (value == NULL) { missing_arg(LegalOpts[c]); } else { Label = value; } break; case BTRFLAG: if (value == NULL) { missing_arg(LegalOpts[c]); } else { BootBlkFn = value; } break; case INITFLAG: if (value == NULL) { missing_arg(LegalOpts[c]); } else { FirstFn = value; } break; case SZFLAG: if (value == NULL) { missing_arg(LegalOpts[c]); } else { TotSize = atoi(value); GetSize = 0; } break; case SECTFLAG: if (value == NULL) { missing_arg(LegalOpts[c]); } else { SecPerTrk = atoi(value); GetSPT = 0; } break; case TRKFLAG: if (value == NULL) { missing_arg(LegalOpts[c]); } else { TrkPerCyl = atoi(value); GetTPC = 0; } break; case SPCFLAG: if (value == NULL) { missing_arg(LegalOpts[c]); } else { SecPerClust = atoi(value); GetSPC = 0; } break; case BPFFLAG: if (value == NULL) { missing_arg(LegalOpts[c]); } else { BitsPerFAT = atoi(value); GetBPF = 0; } break; case NOFDISKFLAG: DontUseFdisk = 1; break; case RESRVFLAG: if (value == NULL) { missing_arg(LegalOpts[c]); } else { Resrvd = atoi(value); GetResrvd = 0; } break; case HIDDENFLAG: if (value == NULL) { missing_arg(LegalOpts[c]); } else { RelOffset = atoi(value); GetOffset = 0; } break; case FFLAG: if (value == NULL) { missing_arg(LegalOpts[c]); } else { DiskName = value; Outputtofile = 1; } break; case DFLAG: if (value == NULL) { missing_arg(LegalOpts[c]); } else { Imagesize = atoi(value); } break; default: bad_arg(value); break; } } } static void sanity_check_options(int argc, int optind) { if (GetFsParams) { if (argc - optind != 1) usage(); return; } if (DontUseFdisk && GetOffset) { /* Set default relative offset of zero */ RelOffset = 0; } if (BitsPerFAT == 32) MakeFAT32 = 1; if (Outputtofile && (argc - optind)) { usage(); } else if (Outputtofile && !DiskName) { usage(); } else if (!Outputtofile && (argc - optind != 1)) { usage(); } else if (SunBPBfields && !BootBlkFn) { (void) fprintf(stderr, gettext("Use of the 'S' option requires that\n" "the 'B=' option also be used.\n\n")); usage(); } else if (Firstfileattr != 0x20 && !FirstFn) { (void) fprintf(stderr, gettext("Use of the 'r', 'h', or 's' options requires\n" "that the 'i=' option also be used.\n\n")); usage(); } else if (!GetOffset && !DontUseFdisk) { (void) fprintf(stderr, gettext("Use of the 'hidden' option requires that\n" "the 'nofdisk' option also be used.\n\n")); usage(); } else if (DontUseFdisk && GetSize) { (void) fprintf(stderr, gettext("Use of the 'nofdisk' option requires that\n" "the 'size=' option also be used.\n\n")); usage(); } else if (!GetBPF && BitsPerFAT != 12 && BitsPerFAT != 16 && BitsPerFAT != 32) { (void) fprintf(stderr, gettext("Invalid Bits/Fat value." " Must be 12, 16 or 32.\n")); exit(ERR_OS); } else if (!GetSPC && !(ISP2(SecPerClust) && IN_RANGE(SecPerClust, 1, 128))) { (void) fprintf(stderr, gettext("Invalid Sectors/Cluster value. Must be a " "power of 2 between 1 and 128.\n")); exit(ERR_OS); } else if (!GetResrvd && (Resrvd < 1 || Resrvd > 0xffff)) { (void) fprintf(stderr, gettext("Invalid number of reserved sectors. " "Must be at least 1 but\nno larger than 65535.")); exit(ERR_OS); } else if (!GetResrvd && MakeFAT32 && (Resrvd < 32 || Resrvd > 0xffff)) { (void) fprintf(stderr, gettext("Invalid number of reserved sectors. " "Must be at least 32 but\nno larger than 65535.")); exit(ERR_OS); } else if (Imagesize != 3 && Imagesize != 5) { usage(); } } int main(int argc, char **argv) { off64_t AbsBootSect = 0; bpb_t dskparamblk; char *string; int fd; int c; (void) setlocale(LC_ALL, ""); #if !defined(TEXT_DOMAIN) #define TEXT_DOMAIN "SYS_TEST" #endif (void) textdomain(TEXT_DOMAIN); if (init_yes() < 0) errx(ERR_OS, gettext(ERR_MSG_INIT_YES), strerror(errno)); while ((c = getopt(argc, argv, "F:Vmo:")) != EOF) { switch (c) { case 'F': string = optarg; if (strcmp(string, "pcfs") != 0) usage(); break; case 'V': { char *opt_text; int opt_count; (void) fprintf(stdout, gettext("mkfs -F pcfs ")); for (opt_count = 1; opt_count < argc; opt_count++) { opt_text = argv[opt_count]; if (opt_text) (void) fprintf(stdout, " %s ", opt_text); } (void) fprintf(stdout, "\n"); } break; case 'm': GetFsParams++; break; case 'o': string = optarg; parse_suboptions(string); break; } } sanity_check_options(argc, optind); if (!Outputtofile) DiskName = argv[optind]; (void) memset(&dskparamblk, 0, sizeof (dskparamblk)); if (GetFsParams) { fd = open_and_examine(DiskName, &dskparamblk); } else { fd = open_and_seek(DiskName, &dskparamblk, &AbsBootSect); if (ask_nicely(Fatentsize, DiskName)) write_fat(fd, AbsBootSect, BootBlkFn, Label, FirstFn, &dskparamblk); } (void) close(fd); fini_yes(); return (0); } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (the "License"). You may not use this file except in compliance # with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2003 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # FSTYPE= pcfs LIBPROG= mount include ../../Makefile.fstype include ../../Makefile.mount include ../../Makefile.mount.targ CERRWARN += $(CNOWARN_UNINIT) /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern int daylight; static int roflag = 0; static char optbuf[MAX_MNTOPT_STR] = { '\0', }; static int optsize = 0; /* * Since the format/value expected for the mount options listed below * differs between what the user mount command expects and what the * kernel module can grok, we transmogrify the mount option string * for such options. Others are copied through as-is. */ static char *pcfs_opts[] = { MNTOPT_PCFS_TIMEZONE, NULL }; #define ARG_PCFS_TIMEZONE 0 /* * While constructing the mount option string, we need to append * comma separators if there have been previous options copied over * from the input string. This takes care of it. */ static int append_opt(char *str, int strsz, char *opt) { if (str[0] != '\0' && strlcat(str, ",", strsz) >= strsz) return (0); return (strlcat(str, opt, strsz) < strsz); } int main(int argc, char *argv[]) { char *mnt_special; char *mnt_mountp; int c; char *myname; char typename[64]; char tzstr[100]; char *tzval; char *savedoptbuf = NULL, *savedoptarg = NULL; char *in_arg, *val, *curarg; extern int optind; extern char *optarg; int error = 0; int verbose = 0; int mflg = MS_OPTIONSTR; /* we always pass mount options */ int qflg = 0; int optcnt = 0; int tzdone = 0; myname = strrchr(argv[0], '/'); myname = myname ? myname + 1 : argv[0]; (void) snprintf(typename, sizeof (typename), "%s_%s", MNTTYPE_PCFS, myname); argv[0] = typename; while ((c = getopt(argc, argv, "Vvmr?o:Oq")) != EOF) { switch (c) { case 'V': case 'v': verbose++; break; case '?': error++; break; case 'r': roflag++; break; case 'm': mflg |= MS_NOMNTTAB; break; case 'o': in_arg = optarg; if ((savedoptarg = strdup(optarg)) == NULL) { (void) fprintf(stderr, gettext("%s: out of memory\n"), myname); exit(2); } while (*in_arg != '\0') { curarg = in_arg; switch (getsubopt(&in_arg, pcfs_opts, &val)) { case ARG_PCFS_TIMEZONE: if (tzdone || val == NULL) goto invalarg; tzval = val; (void) snprintf(tzstr, 100, "TZ=%s", tzval); tzstr[99] = '\0'; (void) putenv(tzstr); tzdone = 1; break; default: /* * Remove empty suboptions * (happens on sequences of commas) */ if (*curarg == '\0') break; if (append_opt(optbuf, sizeof (optbuf), curarg) == 0) goto invalarg; } } break; case 'O': mflg |= MS_OVERLAY; break; case 'q': qflg = 1; break; } } if (verbose && !error) { char *optptr; (void) fprintf(stderr, "%s", typename); for (optcnt = 1; optcnt < argc; optcnt++) { optptr = argv[optcnt]; if (optptr) (void) fprintf(stderr, " %s", optptr); } (void) fprintf(stderr, "\n"); } if (argc - optind != 2 || error) { /* * don't hint at options yet (none are really supported) */ (void) fprintf(stderr, gettext( "Usage: %s [generic options] [-o suboptions] " "special mount_point\n"), typename); (void) fprintf(stderr, gettext( "\tpcfs-specific suboptions are:\n" "\t clamptime,noclamptime\n" "\t hidden,nohidden\n" "\t atime,noatime\n" "\t foldcase,nofoldcase\n" "\t timezone=")); exit(32); } mnt_special = argv[optind++]; mnt_mountp = argv[optind++]; /* * Pass timezone information to the kernel module so that * FAT timestamps, as per spec, can be recorded in local time. */ tzset(); /* * We perform this validation only in case the user of * mount(8) specified the "timezone=..." option. That's * because we don't want PCFS mounts to fail due to a * botched $TZ environment variable. If the admin's * environment contains garbage, it'll just parse as * GMT (timezone=0). */ if (tzdone && timezone == 0 && altzone == 0 && daylight == 0 && strcmp(tzname[0], tzval) && strspn(tzname[1], " ") == strlen(tzname[1])) { goto invalarg; } (void) snprintf(tzstr, 100, "timezone=%d", timezone); tzstr[99] = '\0'; if (append_opt(optbuf, sizeof (optbuf), tzstr) == 0) goto invalarg; optsize = strlen(optbuf); if (roflag) mflg |= MS_RDONLY; if ((savedoptbuf = strdup(optbuf)) == NULL) { (void) fprintf(stderr, gettext("%s: out of memory\n"), myname); exit(2); } (void) signal(SIGHUP, SIG_IGN); (void) signal(SIGQUIT, SIG_IGN); (void) signal(SIGINT, SIG_IGN); if (verbose) { (void) fprintf(stderr, "mount(%s, \"%s\", %d, %s", mnt_special, mnt_mountp, mflg, MNTTYPE_PCFS); } if (mount(mnt_special, mnt_mountp, mflg, MNTTYPE_PCFS, NULL, 0, optbuf, MAX_MNTOPT_STR)) { if (errno == EBUSY) { (void) fprintf(stderr, gettext( "mount: %s is already mounted or %s is busy\n"), mnt_special, mnt_mountp); } else if (errno == EINVAL) { (void) fprintf(stderr, gettext( "mount: %s is not a DOS filesystem.\n"), mnt_special); } else { perror("mount"); } exit(32); } if (optsize && !qflg) cmp_requested_to_actual_options(savedoptbuf, optbuf, mnt_special, mnt_mountp); return (0); invalarg: (void) fprintf(stderr, gettext("%s: Invalid mount options: %s\n"), myname, savedoptarg); return (2); }