blob: a7c4724c466290d691a273702c583154be3fba67 [file] [log] [blame]
srs5694add79a62010-01-26 15:59:58 -05001//
2// C++ Interface: diskio (Unix components [Linux, FreeBSD, Mac OS X])
3//
4// Description: Class to handle low-level disk I/O for GPT fdisk
5//
6//
7// Author: Rod Smith <rodsmith@rodsbooks.com>, (C) 2009
8//
9// Copyright: See COPYING file that comes with this distribution
10//
11//
12// This program is copyright (c) 2009 by Roderick W. Smith. It is distributed
13// under the terms of the GNU GPL version 2, as detailed in the COPYING file.
14
15#define __STDC_LIMIT_MACROS
16#define __STDC_CONSTANT_MACROS
17
18#include <sys/ioctl.h>
srs5694fed16d02010-01-27 23:03:40 -050019#include <string.h>
srs5694add79a62010-01-26 15:59:58 -050020#include <string>
21#include <stdint.h>
Roderick W. Smith24bba6e2013-10-12 19:07:16 -040022#include <unistd.h>
srs5694add79a62010-01-26 15:59:58 -050023#include <errno.h>
24#include <fcntl.h>
25#include <sys/stat.h>
srs569434882942012-03-23 12:49:15 -040026#include <unistd.h>
srs5694bf8950c2011-03-12 01:23:12 -050027
28#ifdef __linux__
29#include "linux/hdreg.h"
30#endif
31
srs5694add79a62010-01-26 15:59:58 -050032#include <iostream>
33
srs5694add79a62010-01-26 15:59:58 -050034#include "diskio.h"
35
36using namespace std;
37
38// Returns the official "real" name for a shortened version of same.
39// Trivial here; more important in Windows
40void DiskIO::MakeRealName(void) {
41 realFilename = userFilename;
42} // DiskIO::MakeRealName()
43
srs569455d92612010-03-07 22:16:07 -050044// Open the currently on-record file for reading. Returns 1 if the file is
45// already open or is opened by this call, 0 if opening the file doesn't
46// work.
srs5694add79a62010-01-26 15:59:58 -050047int DiskIO::OpenForRead(void) {
48 int shouldOpen = 1;
srs56948f1b2d62010-05-23 13:07:19 -040049 struct stat64 st;
srs5694add79a62010-01-26 15:59:58 -050050
51 if (isOpen) { // file is already open
52 if (openForWrite) {
53 Close();
54 } else {
55 shouldOpen = 0;
56 } // if/else
57 } // if
58
59 if (shouldOpen) {
60 fd = open(realFilename.c_str(), O_RDONLY);
61 if (fd == -1) {
srs569455d92612010-03-07 22:16:07 -050062 cerr << "Problem opening " << realFilename << " for reading! Error is " << errno << ".\n";
63 if (errno == EACCES) // User is probably not running as root
srs5694fed16d02010-01-27 23:03:40 -050064 cerr << "You must run this program as root or use sudo!\n";
srs569455d92612010-03-07 22:16:07 -050065 if (errno == ENOENT)
66 cerr << "The specified file does not exist!\n";
srs5694add79a62010-01-26 15:59:58 -050067 realFilename = "";
68 userFilename = "";
69 isOpen = 0;
70 openForWrite = 0;
71 } else {
srs56948f1b2d62010-05-23 13:07:19 -040072 isOpen = 0;
srs5694add79a62010-01-26 15:59:58 -050073 openForWrite = 0;
srs56948f1b2d62010-05-23 13:07:19 -040074 if (fstat64(fd, &st) == 0) {
75 if (S_ISDIR(st.st_mode))
76 cerr << "The specified path is a directory!\n";
Guillaume Delacourfd118a42014-07-23 01:28:29 +020077#if !(defined(__FreeBSD__) || defined(__FreeBSD_kernel__)) \
Roderick W. Smith42166482015-06-19 10:26:17 -040078 && !defined(__APPLE__)
srs56948f1b2d62010-05-23 13:07:19 -040079 else if (S_ISCHR(st.st_mode))
80 cerr << "The specified path is a character device!\n";
81#endif
82 else if (S_ISFIFO(st.st_mode))
83 cerr << "The specified path is a FIFO!\n";
84 else if (S_ISSOCK(st.st_mode))
85 cerr << "The specified path is a socket!\n";
86 else
87 isOpen = 1;
88 } // if (fstat64()...)
srs5694add79a62010-01-26 15:59:58 -050089 } // if/else
90 } // if
91
92 return isOpen;
93} // DiskIO::OpenForRead(void)
94
95// An extended file-open function. This includes some system-specific checks.
96// Returns 1 if the file is open, 0 otherwise....
97int DiskIO::OpenForWrite(void) {
98 if ((isOpen) && (openForWrite))
99 return 1;
100
101 // Close the disk, in case it's already open for reading only....
102 Close();
103
104 // try to open the device; may fail....
105 fd = open(realFilename.c_str(), O_WRONLY | O_CREAT, S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH);
106#ifdef __APPLE__
107 // MacOS X requires a shared lock under some circumstances....
108 if (fd < 0) {
Roderick W. Smithe09ef882013-07-08 22:56:00 -0400109 cerr << "Warning: Devices opened with shared lock will not have their\npartition table automatically reloaded!\n";
srs5694fed16d02010-01-27 23:03:40 -0500110 fd = open(realFilename.c_str(), O_WRONLY | O_SHLOCK);
srs5694add79a62010-01-26 15:59:58 -0500111 } // if
112#endif
113 if (fd >= 0) {
114 isOpen = 1;
115 openForWrite = 1;
116 } else {
117 isOpen = 0;
118 openForWrite = 0;
119 } // if/else
120 return isOpen;
121} // DiskIO::OpenForWrite(void)
122
123// Close the disk device. Note that this does NOT erase the stored filenames,
124// so the file can be re-opened without specifying the filename.
125void DiskIO::Close(void) {
126 if (isOpen)
srs569408bb0da2010-02-19 17:19:55 -0500127 if (close(fd) < 0)
128 cerr << "Warning! Problem closing file!\n";
srs5694add79a62010-01-26 15:59:58 -0500129 isOpen = 0;
130 openForWrite = 0;
131} // DiskIO::Close()
132
133// Returns block size of device pointed to by fd file descriptor. If the ioctl
134// returns an error condition, print a warning but return a value of SECTOR_SIZE
srs569455d92612010-03-07 22:16:07 -0500135// (512). If the disk can't be opened at all, return a value of 0.
srs5694add79a62010-01-26 15:59:58 -0500136int DiskIO::GetBlockSize(void) {
137 int err = -1, blockSize = 0;
srs56940741fa22013-01-09 12:55:40 -0500138#ifdef __sun__
139 struct dk_minfo minfo;
140#endif
srs5694add79a62010-01-26 15:59:58 -0500141
142 // If disk isn't open, try to open it....
143 if (!isOpen) {
144 OpenForRead();
145 } // if
146
147 if (isOpen) {
148#ifdef __APPLE__
149 err = ioctl(fd, DKIOCGETBLOCKSIZE, &blockSize);
150#endif
srs56940741fa22013-01-09 12:55:40 -0500151#ifdef __sun__
152 err = ioctl(fd, DKIOCGMEDIAINFO, &minfo);
153 if (err == 0)
154 blockSize = minfo.dki_lbsize;
155#endif
srs569408bb0da2010-02-19 17:19:55 -0500156#if defined (__FreeBSD__) || defined (__FreeBSD_kernel__)
srs5694add79a62010-01-26 15:59:58 -0500157 err = ioctl(fd, DIOCGSECTORSIZE, &blockSize);
158#endif
159#ifdef __linux__
160 err = ioctl(fd, BLKSSZGET, &blockSize);
161#endif
162
163 if (err == -1) {
164 blockSize = SECTOR_SIZE;
165 // ENOTTY = inappropriate ioctl; probably being called on a disk image
166 // file, so don't display the warning message....
167 // 32-bit code returns EINVAL, I don't know why. I know I'm treading on
168 // thin ice here, but it should be OK in all but very weird cases....
169 if ((errno != ENOTTY) && (errno != EINVAL)) {
srs5694fed16d02010-01-27 23:03:40 -0500170 cerr << "\aError " << errno << " when determining sector size! Setting sector size to "
171 << SECTOR_SIZE << "\n";
srs569455d92612010-03-07 22:16:07 -0500172 cout << "Disk device is " << realFilename << "\n";
srs5694add79a62010-01-26 15:59:58 -0500173 } // if
174 } // if (err == -1)
175 } // if (isOpen)
176
177 return (blockSize);
178} // DiskIO::GetBlockSize()
179
Rod Smithfc0e0142017-07-25 21:33:18 -0400180// Returns the physical block size of the device, if possible. If this is
181// not supported, or if an error occurs, this function returns 0.
182// TODO: Get this working in more OSes than Linux.
183int DiskIO::GetPhysBlockSize(void) {
184 int err = -1, physBlockSize = 0;
185
186 // If disk isn't open, try to open it....
187 if (!isOpen) {
188 OpenForRead();
189 } // if
190
191 if (isOpen) {
192#if defined __linux__ && !defined(EFI)
193 err = ioctl(fd, BLKPBSZGET, &physBlockSize);
194#endif
195 } // if (isOpen)
196 if (err == -1)
197 physBlockSize = 0;
198 return (physBlockSize);
199} // DiskIO::GetPhysBlockSize(void)
200
srs5694bf8950c2011-03-12 01:23:12 -0500201// Returns the number of heads, according to the kernel, or 255 if the
202// correct value can't be determined.
203uint32_t DiskIO::GetNumHeads(void) {
204 uint32_t numHeads = 255;
205
206#ifdef HDIO_GETGEO
207 struct hd_geometry geometry;
208
209 // If disk isn't open, try to open it....
210 if (!isOpen)
211 OpenForRead();
212
213 if (!ioctl(fd, HDIO_GETGEO, &geometry))
214 numHeads = (uint32_t) geometry.heads;
215#endif
216 return numHeads;
217} // DiskIO::GetNumHeads();
218
219// Returns the number of sectors per track, according to the kernel, or 63
220// if the correct value can't be determined.
221uint32_t DiskIO::GetNumSecsPerTrack(void) {
222 uint32_t numSecs = 63;
223
224 #ifdef HDIO_GETGEO
225 struct hd_geometry geometry;
226
227 // If disk isn't open, try to open it....
228 if (!isOpen)
229 OpenForRead();
230
231 if (!ioctl(fd, HDIO_GETGEO, &geometry))
232 numSecs = (uint32_t) geometry.sectors;
233 #endif
234 return numSecs;
235} // DiskIO::GetNumSecsPerTrack()
236
srs5694add79a62010-01-26 15:59:58 -0500237// Resync disk caches so the OS uses the new partition table. This code varies
238// a lot from one OS to another.
srs5694a17fe692011-09-10 20:30:20 -0400239// Returns 1 on success, 0 if the kernel continues to use the old partition table.
240// (Note that for most OSes, the default of 0 is returned because I've not yet
241// looked into how to test for success in the underlying system calls...)
242int DiskIO::DiskSync(void) {
243 int i, retval = 0, platformFound = 0;
srs5694add79a62010-01-26 15:59:58 -0500244
245 // If disk isn't open, try to open it....
246 if (!isOpen) {
247 OpenForRead();
248 } // if
249
250 if (isOpen) {
251 sync();
srs56940741fa22013-01-09 12:55:40 -0500252#if defined(__APPLE__) || defined(__sun__)
srs5694fed16d02010-01-27 23:03:40 -0500253 cout << "Warning: The kernel may continue to use old or deleted partitions.\n"
254 << "You should reboot or remove the drive.\n";
srs5694add79a62010-01-26 15:59:58 -0500255 /* don't know if this helps
256 * it definitely will get things on disk though:
257 * http://topiks.org/mac-os-x/0321278542/ch12lev1sec8.html */
srs56940741fa22013-01-09 12:55:40 -0500258#ifdef __sun__
259 i = ioctl(fd, DKIOCFLUSHWRITECACHE);
260#else
srs5694add79a62010-01-26 15:59:58 -0500261 i = ioctl(fd, DKIOCSYNCHRONIZECACHE);
srs56940741fa22013-01-09 12:55:40 -0500262#endif
srs5694add79a62010-01-26 15:59:58 -0500263 platformFound++;
264#endif
srs569408bb0da2010-02-19 17:19:55 -0500265#if defined (__FreeBSD__) || defined (__FreeBSD_kernel__)
srs5694add79a62010-01-26 15:59:58 -0500266 sleep(2);
267 i = ioctl(fd, DIOCGFLUSH);
srs5694fed16d02010-01-27 23:03:40 -0500268 cout << "Warning: The kernel may continue to use old or deleted partitions.\n"
269 << "You should reboot or remove the drive.\n";
srs5694add79a62010-01-26 15:59:58 -0500270 platformFound++;
271#endif
272#ifdef __linux__
srs569400b6d7a2011-06-26 22:40:06 -0400273 sleep(1); // Theoretically unnecessary, but ioctl() fails sometimes if omitted....
274 fsync(fd);
srs5694add79a62010-01-26 15:59:58 -0500275 i = ioctl(fd, BLKRRPART);
srs5694a17fe692011-09-10 20:30:20 -0400276 if (i) {
srs5694fed16d02010-01-27 23:03:40 -0500277 cout << "Warning: The kernel is still using the old partition table.\n"
Roderick W. Smith54f8fb12015-03-17 19:46:05 -0400278 << "The new table will be used at the next reboot or after you\n"
279 << "run partprobe(8) or kpartx(8)\n";
srs5694a17fe692011-09-10 20:30:20 -0400280 } else {
281 retval = 1;
282 } // if/else
srs5694add79a62010-01-26 15:59:58 -0500283 platformFound++;
284#endif
285 if (platformFound == 0)
srs5694fed16d02010-01-27 23:03:40 -0500286 cerr << "Warning: Platform not recognized!\n";
srs5694add79a62010-01-26 15:59:58 -0500287 if (platformFound > 1)
srs5694fed16d02010-01-27 23:03:40 -0500288 cerr << "\nWarning: We seem to be running on multiple platforms!\n";
srs5694add79a62010-01-26 15:59:58 -0500289 } // if (isOpen)
srs5694a17fe692011-09-10 20:30:20 -0400290 return retval;
srs5694add79a62010-01-26 15:59:58 -0500291} // DiskIO::DiskSync()
292
293// Seek to the specified sector. Returns 1 on success, 0 on failure.
srs5694cb76c672010-02-11 22:22:22 -0500294// Note that seeking beyond the end of the file is NOT detected as a failure!
srs5694add79a62010-01-26 15:59:58 -0500295int DiskIO::Seek(uint64_t sector) {
296 int retval = 1;
297 off_t seekTo, sought;
298
299 // If disk isn't open, try to open it....
300 if (!isOpen) {
301 retval = OpenForRead();
302 } // if
303
304 if (isOpen) {
305 seekTo = sector * (uint64_t) GetBlockSize();
306 sought = lseek64(fd, seekTo, SEEK_SET);
307 if (sought != seekTo) {
308 retval = 0;
309 } // if
310 } // if
311 return retval;
312} // DiskIO::Seek()
313
314// A variant on the standard read() function. Done to work around
315// limitations in FreeBSD concerning the matching of the sector
316// size with the number of bytes read.
317// Returns the number of bytes read into buffer.
318int DiskIO::Read(void* buffer, int numBytes) {
srs5694cb76c672010-02-11 22:22:22 -0500319 int blockSize, numBlocks, retval = 0;
srs5694add79a62010-01-26 15:59:58 -0500320 char* tempSpace;
321
322 // If disk isn't open, try to open it....
323 if (!isOpen) {
324 OpenForRead();
325 } // if
326
327 if (isOpen) {
328 // Compute required space and allocate memory
329 blockSize = GetBlockSize();
330 if (numBytes <= blockSize) {
331 numBlocks = 1;
srs5694cb76c672010-02-11 22:22:22 -0500332 tempSpace = new char [blockSize];
srs5694add79a62010-01-26 15:59:58 -0500333 } else {
334 numBlocks = numBytes / blockSize;
srs5694cb76c672010-02-11 22:22:22 -0500335 if ((numBytes % blockSize) != 0)
336 numBlocks++;
337 tempSpace = new char [numBlocks * blockSize];
srs5694add79a62010-01-26 15:59:58 -0500338 } // if/else
srs56946aae2a92011-06-10 01:16:51 -0400339 if (tempSpace == NULL) {
340 cerr << "Unable to allocate memory in DiskIO::Read()! Terminating!\n";
341 exit(1);
342 } // if
srs5694add79a62010-01-26 15:59:58 -0500343
344 // Read the data into temporary space, then copy it to buffer
345 retval = read(fd, tempSpace, numBlocks * blockSize);
346 memcpy(buffer, tempSpace, numBytes);
srs5694add79a62010-01-26 15:59:58 -0500347
348 // Adjust the return value, if necessary....
349 if (((numBlocks * blockSize) != numBytes) && (retval > 0))
350 retval = numBytes;
351
srs5694cb76c672010-02-11 22:22:22 -0500352 delete[] tempSpace;
srs5694add79a62010-01-26 15:59:58 -0500353 } // if (isOpen)
354 return retval;
355} // DiskIO::Read()
356
357// A variant on the standard write() function. Done to work around
358// limitations in FreeBSD concerning the matching of the sector
359// size with the number of bytes read.
360// Returns the number of bytes written.
361int DiskIO::Write(void* buffer, int numBytes) {
362 int blockSize = 512, i, numBlocks, retval = 0;
363 char* tempSpace;
364
365 // If disk isn't open, try to open it....
366 if ((!isOpen) || (!openForWrite)) {
367 OpenForWrite();
368 } // if
369
370 if (isOpen) {
371 // Compute required space and allocate memory
372 blockSize = GetBlockSize();
373 if (numBytes <= blockSize) {
374 numBlocks = 1;
srs5694cb76c672010-02-11 22:22:22 -0500375 tempSpace = new char [blockSize];
srs5694add79a62010-01-26 15:59:58 -0500376 } else {
377 numBlocks = numBytes / blockSize;
378 if ((numBytes % blockSize) != 0) numBlocks++;
srs5694cb76c672010-02-11 22:22:22 -0500379 tempSpace = new char [numBlocks * blockSize];
srs5694add79a62010-01-26 15:59:58 -0500380 } // if/else
srs56946aae2a92011-06-10 01:16:51 -0400381 if (tempSpace == NULL) {
382 cerr << "Unable to allocate memory in DiskIO::Write()! Terminating!\n";
383 exit(1);
384 } // if
385
srs5694add79a62010-01-26 15:59:58 -0500386 // Copy the data to my own buffer, then write it
srs5694add79a62010-01-26 15:59:58 -0500387 memcpy(tempSpace, buffer, numBytes);
388 for (i = numBytes; i < numBlocks * blockSize; i++) {
389 tempSpace[i] = 0;
390 } // for
391 retval = write(fd, tempSpace, numBlocks * blockSize);
392
393 // Adjust the return value, if necessary....
394 if (((numBlocks * blockSize) != numBytes) && (retval > 0))
395 retval = numBytes;
396
srs5694cb76c672010-02-11 22:22:22 -0500397 delete[] tempSpace;
srs5694add79a62010-01-26 15:59:58 -0500398 } // if (isOpen)
399 return retval;
400} // DiskIO:Write()
401
402/**************************************************************************************
403 * *
404 * Below functions are lifted from various sources, as documented in comments before *
405 * each one. *
406 * *
407 **************************************************************************************/
408
409// The disksize function is taken from the Linux fdisk code and modified
410// greatly since then to enable FreeBSD and MacOS support, as well as to
411// return correct values for disk image files.
412uint64_t DiskIO::DiskSize(int *err) {
srs5694add79a62010-01-26 15:59:58 -0500413 uint64_t sectors = 0; // size in sectors
414 off_t bytes = 0; // size in bytes
415 struct stat64 st;
416 int platformFound = 0;
srs56940741fa22013-01-09 12:55:40 -0500417#ifdef __sun__
418 struct dk_minfo minfo;
419#endif
srs5694add79a62010-01-26 15:59:58 -0500420
421 // If disk isn't open, try to open it....
422 if (!isOpen) {
423 OpenForRead();
424 } // if
425
426 if (isOpen) {
427 // Note to self: I recall testing a simplified version of
428 // this code, similar to what's in the __APPLE__ block,
429 // on Linux, but I had some problems. IIRC, it ran OK on 32-bit
430 // systems but not on 64-bit. Keep this in mind in case of
431 // 32/64-bit issues on MacOS....
432#ifdef __APPLE__
433 *err = ioctl(fd, DKIOCGETBLOCKCOUNT, &sectors);
434 platformFound++;
435#endif
srs56940741fa22013-01-09 12:55:40 -0500436#ifdef __sun__
437 *err = ioctl(fd, DKIOCGMEDIAINFO, &minfo);
438 if (*err == 0)
439 sectors = minfo.dki_capacity;
440 platformFound++;
441#endif
srs569408bb0da2010-02-19 17:19:55 -0500442#if defined (__FreeBSD__) || defined (__FreeBSD_kernel__)
srs5694add79a62010-01-26 15:59:58 -0500443 *err = ioctl(fd, DIOCGMEDIASIZE, &bytes);
srs569408bb0da2010-02-19 17:19:55 -0500444 long long b = GetBlockSize();
srs5694add79a62010-01-26 15:59:58 -0500445 sectors = bytes / b;
446 platformFound++;
447#endif
448#ifdef __linux__
srs569408bb0da2010-02-19 17:19:55 -0500449 long sz;
450 long long b;
srs5694add79a62010-01-26 15:59:58 -0500451 *err = ioctl(fd, BLKGETSIZE, &sz);
452 if (*err) {
453 sectors = sz = 0;
454 } // if
srs569464cbd172011-03-01 22:03:54 -0500455 if ((!*err) || (errno == EFBIG)) {
srs5694add79a62010-01-26 15:59:58 -0500456 *err = ioctl(fd, BLKGETSIZE64, &b);
457 if (*err || b == 0 || b == sz)
458 sectors = sz;
459 else
460 sectors = (b >> 9);
461 } // if
462 // Unintuitively, the above returns values in 512-byte blocks, no
463 // matter what the underlying device's block size. Correct for this....
464 sectors /= (GetBlockSize() / 512);
465 platformFound++;
466#endif
467 if (platformFound != 1)
srs5694fed16d02010-01-27 23:03:40 -0500468 cerr << "Warning! We seem to be running on no known platform!\n";
srs5694add79a62010-01-26 15:59:58 -0500469
470 // The above methods have failed, so let's assume it's a regular
471 // file (a QEMU image, dd backup, or what have you) and see what
472 // fstat() gives us....
473 if ((sectors == 0) || (*err == -1)) {
474 if (fstat64(fd, &st) == 0) {
srs56949a46b042011-03-15 00:34:10 -0400475 bytes = st.st_size;
srs5694add79a62010-01-26 15:59:58 -0500476 if ((bytes % UINT64_C(512)) != 0)
srs5694fed16d02010-01-27 23:03:40 -0500477 cerr << "Warning: File size is not a multiple of 512 bytes!"
478 << " Misbehavior is likely!\n\a";
srs5694add79a62010-01-26 15:59:58 -0500479 sectors = bytes / UINT64_C(512);
480 } // if
481 } // if
482 } // if (isOpen)
483 return sectors;
484} // DiskIO::DiskSize()