blob: 75fad2e01e75ae1ee4bbdc093394499137c63a03 [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>
35
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 = "";
71 isOpen = 0;
72 openForWrite = 0;
73 } else {
srs56948f1b2d62010-05-23 13:07:19 -040074 isOpen = 0;
srs5694add79a62010-01-26 15:59:58 -050075 openForWrite = 0;
srs56948f1b2d62010-05-23 13:07:19 -040076 if (fstat64(fd, &st) == 0) {
77 if (S_ISDIR(st.st_mode))
78 cerr << "The specified path is a directory!\n";
Aurimas Liutikas74b74902016-05-10 18:53:54 -070079#if !(defined(__FreeBSD__) || defined(__FreeBSD_kernel__)) \
80 && !defined(__APPLE__)
srs56948f1b2d62010-05-23 13:07:19 -040081 else if (S_ISCHR(st.st_mode))
82 cerr << "The specified path is a character device!\n";
83#endif
84 else if (S_ISFIFO(st.st_mode))
85 cerr << "The specified path is a FIFO!\n";
86 else if (S_ISSOCK(st.st_mode))
87 cerr << "The specified path is a socket!\n";
88 else
89 isOpen = 1;
90 } // if (fstat64()...)
srs5694add79a62010-01-26 15:59:58 -050091 } // if/else
92 } // if
93
94 return isOpen;
95} // DiskIO::OpenForRead(void)
96
97// An extended file-open function. This includes some system-specific checks.
98// Returns 1 if the file is open, 0 otherwise....
99int DiskIO::OpenForWrite(void) {
100 if ((isOpen) && (openForWrite))
101 return 1;
102
103 // Close the disk, in case it's already open for reading only....
104 Close();
105
106 // try to open the device; may fail....
107 fd = open(realFilename.c_str(), O_WRONLY | O_CREAT, S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH);
108#ifdef __APPLE__
109 // MacOS X requires a shared lock under some circumstances....
110 if (fd < 0) {
Roderick W. Smithe09ef882013-07-08 22:56:00 -0400111 cerr << "Warning: Devices opened with shared lock will not have their\npartition table automatically reloaded!\n";
srs5694fed16d02010-01-27 23:03:40 -0500112 fd = open(realFilename.c_str(), O_WRONLY | O_SHLOCK);
srs5694add79a62010-01-26 15:59:58 -0500113 } // if
114#endif
115 if (fd >= 0) {
116 isOpen = 1;
117 openForWrite = 1;
118 } else {
119 isOpen = 0;
120 openForWrite = 0;
121 } // if/else
122 return isOpen;
123} // DiskIO::OpenForWrite(void)
124
125// Close the disk device. Note that this does NOT erase the stored filenames,
126// so the file can be re-opened without specifying the filename.
127void DiskIO::Close(void) {
128 if (isOpen)
srs569408bb0da2010-02-19 17:19:55 -0500129 if (close(fd) < 0)
130 cerr << "Warning! Problem closing file!\n";
srs5694add79a62010-01-26 15:59:58 -0500131 isOpen = 0;
132 openForWrite = 0;
133} // DiskIO::Close()
134
135// Returns block size of device pointed to by fd file descriptor. If the ioctl
136// returns an error condition, print a warning but return a value of SECTOR_SIZE
srs569455d92612010-03-07 22:16:07 -0500137// (512). If the disk can't be opened at all, return a value of 0.
srs5694add79a62010-01-26 15:59:58 -0500138int DiskIO::GetBlockSize(void) {
139 int err = -1, blockSize = 0;
srs56940741fa22013-01-09 12:55:40 -0500140#ifdef __sun__
141 struct dk_minfo minfo;
142#endif
srs5694add79a62010-01-26 15:59:58 -0500143
144 // If disk isn't open, try to open it....
145 if (!isOpen) {
146 OpenForRead();
147 } // if
148
149 if (isOpen) {
150#ifdef __APPLE__
151 err = ioctl(fd, DKIOCGETBLOCKSIZE, &blockSize);
152#endif
srs56940741fa22013-01-09 12:55:40 -0500153#ifdef __sun__
154 err = ioctl(fd, DKIOCGMEDIAINFO, &minfo);
155 if (err == 0)
156 blockSize = minfo.dki_lbsize;
157#endif
srs569408bb0da2010-02-19 17:19:55 -0500158#if defined (__FreeBSD__) || defined (__FreeBSD_kernel__)
srs5694add79a62010-01-26 15:59:58 -0500159 err = ioctl(fd, DIOCGSECTORSIZE, &blockSize);
160#endif
161#ifdef __linux__
162 err = ioctl(fd, BLKSSZGET, &blockSize);
163#endif
164
165 if (err == -1) {
166 blockSize = SECTOR_SIZE;
167 // ENOTTY = inappropriate ioctl; probably being called on a disk image
168 // file, so don't display the warning message....
169 // 32-bit code returns EINVAL, I don't know why. I know I'm treading on
170 // thin ice here, but it should be OK in all but very weird cases....
171 if ((errno != ENOTTY) && (errno != EINVAL)) {
srs5694fed16d02010-01-27 23:03:40 -0500172 cerr << "\aError " << errno << " when determining sector size! Setting sector size to "
173 << SECTOR_SIZE << "\n";
srs569455d92612010-03-07 22:16:07 -0500174 cout << "Disk device is " << realFilename << "\n";
srs5694add79a62010-01-26 15:59:58 -0500175 } // if
176 } // if (err == -1)
177 } // if (isOpen)
178
179 return (blockSize);
180} // DiskIO::GetBlockSize()
181
srs5694bf8950c2011-03-12 01:23:12 -0500182// Returns the number of heads, according to the kernel, or 255 if the
183// correct value can't be determined.
184uint32_t DiskIO::GetNumHeads(void) {
185 uint32_t numHeads = 255;
186
187#ifdef HDIO_GETGEO
188 struct hd_geometry geometry;
189
190 // If disk isn't open, try to open it....
191 if (!isOpen)
192 OpenForRead();
193
194 if (!ioctl(fd, HDIO_GETGEO, &geometry))
195 numHeads = (uint32_t) geometry.heads;
196#endif
197 return numHeads;
198} // DiskIO::GetNumHeads();
199
200// Returns the number of sectors per track, according to the kernel, or 63
201// if the correct value can't be determined.
202uint32_t DiskIO::GetNumSecsPerTrack(void) {
203 uint32_t numSecs = 63;
204
205 #ifdef HDIO_GETGEO
206 struct hd_geometry geometry;
207
208 // If disk isn't open, try to open it....
209 if (!isOpen)
210 OpenForRead();
211
212 if (!ioctl(fd, HDIO_GETGEO, &geometry))
213 numSecs = (uint32_t) geometry.sectors;
214 #endif
215 return numSecs;
216} // DiskIO::GetNumSecsPerTrack()
217
srs5694add79a62010-01-26 15:59:58 -0500218// Resync disk caches so the OS uses the new partition table. This code varies
219// a lot from one OS to another.
srs5694a17fe692011-09-10 20:30:20 -0400220// Returns 1 on success, 0 if the kernel continues to use the old partition table.
221// (Note that for most OSes, the default of 0 is returned because I've not yet
222// looked into how to test for success in the underlying system calls...)
223int DiskIO::DiskSync(void) {
224 int i, retval = 0, platformFound = 0;
srs5694add79a62010-01-26 15:59:58 -0500225
226 // If disk isn't open, try to open it....
227 if (!isOpen) {
228 OpenForRead();
229 } // if
230
231 if (isOpen) {
232 sync();
srs56940741fa22013-01-09 12:55:40 -0500233#if defined(__APPLE__) || defined(__sun__)
srs5694fed16d02010-01-27 23:03:40 -0500234 cout << "Warning: The kernel may continue to use old or deleted partitions.\n"
235 << "You should reboot or remove the drive.\n";
srs5694add79a62010-01-26 15:59:58 -0500236 /* don't know if this helps
237 * it definitely will get things on disk though:
238 * http://topiks.org/mac-os-x/0321278542/ch12lev1sec8.html */
srs56940741fa22013-01-09 12:55:40 -0500239#ifdef __sun__
240 i = ioctl(fd, DKIOCFLUSHWRITECACHE);
241#else
srs5694add79a62010-01-26 15:59:58 -0500242 i = ioctl(fd, DKIOCSYNCHRONIZECACHE);
srs56940741fa22013-01-09 12:55:40 -0500243#endif
srs5694add79a62010-01-26 15:59:58 -0500244 platformFound++;
245#endif
srs569408bb0da2010-02-19 17:19:55 -0500246#if defined (__FreeBSD__) || defined (__FreeBSD_kernel__)
srs5694add79a62010-01-26 15:59:58 -0500247 sleep(2);
248 i = ioctl(fd, DIOCGFLUSH);
srs5694fed16d02010-01-27 23:03:40 -0500249 cout << "Warning: The kernel may continue to use old or deleted partitions.\n"
250 << "You should reboot or remove the drive.\n";
srs5694add79a62010-01-26 15:59:58 -0500251 platformFound++;
252#endif
253#ifdef __linux__
srs569400b6d7a2011-06-26 22:40:06 -0400254 sleep(1); // Theoretically unnecessary, but ioctl() fails sometimes if omitted....
255 fsync(fd);
srs5694add79a62010-01-26 15:59:58 -0500256 i = ioctl(fd, BLKRRPART);
srs5694a17fe692011-09-10 20:30:20 -0400257 if (i) {
srs5694fed16d02010-01-27 23:03:40 -0500258 cout << "Warning: The kernel is still using the old partition table.\n"
Aurimas Liutikas74b74902016-05-10 18:53:54 -0700259 << "The new table will be used at the next reboot or after you\n"
260 << "run partprobe(8) or kpartx(8)\n";
srs5694a17fe692011-09-10 20:30:20 -0400261 } else {
262 retval = 1;
263 } // if/else
srs5694add79a62010-01-26 15:59:58 -0500264 platformFound++;
265#endif
266 if (platformFound == 0)
srs5694fed16d02010-01-27 23:03:40 -0500267 cerr << "Warning: Platform not recognized!\n";
srs5694add79a62010-01-26 15:59:58 -0500268 if (platformFound > 1)
srs5694fed16d02010-01-27 23:03:40 -0500269 cerr << "\nWarning: We seem to be running on multiple platforms!\n";
srs5694add79a62010-01-26 15:59:58 -0500270 } // if (isOpen)
srs5694a17fe692011-09-10 20:30:20 -0400271 return retval;
srs5694add79a62010-01-26 15:59:58 -0500272} // DiskIO::DiskSync()
273
274// Seek to the specified sector. Returns 1 on success, 0 on failure.
srs5694cb76c672010-02-11 22:22:22 -0500275// Note that seeking beyond the end of the file is NOT detected as a failure!
srs5694add79a62010-01-26 15:59:58 -0500276int DiskIO::Seek(uint64_t sector) {
277 int retval = 1;
Aurimas Liutikas74b74902016-05-10 18:53:54 -0700278 off_t seekTo, sought;
srs5694add79a62010-01-26 15:59:58 -0500279
280 // If disk isn't open, try to open it....
281 if (!isOpen) {
282 retval = OpenForRead();
283 } // if
284
285 if (isOpen) {
286 seekTo = sector * (uint64_t) GetBlockSize();
287 sought = lseek64(fd, seekTo, SEEK_SET);
288 if (sought != seekTo) {
289 retval = 0;
290 } // if
291 } // if
292 return retval;
293} // DiskIO::Seek()
294
295// A variant on the standard read() function. Done to work around
296// limitations in FreeBSD concerning the matching of the sector
297// size with the number of bytes read.
298// Returns the number of bytes read into buffer.
299int DiskIO::Read(void* buffer, int numBytes) {
srs5694cb76c672010-02-11 22:22:22 -0500300 int blockSize, numBlocks, retval = 0;
srs5694add79a62010-01-26 15:59:58 -0500301 char* tempSpace;
302
303 // If disk isn't open, try to open it....
304 if (!isOpen) {
305 OpenForRead();
306 } // if
307
308 if (isOpen) {
309 // Compute required space and allocate memory
310 blockSize = GetBlockSize();
311 if (numBytes <= blockSize) {
312 numBlocks = 1;
srs5694cb76c672010-02-11 22:22:22 -0500313 tempSpace = new char [blockSize];
srs5694add79a62010-01-26 15:59:58 -0500314 } else {
315 numBlocks = numBytes / blockSize;
srs5694cb76c672010-02-11 22:22:22 -0500316 if ((numBytes % blockSize) != 0)
317 numBlocks++;
318 tempSpace = new char [numBlocks * blockSize];
srs5694add79a62010-01-26 15:59:58 -0500319 } // if/else
srs56946aae2a92011-06-10 01:16:51 -0400320 if (tempSpace == NULL) {
321 cerr << "Unable to allocate memory in DiskIO::Read()! Terminating!\n";
322 exit(1);
323 } // if
srs5694add79a62010-01-26 15:59:58 -0500324
325 // Read the data into temporary space, then copy it to buffer
326 retval = read(fd, tempSpace, numBlocks * blockSize);
327 memcpy(buffer, tempSpace, numBytes);
srs5694add79a62010-01-26 15:59:58 -0500328
329 // Adjust the return value, if necessary....
330 if (((numBlocks * blockSize) != numBytes) && (retval > 0))
331 retval = numBytes;
332
srs5694cb76c672010-02-11 22:22:22 -0500333 delete[] tempSpace;
srs5694add79a62010-01-26 15:59:58 -0500334 } // if (isOpen)
335 return retval;
336} // DiskIO::Read()
337
338// A variant on the standard write() function. Done to work around
339// limitations in FreeBSD concerning the matching of the sector
340// size with the number of bytes read.
341// Returns the number of bytes written.
342int DiskIO::Write(void* buffer, int numBytes) {
343 int blockSize = 512, i, numBlocks, retval = 0;
344 char* tempSpace;
345
346 // If disk isn't open, try to open it....
347 if ((!isOpen) || (!openForWrite)) {
348 OpenForWrite();
349 } // if
350
351 if (isOpen) {
352 // Compute required space and allocate memory
353 blockSize = GetBlockSize();
354 if (numBytes <= blockSize) {
355 numBlocks = 1;
srs5694cb76c672010-02-11 22:22:22 -0500356 tempSpace = new char [blockSize];
srs5694add79a62010-01-26 15:59:58 -0500357 } else {
358 numBlocks = numBytes / blockSize;
359 if ((numBytes % blockSize) != 0) numBlocks++;
srs5694cb76c672010-02-11 22:22:22 -0500360 tempSpace = new char [numBlocks * blockSize];
srs5694add79a62010-01-26 15:59:58 -0500361 } // if/else
srs56946aae2a92011-06-10 01:16:51 -0400362 if (tempSpace == NULL) {
363 cerr << "Unable to allocate memory in DiskIO::Write()! Terminating!\n";
364 exit(1);
365 } // if
366
srs5694add79a62010-01-26 15:59:58 -0500367 // Copy the data to my own buffer, then write it
srs5694add79a62010-01-26 15:59:58 -0500368 memcpy(tempSpace, buffer, numBytes);
369 for (i = numBytes; i < numBlocks * blockSize; i++) {
370 tempSpace[i] = 0;
371 } // for
372 retval = write(fd, tempSpace, numBlocks * blockSize);
373
374 // Adjust the return value, if necessary....
375 if (((numBlocks * blockSize) != numBytes) && (retval > 0))
376 retval = numBytes;
377
srs5694cb76c672010-02-11 22:22:22 -0500378 delete[] tempSpace;
srs5694add79a62010-01-26 15:59:58 -0500379 } // if (isOpen)
380 return retval;
381} // DiskIO:Write()
382
383/**************************************************************************************
384 * *
385 * Below functions are lifted from various sources, as documented in comments before *
386 * each one. *
387 * *
388 **************************************************************************************/
389
390// The disksize function is taken from the Linux fdisk code and modified
391// greatly since then to enable FreeBSD and MacOS support, as well as to
392// return correct values for disk image files.
393uint64_t DiskIO::DiskSize(int *err) {
srs5694add79a62010-01-26 15:59:58 -0500394 uint64_t sectors = 0; // size in sectors
395 off_t bytes = 0; // size in bytes
396 struct stat64 st;
397 int platformFound = 0;
srs56940741fa22013-01-09 12:55:40 -0500398#ifdef __sun__
399 struct dk_minfo minfo;
400#endif
srs5694add79a62010-01-26 15:59:58 -0500401
402 // If disk isn't open, try to open it....
403 if (!isOpen) {
404 OpenForRead();
405 } // if
406
407 if (isOpen) {
408 // Note to self: I recall testing a simplified version of
409 // this code, similar to what's in the __APPLE__ block,
410 // on Linux, but I had some problems. IIRC, it ran OK on 32-bit
411 // systems but not on 64-bit. Keep this in mind in case of
412 // 32/64-bit issues on MacOS....
413#ifdef __APPLE__
414 *err = ioctl(fd, DKIOCGETBLOCKCOUNT, &sectors);
415 platformFound++;
416#endif
srs56940741fa22013-01-09 12:55:40 -0500417#ifdef __sun__
418 *err = ioctl(fd, DKIOCGMEDIAINFO, &minfo);
419 if (*err == 0)
420 sectors = minfo.dki_capacity;
421 platformFound++;
422#endif
srs569408bb0da2010-02-19 17:19:55 -0500423#if defined (__FreeBSD__) || defined (__FreeBSD_kernel__)
srs5694add79a62010-01-26 15:59:58 -0500424 *err = ioctl(fd, DIOCGMEDIASIZE, &bytes);
srs569408bb0da2010-02-19 17:19:55 -0500425 long long b = GetBlockSize();
srs5694add79a62010-01-26 15:59:58 -0500426 sectors = bytes / b;
427 platformFound++;
428#endif
429#ifdef __linux__
srs569408bb0da2010-02-19 17:19:55 -0500430 long sz;
431 long long b;
srs5694add79a62010-01-26 15:59:58 -0500432 *err = ioctl(fd, BLKGETSIZE, &sz);
433 if (*err) {
434 sectors = sz = 0;
435 } // if
srs569464cbd172011-03-01 22:03:54 -0500436 if ((!*err) || (errno == EFBIG)) {
srs5694add79a62010-01-26 15:59:58 -0500437 *err = ioctl(fd, BLKGETSIZE64, &b);
438 if (*err || b == 0 || b == sz)
439 sectors = sz;
440 else
441 sectors = (b >> 9);
442 } // if
443 // Unintuitively, the above returns values in 512-byte blocks, no
444 // matter what the underlying device's block size. Correct for this....
445 sectors /= (GetBlockSize() / 512);
446 platformFound++;
447#endif
448 if (platformFound != 1)
srs5694fed16d02010-01-27 23:03:40 -0500449 cerr << "Warning! We seem to be running on no known platform!\n";
srs5694add79a62010-01-26 15:59:58 -0500450
451 // The above methods have failed, so let's assume it's a regular
452 // file (a QEMU image, dd backup, or what have you) and see what
453 // fstat() gives us....
454 if ((sectors == 0) || (*err == -1)) {
455 if (fstat64(fd, &st) == 0) {
srs56949a46b042011-03-15 00:34:10 -0400456 bytes = st.st_size;
srs5694add79a62010-01-26 15:59:58 -0500457 if ((bytes % UINT64_C(512)) != 0)
srs5694fed16d02010-01-27 23:03:40 -0500458 cerr << "Warning: File size is not a multiple of 512 bytes!"
459 << " Misbehavior is likely!\n\a";
srs5694add79a62010-01-26 15:59:58 -0500460 sectors = bytes / UINT64_C(512);
461 } // if
462 } // if
463 } // if (isOpen)
464 return sectors;
465} // DiskIO::DiskSize()