blob: 6d3e5b1c94bc4aca305a2c21522f046616b9a284 [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
Aurimas Liutikasfcad0602016-05-10 19:16:10 -070016#ifndef __STDC_CONSTANT_MACROS
srs5694add79a62010-01-26 15:59:58 -050017#define __STDC_CONSTANT_MACROS
Aurimas Liutikasfcad0602016-05-10 19:16:10 -070018#endif
srs5694add79a62010-01-26 15:59:58 -050019
20#include <sys/ioctl.h>
srs5694fed16d02010-01-27 23:03:40 -050021#include <string.h>
srs5694add79a62010-01-26 15:59:58 -050022#include <string>
23#include <stdint.h>
Roderick W. Smith24bba6e2013-10-12 19:07:16 -040024#include <unistd.h>
srs5694add79a62010-01-26 15:59:58 -050025#include <errno.h>
26#include <fcntl.h>
27#include <sys/stat.h>
srs569434882942012-03-23 12:49:15 -040028#include <unistd.h>
srs5694bf8950c2011-03-12 01:23:12 -050029
30#ifdef __linux__
31#include "linux/hdreg.h"
32#endif
33
srs5694add79a62010-01-26 15:59:58 -050034#include <iostream>
Rod Smith7dfc8962017-07-26 19:45:51 -040035#include <fstream>
36#include <sstream>
srs5694add79a62010-01-26 15:59:58 -050037
srs5694add79a62010-01-26 15:59:58 -050038#include "diskio.h"
39
40using namespace std;
41
42// Returns the official "real" name for a shortened version of same.
43// Trivial here; more important in Windows
44void DiskIO::MakeRealName(void) {
45 realFilename = userFilename;
46} // DiskIO::MakeRealName()
47
srs569455d92612010-03-07 22:16:07 -050048// Open the currently on-record file for reading. Returns 1 if the file is
49// already open or is opened by this call, 0 if opening the file doesn't
50// work.
srs5694add79a62010-01-26 15:59:58 -050051int DiskIO::OpenForRead(void) {
52 int shouldOpen = 1;
srs56948f1b2d62010-05-23 13:07:19 -040053 struct stat64 st;
srs5694add79a62010-01-26 15:59:58 -050054
55 if (isOpen) { // file is already open
56 if (openForWrite) {
57 Close();
58 } else {
59 shouldOpen = 0;
60 } // if/else
61 } // if
62
63 if (shouldOpen) {
64 fd = open(realFilename.c_str(), O_RDONLY);
65 if (fd == -1) {
srs569455d92612010-03-07 22:16:07 -050066 cerr << "Problem opening " << realFilename << " for reading! Error is " << errno << ".\n";
67 if (errno == EACCES) // User is probably not running as root
srs5694fed16d02010-01-27 23:03:40 -050068 cerr << "You must run this program as root or use sudo!\n";
srs569455d92612010-03-07 22:16:07 -050069 if (errno == ENOENT)
70 cerr << "The specified file does not exist!\n";
srs5694add79a62010-01-26 15:59:58 -050071 realFilename = "";
72 userFilename = "";
Rod Smith7dfc8962017-07-26 19:45:51 -040073 modelName = "";
srs5694add79a62010-01-26 15:59:58 -050074 isOpen = 0;
75 openForWrite = 0;
76 } else {
srs56948f1b2d62010-05-23 13:07:19 -040077 isOpen = 0;
srs5694add79a62010-01-26 15:59:58 -050078 openForWrite = 0;
srs56948f1b2d62010-05-23 13:07:19 -040079 if (fstat64(fd, &st) == 0) {
80 if (S_ISDIR(st.st_mode))
81 cerr << "The specified path is a directory!\n";
Guillaume Delacourfd118a42014-07-23 01:28:29 +020082#if !(defined(__FreeBSD__) || defined(__FreeBSD_kernel__)) \
Roderick W. Smith42166482015-06-19 10:26:17 -040083 && !defined(__APPLE__)
srs56948f1b2d62010-05-23 13:07:19 -040084 else if (S_ISCHR(st.st_mode))
85 cerr << "The specified path is a character device!\n";
86#endif
87 else if (S_ISFIFO(st.st_mode))
88 cerr << "The specified path is a FIFO!\n";
89 else if (S_ISSOCK(st.st_mode))
90 cerr << "The specified path is a socket!\n";
91 else
92 isOpen = 1;
93 } // if (fstat64()...)
Rod Smith7dfc8962017-07-26 19:45:51 -040094#if defined(__linux__) && !defined(EFI)
95 if (isOpen && realFilename.substr(0,4) == "/dev") {
96 ostringstream modelNameFilename;
97 modelNameFilename << "/sys/block" << realFilename.substr(4,512) << "/device/model";
98 ifstream modelNameFile(modelNameFilename.str().c_str());
99 if (modelNameFile.is_open()) {
100 getline(modelNameFile, modelName);
101 } // if
102 } // if
103#endif
srs5694add79a62010-01-26 15:59:58 -0500104 } // if/else
105 } // if
106
107 return isOpen;
108} // DiskIO::OpenForRead(void)
109
110// An extended file-open function. This includes some system-specific checks.
111// Returns 1 if the file is open, 0 otherwise....
112int DiskIO::OpenForWrite(void) {
113 if ((isOpen) && (openForWrite))
114 return 1;
115
116 // Close the disk, in case it's already open for reading only....
117 Close();
118
119 // try to open the device; may fail....
120 fd = open(realFilename.c_str(), O_WRONLY | O_CREAT, S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH);
121#ifdef __APPLE__
122 // MacOS X requires a shared lock under some circumstances....
123 if (fd < 0) {
Roderick W. Smithe09ef882013-07-08 22:56:00 -0400124 cerr << "Warning: Devices opened with shared lock will not have their\npartition table automatically reloaded!\n";
srs5694fed16d02010-01-27 23:03:40 -0500125 fd = open(realFilename.c_str(), O_WRONLY | O_SHLOCK);
srs5694add79a62010-01-26 15:59:58 -0500126 } // if
127#endif
128 if (fd >= 0) {
129 isOpen = 1;
130 openForWrite = 1;
131 } else {
132 isOpen = 0;
133 openForWrite = 0;
134 } // if/else
135 return isOpen;
136} // DiskIO::OpenForWrite(void)
137
138// Close the disk device. Note that this does NOT erase the stored filenames,
139// so the file can be re-opened without specifying the filename.
140void DiskIO::Close(void) {
141 if (isOpen)
srs569408bb0da2010-02-19 17:19:55 -0500142 if (close(fd) < 0)
143 cerr << "Warning! Problem closing file!\n";
srs5694add79a62010-01-26 15:59:58 -0500144 isOpen = 0;
145 openForWrite = 0;
146} // DiskIO::Close()
147
148// Returns block size of device pointed to by fd file descriptor. If the ioctl
149// returns an error condition, print a warning but return a value of SECTOR_SIZE
srs569455d92612010-03-07 22:16:07 -0500150// (512). If the disk can't be opened at all, return a value of 0.
srs5694add79a62010-01-26 15:59:58 -0500151int DiskIO::GetBlockSize(void) {
152 int err = -1, blockSize = 0;
srs56940741fa22013-01-09 12:55:40 -0500153#ifdef __sun__
154 struct dk_minfo minfo;
155#endif
srs5694add79a62010-01-26 15:59:58 -0500156
157 // If disk isn't open, try to open it....
158 if (!isOpen) {
159 OpenForRead();
160 } // if
161
162 if (isOpen) {
163#ifdef __APPLE__
164 err = ioctl(fd, DKIOCGETBLOCKSIZE, &blockSize);
165#endif
srs56940741fa22013-01-09 12:55:40 -0500166#ifdef __sun__
167 err = ioctl(fd, DKIOCGMEDIAINFO, &minfo);
168 if (err == 0)
169 blockSize = minfo.dki_lbsize;
170#endif
srs569408bb0da2010-02-19 17:19:55 -0500171#if defined (__FreeBSD__) || defined (__FreeBSD_kernel__)
srs5694add79a62010-01-26 15:59:58 -0500172 err = ioctl(fd, DIOCGSECTORSIZE, &blockSize);
173#endif
174#ifdef __linux__
175 err = ioctl(fd, BLKSSZGET, &blockSize);
176#endif
177
178 if (err == -1) {
179 blockSize = SECTOR_SIZE;
180 // ENOTTY = inappropriate ioctl; probably being called on a disk image
181 // file, so don't display the warning message....
182 // 32-bit code returns EINVAL, I don't know why. I know I'm treading on
183 // thin ice here, but it should be OK in all but very weird cases....
184 if ((errno != ENOTTY) && (errno != EINVAL)) {
srs5694fed16d02010-01-27 23:03:40 -0500185 cerr << "\aError " << errno << " when determining sector size! Setting sector size to "
186 << SECTOR_SIZE << "\n";
srs569455d92612010-03-07 22:16:07 -0500187 cout << "Disk device is " << realFilename << "\n";
srs5694add79a62010-01-26 15:59:58 -0500188 } // if
189 } // if (err == -1)
190 } // if (isOpen)
191
192 return (blockSize);
193} // DiskIO::GetBlockSize()
194
Rod Smithfc0e0142017-07-25 21:33:18 -0400195// Returns the physical block size of the device, if possible. If this is
196// not supported, or if an error occurs, this function returns 0.
197// TODO: Get this working in more OSes than Linux.
198int DiskIO::GetPhysBlockSize(void) {
199 int err = -1, physBlockSize = 0;
200
201 // If disk isn't open, try to open it....
202 if (!isOpen) {
203 OpenForRead();
204 } // if
205
206 if (isOpen) {
207#if defined __linux__ && !defined(EFI)
208 err = ioctl(fd, BLKPBSZGET, &physBlockSize);
209#endif
210 } // if (isOpen)
211 if (err == -1)
212 physBlockSize = 0;
213 return (physBlockSize);
214} // DiskIO::GetPhysBlockSize(void)
215
srs5694bf8950c2011-03-12 01:23:12 -0500216// Returns the number of heads, according to the kernel, or 255 if the
217// correct value can't be determined.
218uint32_t DiskIO::GetNumHeads(void) {
219 uint32_t numHeads = 255;
220
221#ifdef HDIO_GETGEO
222 struct hd_geometry geometry;
223
224 // If disk isn't open, try to open it....
225 if (!isOpen)
226 OpenForRead();
227
228 if (!ioctl(fd, HDIO_GETGEO, &geometry))
229 numHeads = (uint32_t) geometry.heads;
230#endif
231 return numHeads;
232} // DiskIO::GetNumHeads();
233
234// Returns the number of sectors per track, according to the kernel, or 63
235// if the correct value can't be determined.
236uint32_t DiskIO::GetNumSecsPerTrack(void) {
237 uint32_t numSecs = 63;
238
239 #ifdef HDIO_GETGEO
240 struct hd_geometry geometry;
241
242 // If disk isn't open, try to open it....
243 if (!isOpen)
244 OpenForRead();
245
246 if (!ioctl(fd, HDIO_GETGEO, &geometry))
247 numSecs = (uint32_t) geometry.sectors;
248 #endif
249 return numSecs;
250} // DiskIO::GetNumSecsPerTrack()
251
srs5694add79a62010-01-26 15:59:58 -0500252// Resync disk caches so the OS uses the new partition table. This code varies
253// a lot from one OS to another.
srs5694a17fe692011-09-10 20:30:20 -0400254// Returns 1 on success, 0 if the kernel continues to use the old partition table.
255// (Note that for most OSes, the default of 0 is returned because I've not yet
256// looked into how to test for success in the underlying system calls...)
257int DiskIO::DiskSync(void) {
258 int i, retval = 0, platformFound = 0;
srs5694add79a62010-01-26 15:59:58 -0500259
260 // If disk isn't open, try to open it....
261 if (!isOpen) {
262 OpenForRead();
263 } // if
264
265 if (isOpen) {
266 sync();
srs56940741fa22013-01-09 12:55:40 -0500267#if defined(__APPLE__) || defined(__sun__)
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 /* don't know if this helps
271 * it definitely will get things on disk though:
272 * http://topiks.org/mac-os-x/0321278542/ch12lev1sec8.html */
srs56940741fa22013-01-09 12:55:40 -0500273#ifdef __sun__
274 i = ioctl(fd, DKIOCFLUSHWRITECACHE);
275#else
srs5694add79a62010-01-26 15:59:58 -0500276 i = ioctl(fd, DKIOCSYNCHRONIZECACHE);
srs56940741fa22013-01-09 12:55:40 -0500277#endif
srs5694add79a62010-01-26 15:59:58 -0500278 platformFound++;
279#endif
srs569408bb0da2010-02-19 17:19:55 -0500280#if defined (__FreeBSD__) || defined (__FreeBSD_kernel__)
srs5694add79a62010-01-26 15:59:58 -0500281 sleep(2);
282 i = ioctl(fd, DIOCGFLUSH);
srs5694fed16d02010-01-27 23:03:40 -0500283 cout << "Warning: The kernel may continue to use old or deleted partitions.\n"
284 << "You should reboot or remove the drive.\n";
srs5694add79a62010-01-26 15:59:58 -0500285 platformFound++;
286#endif
287#ifdef __linux__
srs569400b6d7a2011-06-26 22:40:06 -0400288 sleep(1); // Theoretically unnecessary, but ioctl() fails sometimes if omitted....
289 fsync(fd);
srs5694add79a62010-01-26 15:59:58 -0500290 i = ioctl(fd, BLKRRPART);
srs5694a17fe692011-09-10 20:30:20 -0400291 if (i) {
srs5694fed16d02010-01-27 23:03:40 -0500292 cout << "Warning: The kernel is still using the old partition table.\n"
Roderick W. Smith54f8fb12015-03-17 19:46:05 -0400293 << "The new table will be used at the next reboot or after you\n"
294 << "run partprobe(8) or kpartx(8)\n";
srs5694a17fe692011-09-10 20:30:20 -0400295 } else {
296 retval = 1;
297 } // if/else
srs5694add79a62010-01-26 15:59:58 -0500298 platformFound++;
299#endif
300 if (platformFound == 0)
srs5694fed16d02010-01-27 23:03:40 -0500301 cerr << "Warning: Platform not recognized!\n";
srs5694add79a62010-01-26 15:59:58 -0500302 if (platformFound > 1)
srs5694fed16d02010-01-27 23:03:40 -0500303 cerr << "\nWarning: We seem to be running on multiple platforms!\n";
srs5694add79a62010-01-26 15:59:58 -0500304 } // if (isOpen)
srs5694a17fe692011-09-10 20:30:20 -0400305 return retval;
srs5694add79a62010-01-26 15:59:58 -0500306} // DiskIO::DiskSync()
307
308// Seek to the specified sector. Returns 1 on success, 0 on failure.
srs5694cb76c672010-02-11 22:22:22 -0500309// Note that seeking beyond the end of the file is NOT detected as a failure!
srs5694add79a62010-01-26 15:59:58 -0500310int DiskIO::Seek(uint64_t sector) {
311 int retval = 1;
Aurimas Liutikasbdbab022017-03-07 09:50:36 -0800312 off64_t seekTo, sought;
srs5694add79a62010-01-26 15:59:58 -0500313
314 // If disk isn't open, try to open it....
315 if (!isOpen) {
316 retval = OpenForRead();
317 } // if
318
319 if (isOpen) {
320 seekTo = sector * (uint64_t) GetBlockSize();
321 sought = lseek64(fd, seekTo, SEEK_SET);
322 if (sought != seekTo) {
323 retval = 0;
324 } // if
325 } // if
326 return retval;
327} // DiskIO::Seek()
328
329// A variant on the standard read() function. Done to work around
330// limitations in FreeBSD concerning the matching of the sector
331// size with the number of bytes read.
332// Returns the number of bytes read into buffer.
333int DiskIO::Read(void* buffer, int numBytes) {
srs5694cb76c672010-02-11 22:22:22 -0500334 int blockSize, numBlocks, retval = 0;
srs5694add79a62010-01-26 15:59:58 -0500335 char* tempSpace;
336
337 // If disk isn't open, try to open it....
338 if (!isOpen) {
339 OpenForRead();
340 } // if
341
342 if (isOpen) {
343 // Compute required space and allocate memory
344 blockSize = GetBlockSize();
345 if (numBytes <= blockSize) {
346 numBlocks = 1;
srs5694cb76c672010-02-11 22:22:22 -0500347 tempSpace = new char [blockSize];
srs5694add79a62010-01-26 15:59:58 -0500348 } else {
349 numBlocks = numBytes / blockSize;
srs5694cb76c672010-02-11 22:22:22 -0500350 if ((numBytes % blockSize) != 0)
351 numBlocks++;
352 tempSpace = new char [numBlocks * blockSize];
srs5694add79a62010-01-26 15:59:58 -0500353 } // if/else
srs56946aae2a92011-06-10 01:16:51 -0400354 if (tempSpace == NULL) {
355 cerr << "Unable to allocate memory in DiskIO::Read()! Terminating!\n";
356 exit(1);
357 } // if
srs5694add79a62010-01-26 15:59:58 -0500358
359 // Read the data into temporary space, then copy it to buffer
360 retval = read(fd, tempSpace, numBlocks * blockSize);
361 memcpy(buffer, tempSpace, numBytes);
srs5694add79a62010-01-26 15:59:58 -0500362
363 // Adjust the return value, if necessary....
364 if (((numBlocks * blockSize) != numBytes) && (retval > 0))
365 retval = numBytes;
366
srs5694cb76c672010-02-11 22:22:22 -0500367 delete[] tempSpace;
srs5694add79a62010-01-26 15:59:58 -0500368 } // if (isOpen)
369 return retval;
370} // DiskIO::Read()
371
372// A variant on the standard write() function. Done to work around
373// limitations in FreeBSD concerning the matching of the sector
374// size with the number of bytes read.
375// Returns the number of bytes written.
376int DiskIO::Write(void* buffer, int numBytes) {
377 int blockSize = 512, i, numBlocks, retval = 0;
378 char* tempSpace;
379
380 // If disk isn't open, try to open it....
381 if ((!isOpen) || (!openForWrite)) {
382 OpenForWrite();
383 } // if
384
385 if (isOpen) {
386 // Compute required space and allocate memory
387 blockSize = GetBlockSize();
388 if (numBytes <= blockSize) {
389 numBlocks = 1;
srs5694cb76c672010-02-11 22:22:22 -0500390 tempSpace = new char [blockSize];
srs5694add79a62010-01-26 15:59:58 -0500391 } else {
392 numBlocks = numBytes / blockSize;
393 if ((numBytes % blockSize) != 0) numBlocks++;
srs5694cb76c672010-02-11 22:22:22 -0500394 tempSpace = new char [numBlocks * blockSize];
srs5694add79a62010-01-26 15:59:58 -0500395 } // if/else
srs56946aae2a92011-06-10 01:16:51 -0400396 if (tempSpace == NULL) {
397 cerr << "Unable to allocate memory in DiskIO::Write()! Terminating!\n";
398 exit(1);
399 } // if
400
srs5694add79a62010-01-26 15:59:58 -0500401 // Copy the data to my own buffer, then write it
srs5694add79a62010-01-26 15:59:58 -0500402 memcpy(tempSpace, buffer, numBytes);
403 for (i = numBytes; i < numBlocks * blockSize; i++) {
404 tempSpace[i] = 0;
405 } // for
406 retval = write(fd, tempSpace, numBlocks * blockSize);
407
408 // Adjust the return value, if necessary....
409 if (((numBlocks * blockSize) != numBytes) && (retval > 0))
410 retval = numBytes;
411
srs5694cb76c672010-02-11 22:22:22 -0500412 delete[] tempSpace;
srs5694add79a62010-01-26 15:59:58 -0500413 } // if (isOpen)
414 return retval;
415} // DiskIO:Write()
416
417/**************************************************************************************
418 * *
419 * Below functions are lifted from various sources, as documented in comments before *
420 * each one. *
421 * *
422 **************************************************************************************/
423
424// The disksize function is taken from the Linux fdisk code and modified
425// greatly since then to enable FreeBSD and MacOS support, as well as to
426// return correct values for disk image files.
427uint64_t DiskIO::DiskSize(int *err) {
srs5694add79a62010-01-26 15:59:58 -0500428 uint64_t sectors = 0; // size in sectors
429 off_t bytes = 0; // size in bytes
430 struct stat64 st;
431 int platformFound = 0;
srs56940741fa22013-01-09 12:55:40 -0500432#ifdef __sun__
433 struct dk_minfo minfo;
434#endif
srs5694add79a62010-01-26 15:59:58 -0500435
436 // If disk isn't open, try to open it....
437 if (!isOpen) {
438 OpenForRead();
439 } // if
440
441 if (isOpen) {
442 // Note to self: I recall testing a simplified version of
443 // this code, similar to what's in the __APPLE__ block,
444 // on Linux, but I had some problems. IIRC, it ran OK on 32-bit
445 // systems but not on 64-bit. Keep this in mind in case of
446 // 32/64-bit issues on MacOS....
447#ifdef __APPLE__
448 *err = ioctl(fd, DKIOCGETBLOCKCOUNT, &sectors);
449 platformFound++;
450#endif
srs56940741fa22013-01-09 12:55:40 -0500451#ifdef __sun__
452 *err = ioctl(fd, DKIOCGMEDIAINFO, &minfo);
453 if (*err == 0)
454 sectors = minfo.dki_capacity;
455 platformFound++;
456#endif
srs569408bb0da2010-02-19 17:19:55 -0500457#if defined (__FreeBSD__) || defined (__FreeBSD_kernel__)
srs5694add79a62010-01-26 15:59:58 -0500458 *err = ioctl(fd, DIOCGMEDIASIZE, &bytes);
srs569408bb0da2010-02-19 17:19:55 -0500459 long long b = GetBlockSize();
srs5694add79a62010-01-26 15:59:58 -0500460 sectors = bytes / b;
461 platformFound++;
462#endif
463#ifdef __linux__
srs569408bb0da2010-02-19 17:19:55 -0500464 long sz;
465 long long b;
srs5694add79a62010-01-26 15:59:58 -0500466 *err = ioctl(fd, BLKGETSIZE, &sz);
467 if (*err) {
468 sectors = sz = 0;
469 } // if
srs569464cbd172011-03-01 22:03:54 -0500470 if ((!*err) || (errno == EFBIG)) {
srs5694add79a62010-01-26 15:59:58 -0500471 *err = ioctl(fd, BLKGETSIZE64, &b);
472 if (*err || b == 0 || b == sz)
473 sectors = sz;
474 else
475 sectors = (b >> 9);
476 } // if
477 // Unintuitively, the above returns values in 512-byte blocks, no
478 // matter what the underlying device's block size. Correct for this....
479 sectors /= (GetBlockSize() / 512);
480 platformFound++;
481#endif
482 if (platformFound != 1)
srs5694fed16d02010-01-27 23:03:40 -0500483 cerr << "Warning! We seem to be running on no known platform!\n";
srs5694add79a62010-01-26 15:59:58 -0500484
485 // The above methods have failed, so let's assume it's a regular
486 // file (a QEMU image, dd backup, or what have you) and see what
487 // fstat() gives us....
488 if ((sectors == 0) || (*err == -1)) {
489 if (fstat64(fd, &st) == 0) {
srs56949a46b042011-03-15 00:34:10 -0400490 bytes = st.st_size;
srs5694add79a62010-01-26 15:59:58 -0500491 if ((bytes % UINT64_C(512)) != 0)
srs5694fed16d02010-01-27 23:03:40 -0500492 cerr << "Warning: File size is not a multiple of 512 bytes!"
493 << " Misbehavior is likely!\n\a";
srs5694add79a62010-01-26 15:59:58 -0500494 sectors = bytes / UINT64_C(512);
495 } // if
496 } // if
497 } // if (isOpen)
498 return sectors;
499} // DiskIO::DiskSize()