/* Simple fdisk for PS2 partitions. * Written on 2002/4/29 by Andrew Church * This program is public domain. */ /* Define this to allow the program to actually write to the device. */ #define DO_WRITE /*************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #undef linux /* we use it as a variable name */ #ifndef DO_WRITE # undef O_RDWR # define O_RDWR O_RDONLY #endif _syscall5(int, _llseek, uint, fd, ulong, hi, ulong, lo, loff_t *, result, uint, wh); /*************************************************************************/ /* Program version string */ #define VERSION "1.0" /* Operating modes */ #define MODE_DEFAULT 0 /* Default mode, edit partition table */ #define MODE_VERSION 1 /* (-v) Print program version */ #define MODE_LIST 2 /* (-l) List all partitions on the device */ #define MODE_XLIST 3 /* (-L) Same, but in expert-list mode */ #define MODE_PRINTSIZE 4 /* (-s) Print size of block device */ /* Default device to operate on */ #define DEFAULT_DEVICE "/dev/hda" /* Various PS2 partition constants */ #define PS2_PARTITION_MAGIC 0x00415041 /* "APA\0" */ #define PS2_PART_IDMAX 32 #define PS2_PART_NAMEMAX 128 #define PS2_PART_MAXSUB 64 /* Maximum # of sub-partitions */ #define PS2_PART_FLAG_SUB 0x0001 /* Is partition a sub-partition? */ #define PS2_MBR_VERSION 2 /* Current MBR version */ /* Partition types */ #define PS2_MBR_PARTITION 0x0001 #define PS2_SWAP_PARTITION 0x0082 #define PS2_LINUX_PARTITION 0x0083 #define PS2_GAME_PARTITION 0x0100 /*************************************************************************/ /* In-memory representation of a single 128MB block */ struct ps2_block { int partition; /* Which partition this block belongs to, -1 if none */ int num; /* Sub-partition number within partition */ int size; /* Size of this subpart in blocks, 0 if unalloc'd */ int next; /* Block address of next sub-partition, 0 if none */ }; /* In-memory representation of a single partition */ struct ps2_partition { short type; /* Partition type, 0 = free */ char id[PS2_PART_IDMAX+1]; char name[PS2_PART_NAMEMAX+1]; char unknown1[16]; /* To preserve old value */ u_int16_t unknown2; /* Likewise */ u_int32_t data_start, data_len; /* Likewise, for the MBR */ time_t created; int first; /* Block address of main partition */ }; /* In-memory representation of a partition table */ struct ps2_partition_table { int nblocks; /* Number of 128MB blocks in disk */ int nfree; /* Number of free blocks */ struct ps2_block *blocks; struct ps2_partition *partitions; }; /* Date/time descriptor used in on-disk partition header */ struct ps2fs_datetime { u_int8_t unused; u_int8_t sec; u_int8_t min; u_int8_t hour; u_int8_t day; u_int8_t month; u_int16_t year; }; /* On-disk partition header for a partition */ struct ps2_partition_header { u_int32_t checksum; /* Sum of all 256 words, assuming checksum==0 */ u_int32_t magic; /* PS2_PARTITION_MAGIC */ u_int32_t next; /* Sector address of next partition */ u_int32_t prev; /* Sector address of previous partition */ char id[PS2_PART_IDMAX]; char unknown1[16]; u_int32_t start; /* Sector address of this partition */ u_int32_t length; /* Sector count */ u_int16_t type; u_int16_t flags; /* PS2_PART_FLAG_* */ u_int32_t nsub; /* No. of sub-partitions (stored in main partition) */ struct ps2fs_datetime created; u_int32_t main; /* For sub-partitions, main partition sector address */ u_int32_t number; /* For sub-partitions, sub-partition number */ u_int16_t unknown2; char unknown3[30]; char name[PS2_PART_NAMEMAX]; struct { char magic[32]; /* Copyright message in MBR */ char unknown_0x02; char unknown1[7]; struct ps2fs_datetime created; /* Same as for the partition, it seems*/ u_int32_t data_start; /* Some sort of MBR data; position in sectors*/ u_int32_t data_len; /* Length also in sectors */ char unknown2[200]; } mbr; struct { /* Sub-partition data */ u_int32_t start;/* Sector address */ u_int32_t length;/* Sector count */ } subs[PS2_PART_MAXSUB]; }; /*************************************************************************/ /**************************** Helper routines ****************************/ /*************************************************************************/ /* Calculate the checksum of a PS2 partition header. */ inline u_int32_t calc_checksum(struct ps2_partition_header *header) { u_int32_t *ptr = (u_int32_t *)header; u_int32_t sum = 0; int i; for (i = 1; i < 256; i++) sum += *++ptr; return sum; } /*************************************************************************/ /* Convert a ps2fs_datetime structure to an ordinary time_t value. */ time_t from_ps2time(const struct ps2fs_datetime *dt) { time_t t = 0, utcoffset; struct tm *tm; int i; static int mdays[12] = {0,31,59,90,120,151,181,212,243,273,304,334}; /* Determine offset from UTC */ tm = localtime(&t); utcoffset = tm->tm_hour*3600 + tm->tm_min*60 + tm->tm_sec; if (tm->tm_year < 70) utcoffset -= 86400; /* Calculate time_t value */ for (i = 1970; i < dt->year; i++) { t += 365*86400; if (i%4 == 0 && (t%100 != 0 || t%400 == 0)) t += 86400; } t += mdays[dt->month-1] * 86400; if (dt->month>2 && (dt->year%4==0 && (dt->year%100!=0 || dt->year%400==0))) t += 86400; return t + (dt->day-1)*86400 + dt->hour*3600 + dt->min*60 + dt->sec - utcoffset; } /*************************************************************************/ /* Convert a time_t value to a ps2fs_datetime structure. */ void to_ps2time(struct ps2fs_datetime *dt, time_t t) { struct tm *tm; tm = localtime(&t); dt->unused = 0; dt->sec = tm->tm_sec; dt->min = tm->tm_min; dt->hour = tm->tm_hour; dt->day = tm->tm_mday; dt->month = tm->tm_mon + 1; dt->year = tm->tm_year + 1900; } /*************************************************************************/ /********************* Partition table manipulation **********************/ /*************************************************************************/ /* Local routines: */ /*************************************************************************/ /* Split up the partition (assumed to be empty space) at the given block. */ static void split_partition(struct ps2_partition_table *ptable, int block) { int start, end; if (!ptable->blocks[block].size) return; start = block & -(ptable->blocks[block].size); end = start + ptable->blocks[block].size; while (start != block) { int i; for (i = start; i < end; i++) ptable->blocks[i].size >>= 1; if (block >= start + ptable->blocks[block].size) start += ptable->blocks[block].size; else end -= ptable->blocks[block].size; } } /*************************************************************************/ /* Create the given partition with the given start and end points (in 128MB * blocks). Always succeeds; any existing association of the blocks with * other partitions is overwritten. `end' points to the first 128MB block * _after_ the end of the partition. */ static void make_partition(struct ps2_partition_table *ptable, int partition, int start, int end) { int subpart = 0; /* Current subpart number, 0 = main partition */ int block; split_partition(ptable, start); split_partition(ptable, end); memset(&ptable->partitions[partition], 0, sizeof(*ptable->partitions)); ptable->partitions[partition].unknown2 = 0x0102; /* shrug */ ptable->partitions[partition].created = time(NULL); ptable->partitions[partition].first = start; for (block = start; block < end; block += ptable->blocks[block].size) { int size = ptable->blocks[block].size; int i; for (i = block; i < block + size; i++) { ptable->blocks[i].partition = partition; ptable->blocks[i].num = subpart; if (block+size < end) ptable->blocks[i].next = block + size; } ptable->nfree -= size; subpart++; } } /*************************************************************************/ /*************************************************************************/ /* Global routines: */ /*************************************************************************/ /* Create a new partition table for the given device. Returns nonzero on * success, zero (and prints an error message) on failure. */ int initialize_partition_table(const char *device, int fd, struct ps2_partition_table *ptable) { int nblocks; /* Number of 1024-byte blocks in device */ int i; /* First try to retrieve device size, and error out if we can't or if * the device is too small. */ if (ioctl(fd, BLKGETSIZE, &nblocks) < 0) { fprintf(stderr, "ioctl(%s, BLKGETSIZE): %s\n", device, strerror(errno)); return 0; } nblocks /= 2; /* 512-blocks -> 1024-blocks */ if (nblocks < 256*1024) { /* 256MB */ fprintf(stderr, "%s: Device too small\n", device); return 0; } /* Clear everything out. */ memset(ptable, 0, sizeof(*ptable)); /* Calculate number of 128MB blocks, and allocate structures * accordingly. */ ptable->nblocks = nblocks / (128*1024); ptable->blocks = malloc(sizeof(struct ps2_block) * ptable->nblocks); ptable->partitions = malloc(sizeof(struct ps2_partition) * ptable->nblocks); if (!ptable->blocks || !ptable->partitions) { fprintf(stderr, "Out of memory\n"); return 0; } /* Mark all blocks as free. */ for (i = 0; i < ptable->nblocks; i++) ptable->blocks[i].partition = -1; ptable->nfree = ptable->nblocks; /* Return success. */ return 1; } /*************************************************************************/ /* Free dynamically-allocated data in a partition table. */ void free_partition_table(struct ps2_partition_table *ptable) { if (ptable->blocks) { free(ptable->blocks); ptable->blocks = NULL; } if (ptable->partitions) { free(ptable->partitions); ptable->partitions = NULL; } } /*************************************************************************/ /* Read in the partition table for the given device. Returns 1 on success * (including cases where part of the partition table is corrupt), 0 if the * device did not have a valid MBR, and -1 on other error; error and * warning messages are printed as appropriate. */ int read_partition_table(const char *device, int fd, struct ps2_partition_table *ptable) { struct ps2_partition_header header; int partition; /* Current partition number */ u_int32_t position; /* Position of current header in blocks */ u_int32_t lastpos; /* Position of previous header (for sanity check) */ int subpart; /* Subpart number currently being processed*/ u_int32_t nextpos; /* Position of next subpart */ loff_t dummy; /* Required by _llseek() */ int i; /* First read the MBR, and stop now if it doesn't look like one (or if * we can't read it at all). */ if (read(fd, &header, sizeof(header)) != sizeof(header)) { fprintf(stderr, "%s: Short read on MBR\n", device); return -1; } if (header.magic != PS2_PARTITION_MAGIC || header.type != PS2_MBR_PARTITION || header.mbr.unknown_0x02 != 0x02 ) { fprintf(stderr, "%s does not have a valid Playstation 2 partition" " table.\n", device); return 0; } /* Set up the partition table structure. */ if (!initialize_partition_table(device, fd, ptable)) return -1; /* Store MBR-specific data in table. */ ptable->partitions[0].data_start = header.mbr.data_start; ptable->partitions[0].data_len = header.mbr.data_len; /* Loop over all partitions, entering them into the table. We start by * entering the current partition's data into the table, then read the * next partition header in, and end the loop with a check for whether * the (just-read-in) header is valid. */ partition = 0; position = 0; lastpos = 0; do { /* Various sanity checks */ if (header.length != 1L<<18 && header.length != 1L<<19 && header.length != 1L<<20 && header.length != 1L<<21 ) { printf("Partition at %08X has bad size (%08X), terminating" " partition table\n", position<<18, header.length); break; } if (position & ((header.length>>18)-1)) { printf("Partition at %08X is misaligned, terminating" " partition table\n", position<<18); break; } if (header.checksum != calc_checksum(&header)) { printf("Warning: partition at %08X has bad checksum (will be" " corrected by write)\n", position<<18); } if (position > 0 && header.prev != lastpos<<18) { printf("Warning: partition at %08X has wrong prev address" " (will be corrected by write)\n", position<<18); } if (header.start != position<<18) { printf("Warning: partition at %08X has wrong start address" " (will be corrected by write)\n", position<<18); } if (ptable->blocks[position].partition >= 0) { /* Subpartitions are already entered; make sure the data matches * up */ if (!(header.flags & PS2_PART_FLAG_SUB)) { printf("Warning: new partition found inside partition %d," " ignored\n", ptable->blocks[position].partition); } else if (header.number != ptable->blocks[position].num) { printf("Warning: subpart number mismatch at %08X (will be" " corrected by write)\n", position<<18); } else if (header.length>>18 != ptable->blocks[position].size) { printf("Warning: subpart size mismatch at %08X (will be" " corrected by write)\n", position<<18); } } else { /* Make sure subparts don't collide */ for (i = 0; i < header.length>>18; i++) { if (ptable->blocks[position+i].partition >= 0) { printf("Partition collision between %d and %d," " terminating partition table", partition, ptable->blocks[position+i].partition); break; } } for (subpart = 0; subpart < header.nsub; subpart++) { for (i = 0; i < header.subs[subpart].length>>18; i++) { if (ptable->blocks[position+i].partition >= 0) { printf("Partition collision between %d and %d," " terminating partition table", partition, ptable->blocks[position+i].partition); break; } } } /* Store partition data if not empty */ if (header.type) { ptable->partitions[partition].type = header.type; memcpy(ptable->partitions[partition].id, header.id, PS2_PART_IDMAX); memcpy(ptable->partitions[partition].name, header.name, PS2_PART_NAMEMAX); memcpy(ptable->partitions[partition].unknown1, header.unknown1, sizeof(header.unknown1)); ptable->partitions[partition].unknown2 = header.unknown2; ptable->partitions[partition].created = from_ps2time(&header.created); ptable->partitions[partition].first = position; } nextpos = header.nsub>0 ? header.subs[0].start>>18 : 0; for (i = 0; i < header.length>>18; i++) { ptable->blocks[position+i].partition = header.type ? partition : -1; ptable->blocks[position+i].num = 0; ptable->blocks[position+i].size = header.length>>18; ptable->blocks[position+i].next = header.type ? nextpos : 0; } if (header.type) ptable->nfree -= header.length>>18; for (subpart = 0; subpart < header.nsub; subpart++) { int thispos = nextpos; nextpos = subpart+1>18 : 0; for (i = 0; i < header.subs[subpart].length>>18; i++) { ptable->blocks[thispos+i].partition = header.type ? partition : -1; ptable->blocks[thispos+i].num = header.type ? subpart+1 : 0; ptable->blocks[thispos+i].size = header.subs[subpart].length>>18; ptable->blocks[thispos+i].next = header.type ? nextpos : 0; } if (header.type) ptable->nfree -= header.subs[subpart].length>>18; } if (header.type) partition++; } /* Check for end of partition table */ if (!header.next) break; /* Avoid going in circles */ if (header.next < position) { printf("Partition %d has next < start, terminating partition" " table\n", partition); break; } /* Read in next partition header */ lastpos = position; position = header.next>>18; if (_llseek(fd, position>>5, position<<27, &dummy, SEEK_SET) < 0) { fprintf(stderr, "%s: llseek(0x%08X%08X, SEEK_SET): %s\n", device, position>>5, position<<27, strerror(errno)); free_partition_table(ptable); return -1; } if (read(fd, &header, sizeof(header)) != sizeof(header)) { fprintf(stderr, "%s: Short read on partition %d header\n", device, partition); return -1; } } while (header.magic == PS2_PARTITION_MAGIC); return 1; } /*************************************************************************/ /* Write the partition table out to the given device, and tell the system * to reread it. Returns nonzero if successful (an error in rereading the * partition table is still considered success), zero if not. */ int write_partition_table(const char *device, int fd, struct ps2_partition_table *ptable) { int partition; /* Partition number being processed */ int subpart; /* Block address of current subpart */ int prev; /* Previous block address */ struct ps2_partition *part; /* Pointer to partition data */ struct ps2_partition_header *headers; /* Partition header array */ headers = calloc(sizeof(*headers), ptable->nblocks); if (!headers) { fprintf(stderr, "Out of memory!\n"); return 0; } /* Set up MBR information; the code to set the MBR magic value is * convoluted to avoid the message it contains (when interpreted as * ASCII characters) giving inappropriate illusions about the status * of this program. */ headers[0].mbr.unknown_0x02 = 2; to_ps2time(&headers[0].mbr.created, ptable->partitions[0].created); headers[0].mbr.data_start = ptable->partitions[0].data_start; headers[0].mbr.data_len = ptable->partitions[0].data_len; { char c = 0x20; headers[0].mbr.magic[ 4] = c; headers[0].mbr.magic[13] = c; headers[0].mbr.magic[27] = c; headers[0].mbr.magic[31] = 0x2E; headers[0].mbr.magic[ 5] = 0x43; headers[0].mbr.magic[14] = 0x45; headers[0].mbr.magic[28] = 0x49; headers[0].mbr.magic[ 0] = 0x53; headers[0].mbr.magic[20] = 0x61; headers[0].mbr.magic[30] = 0x63; c = 0x65; headers[0].mbr.magic[11] = c; headers[0].mbr.magic[17] = c; headers[0].mbr.magic[24] = c; headers[0].mbr.magic[21] = 0x69; c = 0x6D; headers[0].mbr.magic[ 7] = c; headers[0].mbr.magic[23] = c; c++; headers[0].mbr.magic[ 2] = c; headers[0].mbr.magic[15] = c; headers[0].mbr.magic[22] = c; headers[0].mbr.magic[25] = c; headers[0].mbr.magic[29] = c; c++; headers[0].mbr.magic[ 1] = c; headers[0].mbr.magic[ 6] = c; headers[0].mbr.magic[ 8] = c+1; c = 0x72; headers[0].mbr.magic[12] = c; headers[0].mbr.magic[18] = c; c += 2; headers[0].mbr.magic[10] = c; headers[0].mbr.magic[16] = c; headers[0].mbr.magic[19] = c; headers[0].mbr.magic[26] = c; headers[0].mbr.magic[ 9] = 0x75; headers[0].mbr.magic[ 3] = 0x79; } /* Set up all next/prev/start pointers and other basic data */ prev = 0; for (subpart = 0; ptable->blocks[subpart].size; prev = subpart, subpart += ptable->blocks[subpart].size ) { headers[subpart].magic = PS2_PARTITION_MAGIC; headers[subpart].next = (subpart + ptable->blocks[subpart].size) << 18; headers[subpart].prev = prev << 18; headers[subpart].start = subpart << 18; headers[subpart].length = ptable->blocks[subpart].size << 18; memcpy(headers[subpart].id, "__empty", 8); to_ps2time(&headers[subpart].created, time(NULL)); } headers[prev].next = 0; headers[0].prev = prev << 18; for (partition = 0, part = ptable->partitions; partition < ptable->nblocks && part->type != 0; partition++, part++ ) { int nsubs; /* Total number of subparts */ int partnum; /* Number of current subpart */ /* Count subparts and store them in main part's header */ nsubs = 0; for (subpart = ptable->blocks[part->first].next; subpart; subpart = ptable->blocks[subpart].next ) { headers[part->first].subs[nsubs].start = subpart << 18; headers[part->first].subs[nsubs].length = ptable->blocks[subpart].size << 18; nsubs++; } /* Set up header for each subpart */ subpart = part->first; partnum = 0; do { struct ps2_partition_header *header = &headers[subpart]; header->type = part->type; headers[subpart].unknown2 = part->unknown2; if (partnum == 0) { memcpy(header->id, part->id, PS2_PART_IDMAX); memcpy(header->name, part->name, PS2_PART_NAMEMAX); memcpy(header->unknown1, part->unknown1, sizeof(header->unknown1)); header->nsub = nsubs; } else { memset(header->id, 0, PS2_PART_IDMAX); header->flags = PS2_PART_FLAG_SUB; header->main = part->first << 18; header->number = partnum; } to_ps2time(&header->created, part->created); partnum++; } while ((subpart = ptable->blocks[subpart].next) != 0); } /* Calculate checksums */ for (subpart = 0; ptable->blocks[subpart].size; subpart += ptable->blocks[subpart].size ) { headers[subpart].checksum = calc_checksum(&headers[subpart]); } #ifdef DO_WRITE /* Write partition headers */ printf("Writing partition table.\n"); for (subpart = 0; ptable->blocks[subpart].size; subpart += ptable->blocks[subpart].size ) { loff_t dummy; if (_llseek(fd, subpart>>5, subpart<<27, &dummy, SEEK_SET) < 0) { fprintf(stderr, "%s: llseek(0x%08X%08X, SEEK_SET): %s\n", device, subpart>>5, subpart<<27, strerror(errno)); free_partition_table(ptable); free(headers); return 0; } if (write(fd,&headers[subpart],sizeof(*headers)) != sizeof(*headers)) { fprintf(stderr, "%s: Short write at %08X%08X: %s\n", device, subpart>>5, subpart<<27, strerror(errno)); free(headers); return 0; } } sync(); /* Reread partition table */ printf("Rereading partition table.\n"); if (ioctl(fd, BLKRRPART) < 0) { fprintf(stderr, "%s: Error re-reading partition table: %s\n", device, strerror(errno)); } #else /* !DO_WRITE */ printf("NOT writing or rereading partition table (DO_WRITE not" " defined).\n"); printf("Writing partition headers to ps2part.dat.\n"); { int fd2 = open("ps2part.dat", O_WRONLY | O_CREAT | O_TRUNC, 0666); if (fd2 >= 0) { write(fd2, headers, sizeof(*headers) * ptable->nblocks); close(fd2); } else { perror("ps2part.dat"); } } #endif free(headers); return 1; } /*************************************************************************/ /* Create a new partition of the given size (in 128MB units) and return its * number. Returns -1 if the partition could not be created. */ int create_partition(struct ps2_partition_table *ptable, int size) { int partition; /* Partition index */ int need1 = 0; /* Number of 128MB blocks needed (0/1) */ int need2 = 0; /* Number of 256MB blocks needed (0/1) */ int need4 = 0; /* Number of 512MB blocks needed (0/1) */ int need8 = 0; /* Number of 1024MB blocks needed */ int needmax = 0; /* Largest size block needed */ int start, end; /* Start and end of new partition in 128MB blocks */ /* Make sure there's room for the partition. */ if (ptable->nfree < size) return -1; for (partition = 0; partition < ptable->nblocks; partition++) { if (ptable->partitions[partition].type == 0) break; } if (partition >= ptable->nblocks) return -1; /* Determine number of each type of block needed. */ need8 = size/8; need4 = size & 4 ? 1 : 0; need2 = size & 2 ? 1 : 0; need1 = size & 1; if (need8) needmax = 8; else if (need4) needmax = 4; else if (need2) needmax = 2; else needmax = 1; /* First look for a contiguous empty block surrounding a block of the * largest size needed (i.e. smallest number of blocks possible). */ for (start = 0; start < ptable->nblocks && ptable->blocks[start].size > 0; start += ptable->blocks[start].size ) { int start_save = start; /* In case we don't succeed */ if (ptable->blocks[start].partition >= 0 || ptable->blocks[start].size < needmax) continue; /* Found a potential candidate; now see if there's enough free * space surrounding it */ end = start + needmax; while (end-start < need8*8) { if (ptable->blocks[end].partition >= 0 || ptable->blocks[end].size != 8) break; end += 8; } if (end - start < need8*8) continue; if (need4 && needmax > 4) { if (ptable->blocks[start-1].size >= 4 && ptable->blocks[start-1].partition < 0) { start -= 4; } else if (ptable->blocks[end].size >= 4 && ptable->blocks[end].partition < 0) { end += 4; } else { continue; } } if (need2 && needmax > 2) { if (ptable->blocks[start-1].size >= 2 && ptable->blocks[start-1].partition < 0) { start -= 2; } else if (ptable->blocks[end].size >= 2 && ptable->blocks[end].partition < 0) { end += 2; } else { start = start_save; continue; } } if (need1 && needmax > 1) { if (ptable->blocks[start-1].partition < 0) { start -= 1; } else if (ptable->blocks[end].partition < 0) { end += 1; } else { start = start_save; continue; } } /* Success, create the partition */ make_partition(ptable, partition, start, end); return partition; } /* See if there's enough space at the end of the disk for the * partition. Note that `start' now points to the first block after * the last used one. */ end = start + size; if (end <= ptable->nblocks) { int i; /* Align the start as much as we can */ if (!need1 && (start & 1) && end+1 <= ptable->nblocks) { ptable->blocks[start++].size = 1; end += 1; if (!need2 && (start & 2) && end+2 <= ptable->nblocks) { ptable->blocks[start++].size = 2; ptable->blocks[start++].size = 2; end += 2; if (!need4 && (start & 4) && end+4 <= ptable->nblocks) { ptable->blocks[start++].size = 4; ptable->blocks[start++].size = 4; ptable->blocks[start++].size = 4; ptable->blocks[start++].size = 4; end += 4; } } } /* Insert partition sizes */ for (i = start; i < end; i += ptable->blocks[i].size) { if ((i & 1) || end-i < 2) { ptable->blocks[i ].size = 1; } else if ((i & 2) || end-i < 4) { ptable->blocks[i ].size = 2; ptable->blocks[i+1].size = 2; } else if ((i & 4) || end-i < 8) { ptable->blocks[i ].size = 4; ptable->blocks[i+1].size = 4; ptable->blocks[i+2].size = 4; ptable->blocks[i+3].size = 4; } else { ptable->blocks[i ].size = 8; ptable->blocks[i+1].size = 8; ptable->blocks[i+2].size = 8; ptable->blocks[i+3].size = 8; ptable->blocks[i+4].size = 8; ptable->blocks[i+5].size = 8; ptable->blocks[i+6].size = 8; ptable->blocks[i+7].size = 8; } } /* Create partition and return successfully */ make_partition(ptable, partition, start, end); return partition; } /* Failure. */ return -1; } /*************************************************************************/ /* Delete the given partition. Does nothing if the partition number is out * of range. */ void delete_partition(struct ps2_partition_table *ptable, int partition) { int i, j, next; int changed; /* Used in merging empty blocks */ /* Check partition number */ if (partition < 0 || partition >= ptable->nblocks || ptable->partitions[partition].type == 0 ) { return; } /* Clear data from blocks array */ for (i = ptable->partitions[partition].first; i; i = next) { next = ptable->blocks[i].next; for (j = i; j < i + ptable->blocks[i].size; j++) { ptable->blocks[j].partition = -1; ptable->blocks[j].num = 0; ptable->blocks[j].next = 0; } ptable->nfree += ptable->blocks[i].size; } /* Clear trailing empty blocks (set size to 0 so they're ignored) */ i = ptable->nblocks-1; while (i >= 0 && ptable->blocks[i].size == 0) i--; while (i >= 0 && ptable->blocks[i].partition < 0) ptable->blocks[i--].size = 0; /* Merge any intermediate empty blocks that we can */ do { changed = 0; for (i = 0; i < ptable->nblocks && ptable->blocks[i].size; i += ptable->blocks[i].size ) { int next = i + ptable->blocks[i].size; if (next < ptable->nblocks && ptable->blocks[i].size == ptable->blocks[next].size && ptable->blocks[i].partition < 0 && ptable->blocks[next].partition < 0 && ptable->blocks[i].size < 8 ) { next += ptable->blocks[next].size; for (j = i; j < next; j++) { ptable->blocks[j].size *= 2; } } } } while (changed); /* Delete partition entry from array */ if (partition < ptable->nblocks-1) { memmove(&ptable->partitions[partition], &ptable->partitions[partition+1], sizeof(*ptable->partitions) * (ptable->nblocks-1 - partition)); } memset(&ptable->partitions[ptable->nblocks-1], 0, sizeof(*ptable->partitions)); /* Adjust partition references from blocks array */ for (i = 0; i < ptable->nblocks; i++) { if (ptable->blocks[i].partition > partition) ptable->blocks[i].partition--; } } /*************************************************************************/ /* List the partition table. */ void list_partition_table(const char *device, struct ps2_partition_table *ptable) { int i; struct { /* Partition sizes and names; [0] is for non-Linux */ int size; /* Size in MB */ char *id, *name; } linux[64]; memset(linux, 0, sizeof(linux)); for (i = 0; i < ptable->nblocks && ptable->partitions[i].type; i++) { /* Calculate total partition size */ int size = ptable->blocks[ptable->partitions[i].first].size; int j; for (j = ptable->blocks[ptable->partitions[i].first].next; j; j = ptable->blocks[j].next ) { size += ptable->blocks[j].size; } size <<= 7; /* convert to MB */ /* Store in appropriate place */ if (ptable->partitions[i].type == PS2_LINUX_PARTITION || ptable->partitions[i].type == PS2_SWAP_PARTITION ) { int partnum = atoi(ptable->partitions[i].id + 6); linux[partnum].size = size; linux[partnum].id = ptable->partitions[i].id; linux[partnum].name = ptable->partitions[i].name; } else { linux[0].size += size; } } printf("%s: %dMB total / %dMB free\n\n", device, ptable->nblocks<<7, ptable->nfree<<7); printf(" Device Size Partition ID\n"); for (i = 1; i <= 63; i++) { if (linux[i].size) { printf("/dev/hda%-2d %6dMB %-15s (%s)\n", i, linux[i].size, linux[i].id, linux[i].name); } } printf(" ---- %6dMB (non-Linux partitions)\n", linux[0].size); } /*************************************************************************/ /* List the partition table verbosely (expert mode). */ void xlist_partition_table(const char *device, struct ps2_partition_table *ptable) { int i, num; printf("%s: %dMB total / %dMB free\n\n", device, ptable->nblocks*128, ptable->nfree*128); printf("No. Start Size Num Total Type ID\n"); for (i = num = 0; i < ptable->nblocks && ptable->blocks[i].size > 0; i += ptable->blocks[i].size, num++ ) { struct ps2_partition *part = &ptable->partitions[ptable->blocks[i].partition]; if (ptable->blocks[i].partition < 0) { /* Unused entry */ printf("%3d %08X %6dMB %3d <%-32s>\n", num, i<<18, ptable->blocks[i].size<<7, 0, "__empty"); } else if (ptable->blocks[i].num == 0) { /* Main partition entry */ int count, j, totalsize; /* Count number of subparts and total size */ count = 0; totalsize = ptable->blocks[i].size; for (j = ptable->blocks[i].next; j; j = ptable->blocks[j].next) { count++; totalsize += ptable->blocks[j].size; } /* Print data */ printf("%3d %08X %6dMB %3d %6dMB %4X [%-32s]\n", num, i<<18, ptable->blocks[i].size<<7, count, totalsize<<7, part->type, part->id); } else { /* Subpart entry */ printf("%3d %08X %6dMB %3d (%08X)\n", num, i<<18, ptable->blocks[i].size<<7, ptable->blocks[i].num, part->first<<18); } } } /*************************************************************************/ /************************** Main program stuff ***************************/ /*************************************************************************/ /* Parse command line options to determine the mode and device to operate * on, and return them in *p_mode and *p_device respectively. Returns * nonzero on success, zero on error or -h. */ int parse_options(int argc, char **argv, int *p_mode, char **p_device) { int c; /* Store default values. */ *p_mode = MODE_DEFAULT; *p_device = DEFAULT_DEVICE; /* Parse option letters; -h or any unrecognized option, or a * combination of multiple mode options, causes a help message to be * printed and parsing to abort. */ while ((c = getopt(argc, argv, "hvlLs")) != EOF) { switch (c) { case 'v': if (*p_mode != MODE_DEFAULT) { fprintf(stderr, "Only one of the `-l', `-L', `-s', and `-v' options may be given.\n"); goto help_message; } *p_mode = MODE_VERSION; break; case 'l': if (*p_mode != MODE_DEFAULT) { fprintf(stderr, "Only one of the `-l', `-L', `-s', and `-v' options may be given.\n"); goto help_message; } *p_mode = MODE_LIST; break; case 'L': if (*p_mode != MODE_DEFAULT) { fprintf(stderr, "Only one of the `-l', `-L', `-s', and `-v' options may be given.\n"); goto help_message; } *p_mode = MODE_XLIST; break; case 's': if (*p_mode != MODE_DEFAULT) { fprintf(stderr, "Only one of the `-l', `-L', `-s', and `-v' options may be given.\n"); goto help_message; } *p_mode = MODE_PRINTSIZE; break; case 'h': default : help_message: printf("Usage: %s [DISK] Edit partition table\n", argv[0]); printf(" %s -l [DISK] Print partition table (normal)\n", argv[0]); printf(" %s -L [DISK] Print partition table (verbose)\n", argv[0]); printf(" %s -L PARTITION Print partition size in blocks\n", argv[0]); printf(" %s -v Print program version\n", argv[0]); printf(" %s -h Print this message\n", argv[0]); printf("Where DISK is a whole-disk device (/dev/hda, /dev/sdb, etc.)\n"); printf("and PARTITION is a single partition device (/dev/hda1, /dev/sdb2, etc.).\n"); return 0; } } /* Take device name from next command-line argument, or use default * device (and say that we're doing so) if there is no next argument. * For -s, a device is required; print a message and return failure if * none is given. */ if (optind < argc) { *p_device = argv[optind++]; } else if (*p_mode == MODE_PRINTSIZE) { fprintf(stderr, "You must supply a device for -s.\n"); return 0; } else { fprintf(stderr, "Using %s as default device!\n", *p_device); } return 1; } /*************************************************************************/ /* Open the given device in a mode appropriate for the operations to be * performed on it, and check that the device is actually a block device. * Returns the file descriptor on success, -1 (and prints an appropriate * error message) on failure. */ int open_device(const char *device, int mode) { int fd; /* File descriptor for device */ struct stat st; /* stat() result for device */ fd = open(device, mode==MODE_DEFAULT ? O_RDWR : O_RDONLY); if (fd < 0) { fprintf(stderr, "Unable to open %s: %s\n", device, strerror(errno)); return -1; } if (fstat(fd, &st) < 0) { fprintf(stderr, "Unable to stat %s: %s\n", device, strerror(errno)); close(fd); return -1; } if (!S_ISBLK(st.st_mode)) { fprintf(stderr, "%s is not a block device\n", device); close(fd); return -1; } return fd; } /*************************************************************************/ /* Interactively edit the partition table. Returns when a quitting command * (q or w) is given. */ void edit_partition_table(const char *device, int fd, struct ps2_partition_table *ptable) { char inbuf[512]; /* Last line read */ int quit; /* Stop editing? */ int expert; /* In expert mode? */ quit = 0; expert = 0; while (!quit) { printf("\n%sommand (m for help): ", expert ? "Expert c" : "C"); fflush(stdout); fgets(inbuf, sizeof(inbuf), stdin); switch (*inbuf) { case 'd': { int partition; /* Linux partition number to delete */ int i; /* Actual partition index */ if (expert) goto print_menu; printf("Partition number to delete (1-63): "); fflush(stdout); fgets(inbuf, sizeof(inbuf), stdin); partition = atoi(inbuf); if (partition < 1 || partition > 63) { printf("Partition number out of range.\n"); break; } for (i = 0; i < ptable->nblocks; i++) { if (!ptable->partitions[i].type) continue; if (strncmp(ptable->partitions[i].id, "linux ", 6) != 0) continue; if (atoi(ptable->partitions[i].id+6) == partition) break; } if (i >= ptable->nblocks) { printf("No such partition.\n"); break; } delete_partition(ptable, i); break; } /* case 'd' */ case 'n': { int partition; /* Linux partition number to create */ int type; /* Partition type */ int size; /* Desired size (128MB units) */ int i; if (expert) goto print_menu; /* Read and check partition number */ printf("Partition number (1-63): "); fflush(stdout); fgets(inbuf, sizeof(inbuf), stdin); partition = atoi(inbuf); if (partition < 1 || partition > 63) { printf("Partition number out of range.\n"); break; } for (i = 0; i < ptable->nblocks; i++) { if (!ptable->partitions[i].type) continue; if (strncmp(ptable->partitions[i].id, "linux ", 6) != 0) continue; if (atoi(ptable->partitions[i].id+6) == partition) break; } if (i < ptable->nblocks) { printf("Partition already exists.\n"); break; } /* Read partition type */ printf("Partition type ([l] linux / [s] linux swap): "); fflush(stdout); fgets(inbuf, sizeof(inbuf), stdin); if (*inbuf == 'l') { type = PS2_LINUX_PARTITION; } else if (*inbuf == 's') { type = PS2_SWAP_PARTITION; } else { printf("Invalid partition type.\n"); break; } /* Read partition size and round up as needed */ printf("Partition size (MB) (128-%d): ", ptable->nfree<<7); fflush(stdout); fgets(inbuf, sizeof(inbuf), stdin); i = atoi(inbuf); if (i < 128 || i > ptable->nfree<<7) { printf("Invalid partition size.\n"); break; } size = (i+127) >> 7; /* Read partition name, and leave it in inbuf */ printf("Comment string: "); fflush(stdout); fgets(inbuf, sizeof(inbuf), stdin); /* Try to actually create partition */ i = create_partition(ptable, size); if (i < 0) { printf("Unable to create partition. Try deleting some" " other partitions first.\n"); break; } /* Store data in partition entry */ snprintf(ptable->partitions[i].id, PS2_PART_IDMAX+1, "linux %d", partition); ptable->partitions[i].type = type; if (inbuf[strlen(inbuf)-1] == '\n') inbuf[strlen(inbuf)-1] = 0; strncpy(ptable->partitions[i].name, inbuf, PS2_PART_NAMEMAX); ptable->partitions[i].name[PS2_PART_NAMEMAX] = 0; break; } /* case 'n' */ case 'o': if (expert) goto print_menu; printf("Creating a new empty PlayStation 2 partition table.\n"); printf("WARNING: All previous data will be lost if changes are written to disk!\n"); free_partition_table(ptable); initialize_partition_table(device, fd, ptable); break; case 'p': if (expert) xlist_partition_table(device, ptable); else list_partition_table(device, ptable); break; case 'q': quit = 1; break; case 'r': if (!expert) goto print_menu; expert = 0; break; case 'w': if (!write_partition_table(device, fd, ptable)) break; quit = 1; break; case 'x': if (expert) goto print_menu; expert = 1; break; default: print_menu: printf("Commands:\n"); if (expert) { printf(" p Print partition table (verbose)\n"); printf(" q Quit without saving changes\n"); printf(" r Return to main menu\n"); #ifdef DO_WRITE printf(" w Write partition table to disk and quit\n"); #else printf(" w Write partition table to disk (not really) and quit\n"); #endif } else { printf(" d Delete a partition\n"); printf(" n Add a new partition\n"); printf(" o Create a new empty PlayStation 2 partition table\n"); printf(" p Print partition table\n"); printf(" q Quit without saving changes\n"); #ifdef DO_WRITE printf(" w Write partition table to disk and quit\n"); #else printf(" w Write partition table to disk (not really) and quit\n"); #endif printf(" x Expert menu\n"); } } /* switch (*inbuf) */ } /* while (!quit) */ } /* edit_partition_table() */ /*************************************************************************/ int main(int argc, char **argv) { int mode; /* Operation mode */ char *device; /* Device to operate on */ int fd; /* File descriptor for device */ struct ps2_partition_table ptable; /* In-memory partition table for device */ /* Parse command-line options, and retrieve the mode of operation and * device to operate on. */ if (!parse_options(argc, argv, &mode, &device)) return 1; /* For MODE_VERSION, just print the version number and exit. */ if (mode == MODE_VERSION) { printf("ps2fdisk " VERSION "\n"); return 0; } /* Make sure the device we're using actually exists and is an * appropriate device (and that we have access to it). */ fd = open_device(device, mode); if (fd < 0) return 1; /* For MODE_PRINTSIZE, we don't touch the partition table; just get the * device size from ioctl(), print it, and exit. */ if (mode == MODE_PRINTSIZE) { int nblocks; if (ioctl(fd, BLKGETSIZE, &nblocks) < 0) { fprintf(stderr, "ioctl(%s, BLKGETSIZE): %s\n", device, strerror(errno)); close(fd); return 1; } printf("%d\n", nblocks); close(fd); return 0; } /* Read in the device's partition table. */ switch (read_partition_table(device, fd, &ptable)) { case -1: /* I/O or other error; the error message has already been printed, * so just exit. */ return 1; case 0: /* No master boot record was found. An error message has already * been printed. */ if (mode == MODE_DEFAULT) { /* Edit mode: create an empty table and set up the MBR. */ fprintf(stderr, "Creating a new partition table.\n"); if (!initialize_partition_table(device, fd, &ptable)) { close(fd); return 1; } ptable.blocks[0].partition = 0; ptable.blocks[0].num = 0; ptable.blocks[0].size = 1; ptable.blocks[0].next = 0; ptable.partitions[0].type = PS2_MBR_PARTITION; memcpy(ptable.partitions[0].id, "__mbr", 6); ptable.partitions[0].name[0] = 0; ptable.partitions[0].first = 0; ptable.nfree--; } else { /* Other modes: nothing to do, so exit. */ close(fd); return 1; } } /* For non-interactive modes, do the requested thing and exit. */ if (mode != MODE_DEFAULT) { close(fd); switch (mode) { case MODE_LIST: list_partition_table(device, &ptable); break; case MODE_XLIST: xlist_partition_table(device, &ptable); break; } return 0; } /* Interactive mode. */ edit_partition_table(device,fd, &ptable); close(fd); return 0; } /*************************************************************************/