openMSX
DiskImageUtils.cc
Go to the documentation of this file.
1 #include "DiskImageUtils.hh"
2 #include "DiskPartition.hh"
3 #include "CommandException.hh"
4 #include "BootBlocks.hh"
5 #include "endian.hh"
6 #include "random.hh"
7 #include "xrange.hh"
8 #include <cstring>
9 #include <cassert>
10 #include <ctime>
11 
13 
14 constexpr char PARTAB_HEADER[11] = {
15  '\353', '\376', '\220', 'M', 'S', 'X', '_', 'I', 'D', 'E', ' '
16 };
17 [[nodiscard]] static bool isPartitionTableSector(const PartitionTable& pt)
18 {
19  return memcmp(pt.header, PARTAB_HEADER, sizeof(PARTAB_HEADER)) == 0;
20 }
21 
23 {
24  SectorBuffer buf;
25  disk.readSector(0, buf);
26  return isPartitionTableSector(buf.pt);
27 }
28 
29 
30 static Partition& checkImpl(
31  SectorAccessibleDisk& disk, unsigned partition, SectorBuffer& buf)
32 {
33  // check number in range
34  if (partition < 1 || partition > 31) {
35  throw CommandException(
36  "Invalid partition number specified (must be 1-31).");
37  }
38  // check drive has a partition table
39  disk.readSector(0, buf);
40  if (!isPartitionTableSector(buf.pt)) {
41  throw CommandException(
42  "No (or invalid) partition table.");
43  }
44  // check valid partition number
45  auto& p = buf.pt.part[31 - partition];
46  if (p.start == 0) {
47  throw CommandException("No partition number ", partition);
48  }
49  return p;
50 }
51 
53 {
54  SectorBuffer buf;
55  checkImpl(disk, partition, buf);
56 }
57 
59 {
60  SectorBuffer buf;
61  Partition& p = checkImpl(disk, partition, buf);
62 
63  // check partition type
64  if (p.sys_ind != 0x01) {
65  throw CommandException("Only FAT12 partitions are supported.");
66  }
67 }
68 
69 
70 // Create a correct bootsector depending on the required size of the filesystem
72  unsigned firstDataSector;
73  byte descriptor;
74 };
75 static SetBootSectorResult setBootSector(
76  MSXBootSector& boot, size_t nbSectors, bool dos1)
77 {
78  // start from the default bootblock ..
79  const auto& defaultBootBlock = dos1 ? BootBlocks::dos1BootBlock : BootBlocks::dos2BootBlock;
80  memcpy(&boot, &defaultBootBlock, sizeof(boot));
81 
82  // .. and fill-in image-size dependent parameters ..
83  // these are the same for most formats
84  byte nbReservedSectors = 1;
85  byte nbHiddenSectors = 1;
86 
87  // all these are initialized below (in this order)
88  word nbSides;
89  byte nbFats;
90  byte nbSectorsPerFat;
91  byte nbSectorsPerCluster;
92  word nbDirEntry;
93  byte descriptor;
94 
95  // now set correct info according to size of image (in sectors!)
96  // and using the same layout as used by Jon in IDEFDISK v 3.1
97  if (nbSectors > 32732) {
98  // 32732 < nbSectors
99  // note: this format is only valid for nbSectors <= 65536
100  nbSides = 32; // copied from a partition from an IDE HD
101  nbFats = 2;
102  nbSectorsPerFat = 12; // copied from a partition from an IDE HD
103  nbSectorsPerCluster = 16;
104  nbDirEntry = 256;
105  descriptor = 0xF0;
106  nbHiddenSectors = 16; // override default from above
107  } else if (nbSectors > 16388) {
108  // 16388 < nbSectors <= 32732
109  nbSides = 2; // unknown yet
110  nbFats = 2;
111  nbSectorsPerFat = 12;
112  nbSectorsPerCluster = 8;
113  nbDirEntry = 256;
114  descriptor = 0XF0;
115  } else if (nbSectors > 8212) {
116  // 8212 < nbSectors <= 16388
117  nbSides = 2; // unknown yet
118  nbFats = 2;
119  nbSectorsPerFat = 12;
120  nbSectorsPerCluster = 4;
121  nbDirEntry = 256;
122  descriptor = 0xF0;
123  } else if (nbSectors > 4126) {
124  // 4126 < nbSectors <= 8212
125  nbSides = 2; // unknown yet
126  nbFats = 2;
127  nbSectorsPerFat = 12;
128  nbSectorsPerCluster = 2;
129  nbDirEntry = 256;
130  descriptor = 0xF0;
131  } else if (nbSectors > 2880) {
132  // 2880 < nbSectors <= 4126
133  nbSides = 2; // unknown yet
134  nbFats = 2;
135  nbSectorsPerFat = 6;
136  nbSectorsPerCluster = 2;
137  nbDirEntry = 224;
138  descriptor = 0xF0;
139  } else if (nbSectors > 1440) {
140  // 1440 < nbSectors <= 2880
141  nbSides = 2; // unknown yet
142  nbFats = 2;
143  nbSectorsPerFat = 5;
144  nbSectorsPerCluster = 2;
145  nbDirEntry = 112;
146  descriptor = 0xF0;
147  } else if (nbSectors > 720) {
148  // normal double sided disk
149  // 720 < nbSectors <= 1440
150  nbSides = 2;
151  nbFats = 2;
152  nbSectorsPerFat = 3;
153  nbSectorsPerCluster = 2;
154  nbDirEntry = 112;
155  descriptor = 0xF9;
156  nbSectors = 1440; // force nbSectors to 1440, why?
157  } else {
158  // normal single sided disk
159  // nbSectors <= 720
160  nbSides = 1;
161  nbFats = 2;
162  nbSectorsPerFat = 2;
163  nbSectorsPerCluster = 2;
164  nbDirEntry = 112;
165  descriptor = 0xF8;
166  nbSectors = 720; // force nbSectors to 720, why?
167  }
168 
169  boot.nrSectors = uint16_t(nbSectors); // TODO check for overflow
170  boot.nrSides = nbSides;
171  boot.spCluster = nbSectorsPerCluster;
172  boot.nrFats = nbFats;
173  boot.sectorsFat = nbSectorsPerFat;
174  boot.dirEntries = nbDirEntry;
175  boot.descriptor = descriptor;
176  boot.resvSectors = nbReservedSectors;
177  boot.hiddenSectors = nbHiddenSectors;
178 
179  if (!dos1) {
180  // set random volume id
181  boot.vol_id = random_32bit() & 0x7F7F7F7F; // why are bits masked?
182  }
183  unsigned nbRootDirSectors = nbDirEntry / 16;
184  unsigned rootDirStart = 1 + nbFats * nbSectorsPerFat;
185  unsigned firstDataSector = rootDirStart + nbRootDirSectors;
186  return {firstDataSector, descriptor};
187 }
188 
189 void format(SectorAccessibleDisk& disk, bool dos1)
190 {
191  // first create a bootsector for given partition size
192  size_t nbSectors = disk.getNbSectors();
193  SectorBuffer buf;
194  auto [firstDataSector, descriptor] = setBootSector(buf.bootSector, nbSectors, dos1);
195  disk.writeSector(0, buf);
196 
197  // write empty FAT and directory sectors
198  memset(&buf, 0, sizeof(buf));
199  for (auto i : xrange(2u, firstDataSector)) {
200  disk.writeSector(i, buf);
201  }
202  // first FAT sector is special:
203  // - first byte contains the media descriptor
204  // - first two clusters must be marked as EOF
205  buf.raw[0] = descriptor;
206  buf.raw[1] = 0xFF;
207  buf.raw[2] = 0xFF;
208  disk.writeSector(1, buf);
209 
210  // write 'empty' data sectors
211  memset(&buf, 0xE5, sizeof(buf));
212  for (auto i : xrange(firstDataSector, nbSectors)) {
213  disk.writeSector(i, buf);
214  }
215 }
216 
217 struct CHS {
218  unsigned cylinder;
219  unsigned head;
220  unsigned sector;
221 };
222 [[nodiscard]] static CHS logicalToCHS(unsigned logical)
223 {
224  // This is made to fit the openMSX harddisk configuration:
225  // 32 sectors/track 16 heads
226  unsigned tmp = logical + 1;
227  unsigned sector = tmp % 32;
228  if (sector == 0) sector = 32;
229  tmp = (tmp - sector) / 32;
230  unsigned head = tmp % 16;
231  unsigned cylinder = tmp / 16;
232  return {cylinder, head, sector};
233 }
234 
235 void partition(SectorAccessibleDisk& disk, const std::vector<unsigned>& sizes)
236 {
237  assert(sizes.size() <= 31);
238 
239  SectorBuffer buf{};
240  memset(&buf, 0, sizeof(buf));
241  memcpy(buf.pt.header, PARTAB_HEADER, sizeof(PARTAB_HEADER));
242  buf.pt.end = 0xAA55;
243 
244  unsigned partitionOffset = 1;
245  for (auto i : xrange(sizes.size())) {
246  unsigned partitionNbSectors = sizes[i];
247  auto& p = buf.pt.part[30 - i];
248  auto [startCylinder, startHead, startSector] =
249  logicalToCHS(partitionOffset);
250  auto [endCylinder, endHead, endSector] =
251  logicalToCHS(partitionOffset + partitionNbSectors - 1);
252  p.boot_ind = (i == 0) ? 0x80 : 0x00; // bootflag
253  p.head = startHead;
254  p.sector = startSector;
255  p.cyl = startCylinder;
256  p.sys_ind = 0x01; // FAT12
257  p.end_head = endHead;
258  p.end_sector = endSector;
259  p.end_cyl = endCylinder;
260  p.start = partitionOffset;
261  p.size = sizes[i];
262  DiskPartition diskPartition(disk, partitionOffset, partitionNbSectors);
263  format(diskPartition);
264  partitionOffset += partitionNbSectors;
265  }
266  disk.writeSector(0, buf);
267 }
268 
269 } // namespace openmsx::DiskImageUtils
static const SectorBuffer dos2BootBlock
Definition: BootBlocks.hh:15
static const SectorBuffer dos1BootBlock
Definition: BootBlocks.hh:12
void readSector(size_t sector, SectorBuffer &buf)
void writeSector(size_t sector, const SectorBuffer &buf)
bool hasPartitionTable(SectorAccessibleDisk &disk)
Check whether the given disk is partitioned.
void format(SectorAccessibleDisk &disk, bool dos1)
Format the given disk (= a single partition).
void checkValidPartition(SectorAccessibleDisk &disk, unsigned partition)
Checks whether the disk is partitioned the specified partition exists throws a CommandException if on...
void partition(SectorAccessibleDisk &disk, const std::vector< unsigned > &sizes)
Write a partition table to the given disk and format each partition.
constexpr char PARTAB_HEADER[11]
void checkFAT12Partition(SectorAccessibleDisk &disk, unsigned partition)
Like above, but also check whether partition is of type FAT12.
uint16_t word
16 bit unsigned integer
Definition: openmsx.hh:29
uint32_t random_32bit()
Return a random 32-bit value.
Definition: random.hh:67
Endian::UA_L16 dirEntries
Endian::UA_L16 nrSectors
MSXBootSector bootSector
constexpr auto xrange(T e)
Definition: xrange.hh:155