blob: d9f8b8d8bedc72346b30f12e1495691a99d48700 [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>
Rod Smith7dfc8962017-07-26 19:45:51 -040033#include <fstream>
34#include <sstream>
srs5694add79a62010-01-26 15:59:58 -050035
srs5694add79a62010-01-26 15:59:58 -050036#include "diskio.h"
37
38using namespace std;
39
40// Returns the official "real" name for a shortened version of same.
41// Trivial here; more important in Windows
42void DiskIO::MakeRealName(void) {
43 realFilename = userFilename;
44} // DiskIO::MakeRealName()
45
srs569455d92612010-03-07 22:16:07 -050046// Open the currently on-record file for reading. Returns 1 if the file is
47// already open or is opened by this call, 0 if opening the file doesn't
48// work.
srs5694add79a62010-01-26 15:59:58 -050049int DiskIO::OpenForRead(void) {
50 int shouldOpen = 1;
srs56948f1b2d62010-05-23 13:07:19 -040051 struct stat64 st;
srs5694add79a62010-01-26 15:59:58 -050052
53 if (isOpen) { // file is already open
54 if (openForWrite) {
55 Close();
56 } else {
57 shouldOpen = 0;
58 } // if/else
59 } // if
60
61 if (shouldOpen) {
62 fd = open(realFilename.c_str(), O_RDONLY);
63 if (fd == -1) {
srs569455d92612010-03-07 22:16:07 -050064 cerr << "Problem opening " << realFilename << " for reading! Error is " << errno << ".\n";
65 if (errno == EACCES) // User is probably not running as root
srs5694fed16d02010-01-27 23:03:40 -050066 cerr << "You must run this program as root or use sudo!\n";
srs569455d92612010-03-07 22:16:07 -050067 if (errno == ENOENT)
68 cerr << "The specified file does not exist!\n";
srs5694add79a62010-01-26 15:59:58 -050069 realFilename = "";
70 userFilename = "";
Rod Smith7dfc8962017-07-26 19:45:51 -040071 modelName = "";
srs5694add79a62010-01-26 15:59:58 -050072 isOpen = 0;
73 openForWrite = 0;
74 } else {
srs56948f1b2d62010-05-23 13:07:19 -040075 isOpen = 0;
srs5694add79a62010-01-26 15:59:58 -050076 openForWrite = 0;
srs56948f1b2d62010-05-23 13:07:19 -040077 if (fstat64(fd, &st) == 0) {
78 if (S_ISDIR(st.st_mode))
79 cerr << "The specified path is a directory!\n";
Guillaume Delacourfd118a42014-07-23 01:28:29 +020080#if !(defined(__FreeBSD__) || defined(__FreeBSD_kernel__)) \
Roderick W. Smith42166482015-06-19 10:26:17 -040081 && !defined(__APPLE__)
srs56948f1b2d62010-05-23 13:07:19 -040082 else if (S_ISCHR(st.st_mode))
83 cerr << "The specified path is a character device!\n";
84#endif
85 else if (S_ISFIFO(st.st_mode))
86 cerr << "The specified path is a FIFO!\n";
87 else if (S_ISSOCK(st.st_mode))
88 cerr << "The specified path is a socket!\n";
89 else
90 isOpen = 1;
91 } // if (fstat64()...)
Rod Smith7dfc8962017-07-26 19:45:51 -040092#if defined(__linux__) && !defined(EFI)
93 if (isOpen && realFilename.substr(0,4) == "/dev") {
94 ostringstream modelNameFilename;
95 modelNameFilename << "/sys/block" << realFilename.substr(4,512) << "/device/model";
96 ifstream modelNameFile(modelNameFilename.str().c_str());
97 if (modelNameFile.is_open()) {
98 getline(modelNameFile, modelName);
99 } // if
100 } // if
101#endif
srs5694add79a62010-01-26 15:59:58 -0500102 } // if/else
103 } // if
104
105 return isOpen;
106} // DiskIO::OpenForRead(void)
107
108// An extended file-open function. This includes some system-specific checks.
109// Returns 1 if the file is open, 0 otherwise....
110int DiskIO::OpenForWrite(void) {
111 if ((isOpen) && (openForWrite))
112 return 1;
113
114 // Close the disk, in case it's already open for reading only....
115 Close();
116
117 // try to open the device; may fail....
118 fd = open(realFilename.c_str(), O_WRONLY | O_CREAT, S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH);
119#ifdef __APPLE__
120 // MacOS X requires a shared lock under some circumstances....
121 if (fd < 0) {
Roderick W. Smithe09ef882013-07-08 22:56:00 -0400122 cerr << "Warning: Devices opened with shared lock will not have their\npartition table automatically reloaded!\n";
srs5694fed16d02010-01-27 23:03:40 -0500123 fd = open(realFilename.c_str(), O_WRONLY | O_SHLOCK);
srs5694add79a62010-01-26 15:59:58 -0500124 } // if
125#endif
126 if (fd >= 0) {
127 isOpen = 1;
128 openForWrite = 1;
129 } else {
130 isOpen = 0;
131 openForWrite = 0;
132 } // if/else
133 return isOpen;
134} // DiskIO::OpenForWrite(void)
135
136// Close the disk device. Note that this does NOT erase the stored filenames,
137// so the file can be re-opened without specifying the filename.
138void DiskIO::Close(void) {
139 if (isOpen)
srs569408bb0da2010-02-19 17:19:55 -0500140 if (close(fd) < 0)
141 cerr << "Warning! Problem closing file!\n";
srs5694add79a62010-01-26 15:59:58 -0500142 isOpen = 0;
143 openForWrite = 0;
144} // DiskIO::Close()
145
146// Returns block size of device pointed to by fd file descriptor. If the ioctl
147// returns an error condition, print a warning but return a value of SECTOR_SIZE
srs569455d92612010-03-07 22:16:07 -0500148// (512). If the disk can't be opened at all, return a value of 0.
srs5694add79a62010-01-26 15:59:58 -0500149int DiskIO::GetBlockSize(void) {
150 int err = -1, blockSize = 0;
srs56940741fa22013-01-09 12:55:40 -0500151#ifdef __sun__
152 struct dk_minfo minfo;
153#endif
srs5694add79a62010-01-26 15:59:58 -0500154
155 // If disk isn't open, try to open it....
156 if (!isOpen) {
157 OpenForRead();
158 } // if
159
160 if (isOpen) {
161#ifdef __APPLE__
162 err = ioctl(fd, DKIOCGETBLOCKSIZE, &blockSize);
163#endif
srs56940741fa22013-01-09 12:55:40 -0500164#ifdef __sun__
165 err = ioctl(fd, DKIOCGMEDIAINFO, &minfo);
166 if (err == 0)
167 blockSize = minfo.dki_lbsize;
168#endif
srs569408bb0da2010-02-19 17:19:55 -0500169#if defined (__FreeBSD__) || defined (__FreeBSD_kernel__)
srs5694add79a62010-01-26 15:59:58 -0500170 err = ioctl(fd, DIOCGSECTORSIZE, &blockSize);
171#endif
172#ifdef __linux__
173 err = ioctl(fd, BLKSSZGET, &blockSize);
174#endif
175
176 if (err == -1) {
177 blockSize = SECTOR_SIZE;
178 // ENOTTY = inappropriate ioctl; probably being called on a disk image
179 // file, so don't display the warning message....
180 // 32-bit code returns EINVAL, I don't know why. I know I'm treading on
181 // thin ice here, but it should be OK in all but very weird cases....
182 if ((errno != ENOTTY) && (errno != EINVAL)) {
srs5694fed16d02010-01-27 23:03:40 -0500183 cerr << "\aError " << errno << " when determining sector size! Setting sector size to "
184 << SECTOR_SIZE << "\n";
srs569455d92612010-03-07 22:16:07 -0500185 cout << "Disk device is " << realFilename << "\n";
srs5694add79a62010-01-26 15:59:58 -0500186 } // if
187 } // if (err == -1)
188 } // if (isOpen)
189
190 return (blockSize);
191} // DiskIO::GetBlockSize()
192
Rod Smithfc0e0142017-07-25 21:33:18 -0400193// Returns the physical block size of the device, if possible. If this is
194// not supported, or if an error occurs, this function returns 0.
195// TODO: Get this working in more OSes than Linux.
196int DiskIO::GetPhysBlockSize(void) {
197 int err = -1, physBlockSize = 0;
198
199 // If disk isn't open, try to open it....
200 if (!isOpen) {
201 OpenForRead();
202 } // if
203
204 if (isOpen) {
205#if defined __linux__ && !defined(EFI)
206 err = ioctl(fd, BLKPBSZGET, &physBlockSize);
207#endif
208 } // if (isOpen)
209 if (err == -1)
210 physBlockSize = 0;
211 return (physBlockSize);
212} // DiskIO::GetPhysBlockSize(void)
213
srs5694bf8950c2011-03-12 01:23:12 -0500214// Returns the number of heads, according to the kernel, or 255 if the
215// correct value can't be determined.
216uint32_t DiskIO::GetNumHeads(void) {
217 uint32_t numHeads = 255;
218
219#ifdef HDIO_GETGEO
220 struct hd_geometry geometry;
221
222 // If disk isn't open, try to open it....
223 if (!isOpen)
224 OpenForRead();
225
226 if (!ioctl(fd, HDIO_GETGEO, &geometry))
227 numHeads = (uint32_t) geometry.heads;
228#endif
229 return numHeads;
230} // DiskIO::GetNumHeads();
231
232// Returns the number of sectors per track, according to the kernel, or 63
233// if the correct value can't be determined.
234uint32_t DiskIO::GetNumSecsPerTrack(void) {
235 uint32_t numSecs = 63;
236
237 #ifdef HDIO_GETGEO
238 struct hd_geometry geometry;
239
240 // If disk isn't open, try to open it....
241 if (!isOpen)
242 OpenForRead();
243
244 if (!ioctl(fd, HDIO_GETGEO, &geometry))
245 numSecs = (uint32_t) geometry.sectors;
246 #endif
247 return numSecs;
248} // DiskIO::GetNumSecsPerTrack()
249
srs5694add79a62010-01-26 15:59:58 -0500250// Resync disk caches so the OS uses the new partition table. This code varies
251// a lot from one OS to another.
srs5694a17fe692011-09-10 20:30:20 -0400252// Returns 1 on success, 0 if the kernel continues to use the old partition table.
253// (Note that for most OSes, the default of 0 is returned because I've not yet
254// looked into how to test for success in the underlying system calls...)
255int DiskIO::DiskSync(void) {
256 int i, retval = 0, platformFound = 0;
srs5694add79a62010-01-26 15:59:58 -0500257
258 // If disk isn't open, try to open it....
259 if (!isOpen) {
260 OpenForRead();
261 } // if
262
263 if (isOpen) {
264 sync();
srs56940741fa22013-01-09 12:55:40 -0500265#if defined(__APPLE__) || defined(__sun__)
srs5694fed16d02010-01-27 23:03:40 -0500266 cout << "Warning: The kernel may continue to use old or deleted partitions.\n"
267 << "You should reboot or remove the drive.\n";
srs5694add79a62010-01-26 15:59:58 -0500268 /* don't know if this helps
269 * it definitely will get things on disk though:
270 * http://topiks.org/mac-os-x/0321278542/ch12lev1sec8.html */
srs56940741fa22013-01-09 12:55:40 -0500271#ifdef __sun__
272 i = ioctl(fd, DKIOCFLUSHWRITECACHE);
273#else
srs5694add79a62010-01-26 15:59:58 -0500274 i = ioctl(fd, DKIOCSYNCHRONIZECACHE);
srs56940741fa22013-01-09 12:55:40 -0500275#endif
srs5694add79a62010-01-26 15:59:58 -0500276 platformFound++;
277#endif
srs569408bb0da2010-02-19 17:19:55 -0500278#if defined (__FreeBSD__) || defined (__FreeBSD_kernel__)
srs5694add79a62010-01-26 15:59:58 -0500279 sleep(2);
280 i = ioctl(fd, DIOCGFLUSH);
srs5694fed16d02010-01-27 23:03:40 -0500281 cout << "Warning: The kernel may continue to use old or deleted partitions.\n"
282 << "You should reboot or remove the drive.\n";
srs5694add79a62010-01-26 15:59:58 -0500283 platformFound++;
284#endif
285#ifdef __linux__
srs569400b6d7a2011-06-26 22:40:06 -0400286 sleep(1); // Theoretically unnecessary, but ioctl() fails sometimes if omitted....
287 fsync(fd);
srs5694add79a62010-01-26 15:59:58 -0500288 i = ioctl(fd, BLKRRPART);
srs5694a17fe692011-09-10 20:30:20 -0400289 if (i) {
srs5694fed16d02010-01-27 23:03:40 -0500290 cout << "Warning: The kernel is still using the old partition table.\n"
Roderick W. Smith54f8fb12015-03-17 19:46:05 -0400291 << "The new table will be used at the next reboot or after you\n"
292 << "run partprobe(8) or kpartx(8)\n";
srs5694a17fe692011-09-10 20:30:20 -0400293 } else {
294 retval = 1;
295 } // if/else
srs5694add79a62010-01-26 15:59:58 -0500296 platformFound++;
297#endif
298 if (platformFound == 0)
srs5694fed16d02010-01-27 23:03:40 -0500299 cerr << "Warning: Platform not recognized!\n";
srs5694add79a62010-01-26 15:59:58 -0500300 if (platformFound > 1)
srs5694fed16d02010-01-27 23:03:40 -0500301 cerr << "\nWarning: We seem to be running on multiple platforms!\n";
srs5694add79a62010-01-26 15:59:58 -0500302 } // if (isOpen)
srs5694a17fe692011-09-10 20:30:20 -0400303 return retval;
srs5694add79a62010-01-26 15:59:58 -0500304} // DiskIO::DiskSync()
305
306// Seek to the specified sector. Returns 1 on success, 0 on failure.
srs5694cb76c672010-02-11 22:22:22 -0500307// Note that seeking beyond the end of the file is NOT detected as a failure!
srs5694add79a62010-01-26 15:59:58 -0500308int DiskIO::Seek(uint64_t sector) {
309 int retval = 1;
310 off_t seekTo, sought;
311
312 // If disk isn't open, try to open it....
313 if (!isOpen) {
314 retval = OpenForRead();
315 } // if
316
317 if (isOpen) {
318 seekTo = sector * (uint64_t) GetBlockSize();
319 sought = lseek64(fd, seekTo, SEEK_SET);
320 if (sought != seekTo) {
321 retval = 0;
322 } // if
323 } // if
324 return retval;
325} // DiskIO::Seek()
326
327// A variant on the standard read() function. Done to work around
328// limitations in FreeBSD concerning the matching of the sector
329// size with the number of bytes read.
330// Returns the number of bytes read into buffer.
331int DiskIO::Read(void* buffer, int numBytes) {
srs5694cb76c672010-02-11 22:22:22 -0500332 int blockSize, numBlocks, retval = 0;
srs5694add79a62010-01-26 15:59:58 -0500333 char* tempSpace;
334
335 // If disk isn't open, try to open it....
336 if (!isOpen) {
337 OpenForRead();
338 } // if
339
340 if (isOpen) {
341 // Compute required space and allocate memory
342 blockSize = GetBlockSize();
343 if (numBytes <= blockSize) {
344 numBlocks = 1;
srs5694cb76c672010-02-11 22:22:22 -0500345 tempSpace = new char [blockSize];
srs5694add79a62010-01-26 15:59:58 -0500346 } else {
347 numBlocks = numBytes / blockSize;
srs5694cb76c672010-02-11 22:22:22 -0500348 if ((numBytes % blockSize) != 0)
349 numBlocks++;
350 tempSpace = new char [numBlocks * blockSize];
srs5694add79a62010-01-26 15:59:58 -0500351 } // if/else
srs56946aae2a92011-06-10 01:16:51 -0400352 if (tempSpace == NULL) {
353 cerr << "Unable to allocate memory in DiskIO::Read()! Terminating!\n";
354 exit(1);
355 } // if
srs5694add79a62010-01-26 15:59:58 -0500356
357 // Read the data into temporary space, then copy it to buffer
358 retval = read(fd, tempSpace, numBlocks * blockSize);
359 memcpy(buffer, tempSpace, numBytes);
srs5694add79a62010-01-26 15:59:58 -0500360
361 // Adjust the return value, if necessary....
362 if (((numBlocks * blockSize) != numBytes) && (retval > 0))
363 retval = numBytes;
364
srs5694cb76c672010-02-11 22:22:22 -0500365 delete[] tempSpace;
srs5694add79a62010-01-26 15:59:58 -0500366 } // if (isOpen)
367 return retval;
368} // DiskIO::Read()
369
370// A variant on the standard write() function. Done to work around
371// limitations in FreeBSD concerning the matching of the sector
372// size with the number of bytes read.
373// Returns the number of bytes written.
374int DiskIO::Write(void* buffer, int numBytes) {
375 int blockSize = 512, i, numBlocks, retval = 0;
376 char* tempSpace;
377
378 // If disk isn't open, try to open it....
379 if ((!isOpen) || (!openForWrite)) {
380 OpenForWrite();
381 } // if
382
383 if (isOpen) {
384 // Compute required space and allocate memory
385 blockSize = GetBlockSize();
386 if (numBytes <= blockSize) {
387 numBlocks = 1;
srs5694cb76c672010-02-11 22:22:22 -0500388 tempSpace = new char [blockSize];
srs5694add79a62010-01-26 15:59:58 -0500389 } else {
390 numBlocks = numBytes / blockSize;
391 if ((numBytes % blockSize) != 0) numBlocks++;
srs5694cb76c672010-02-11 22:22:22 -0500392 tempSpace = new char [numBlocks * blockSize];
srs5694add79a62010-01-26 15:59:58 -0500393 } // if/else
srs56946aae2a92011-06-10 01:16:51 -0400394 if (tempSpace == NULL) {
395 cerr << "Unable to allocate memory in DiskIO::Write()! Terminating!\n";
396 exit(1);
397 } // if
398
srs5694add79a62010-01-26 15:59:58 -0500399 // Copy the data to my own buffer, then write it
srs5694add79a62010-01-26 15:59:58 -0500400 memcpy(tempSpace, buffer, numBytes);
401 for (i = numBytes; i < numBlocks * blockSize; i++) {
402 tempSpace[i] = 0;
403 } // for
404 retval = write(fd, tempSpace, numBlocks * blockSize);
405
406 // Adjust the return value, if necessary....
407 if (((numBlocks * blockSize) != numBytes) && (retval > 0))
408 retval = numBytes;
409
srs5694cb76c672010-02-11 22:22:22 -0500410 delete[] tempSpace;
srs5694add79a62010-01-26 15:59:58 -0500411 } // if (isOpen)
412 return retval;
413} // DiskIO:Write()
414
415/**************************************************************************************
416 * *
417 * Below functions are lifted from various sources, as documented in comments before *
418 * each one. *
419 * *
420 **************************************************************************************/
421
422// The disksize function is taken from the Linux fdisk code and modified
423// greatly since then to enable FreeBSD and MacOS support, as well as to
424// return correct values for disk image files.
425uint64_t DiskIO::DiskSize(int *err) {
srs5694add79a62010-01-26 15:59:58 -0500426 uint64_t sectors = 0; // size in sectors
427 off_t bytes = 0; // size in bytes
428 struct stat64 st;
429 int platformFound = 0;
srs56940741fa22013-01-09 12:55:40 -0500430#ifdef __sun__
431 struct dk_minfo minfo;
432#endif
srs5694add79a62010-01-26 15:59:58 -0500433
434 // If disk isn't open, try to open it....
435 if (!isOpen) {
436 OpenForRead();
437 } // if
438
439 if (isOpen) {
440 // Note to self: I recall testing a simplified version of
441 // this code, similar to what's in the __APPLE__ block,
442 // on Linux, but I had some problems. IIRC, it ran OK on 32-bit
443 // systems but not on 64-bit. Keep this in mind in case of
444 // 32/64-bit issues on MacOS....
445#ifdef __APPLE__
446 *err = ioctl(fd, DKIOCGETBLOCKCOUNT, &sectors);
447 platformFound++;
448#endif
srs56940741fa22013-01-09 12:55:40 -0500449#ifdef __sun__
450 *err = ioctl(fd, DKIOCGMEDIAINFO, &minfo);
451 if (*err == 0)
452 sectors = minfo.dki_capacity;
453 platformFound++;
454#endif
srs569408bb0da2010-02-19 17:19:55 -0500455#if defined (__FreeBSD__) || defined (__FreeBSD_kernel__)
srs5694add79a62010-01-26 15:59:58 -0500456 *err = ioctl(fd, DIOCGMEDIASIZE, &bytes);
srs569408bb0da2010-02-19 17:19:55 -0500457 long long b = GetBlockSize();
srs5694add79a62010-01-26 15:59:58 -0500458 sectors = bytes / b;
459 platformFound++;
460#endif
461#ifdef __linux__
srs569408bb0da2010-02-19 17:19:55 -0500462 long sz;
463 long long b;
srs5694add79a62010-01-26 15:59:58 -0500464 *err = ioctl(fd, BLKGETSIZE, &sz);
465 if (*err) {
466 sectors = sz = 0;
467 } // if
srs569464cbd172011-03-01 22:03:54 -0500468 if ((!*err) || (errno == EFBIG)) {
srs5694add79a62010-01-26 15:59:58 -0500469 *err = ioctl(fd, BLKGETSIZE64, &b);
470 if (*err || b == 0 || b == sz)
471 sectors = sz;
472 else
473 sectors = (b >> 9);
474 } // if
475 // Unintuitively, the above returns values in 512-byte blocks, no
476 // matter what the underlying device's block size. Correct for this....
477 sectors /= (GetBlockSize() / 512);
478 platformFound++;
479#endif
480 if (platformFound != 1)
srs5694fed16d02010-01-27 23:03:40 -0500481 cerr << "Warning! We seem to be running on no known platform!\n";
srs5694add79a62010-01-26 15:59:58 -0500482
483 // The above methods have failed, so let's assume it's a regular
484 // file (a QEMU image, dd backup, or what have you) and see what
485 // fstat() gives us....
486 if ((sectors == 0) || (*err == -1)) {
487 if (fstat64(fd, &st) == 0) {
srs56949a46b042011-03-15 00:34:10 -0400488 bytes = st.st_size;
srs5694add79a62010-01-26 15:59:58 -0500489 if ((bytes % UINT64_C(512)) != 0)
srs5694fed16d02010-01-27 23:03:40 -0500490 cerr << "Warning: File size is not a multiple of 512 bytes!"
491 << " Misbehavior is likely!\n\a";
srs5694add79a62010-01-26 15:59:58 -0500492 sectors = bytes / UINT64_C(512);
493 } // if
494 } // if
495 } // if (isOpen)
496 return sectors;
497} // DiskIO::DiskSize()