1 // 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 (at) 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> 19 #include <string.h> 20 #include <string> 21 #include <stdint.h> 22 #include <unistd.h> 23 #include <errno.h> 24 #include <fcntl.h> 25 #include <sys/stat.h> 26 #include <unistd.h> 27 28 #ifdef __linux__ 29 #include "linux/hdreg.h" 30 #endif 31 32 #include <iostream> 33 34 #include "diskio.h" 35 36 using namespace std; 37 38 // Returns the official "real" name for a shortened version of same. 39 // Trivial here; more important in Windows 40 void DiskIO::MakeRealName(void) { 41 realFilename = userFilename; 42 } // DiskIO::MakeRealName() 43 44 // 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. 47 int DiskIO::OpenForRead(void) { 48 int shouldOpen = 1; 49 struct stat64 st; 50 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) { 62 cerr << "Problem opening " << realFilename << " for reading! Error is " << errno << ".\n"; 63 if (errno == EACCES) // User is probably not running as root 64 cerr << "You must run this program as root or use sudo!\n"; 65 if (errno == ENOENT) 66 cerr << "The specified file does not exist!\n"; 67 realFilename = ""; 68 userFilename = ""; 69 isOpen = 0; 70 openForWrite = 0; 71 } else { 72 isOpen = 0; 73 openForWrite = 0; 74 if (fstat64(fd, &st) == 0) { 75 if (S_ISDIR(st.st_mode)) 76 cerr << "The specified path is a directory!\n"; 77 #if !defined(__FreeBSD__) && !defined(__APPLE__) 78 else if (S_ISCHR(st.st_mode)) 79 cerr << "The specified path is a character device!\n"; 80 #endif 81 else if (S_ISFIFO(st.st_mode)) 82 cerr << "The specified path is a FIFO!\n"; 83 else if (S_ISSOCK(st.st_mode)) 84 cerr << "The specified path is a socket!\n"; 85 else 86 isOpen = 1; 87 } // if (fstat64()...) 88 } // if/else 89 } // if 90 91 return isOpen; 92 } // DiskIO::OpenForRead(void) 93 94 // An extended file-open function. This includes some system-specific checks. 95 // Returns 1 if the file is open, 0 otherwise.... 96 int DiskIO::OpenForWrite(void) { 97 if ((isOpen) && (openForWrite)) 98 return 1; 99 100 // Close the disk, in case it's already open for reading only.... 101 Close(); 102 103 // try to open the device; may fail.... 104 fd = open(realFilename.c_str(), O_WRONLY | O_CREAT, S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH); 105 #ifdef __APPLE__ 106 // MacOS X requires a shared lock under some circumstances.... 107 if (fd < 0) { 108 cerr << "Warning: Devices opened with shared lock will not have their\npartition table automatically reloaded!\n"; 109 fd = open(realFilename.c_str(), O_WRONLY | O_SHLOCK); 110 } // if 111 #endif 112 if (fd >= 0) { 113 isOpen = 1; 114 openForWrite = 1; 115 } else { 116 isOpen = 0; 117 openForWrite = 0; 118 } // if/else 119 return isOpen; 120 } // DiskIO::OpenForWrite(void) 121 122 // Close the disk device. Note that this does NOT erase the stored filenames, 123 // so the file can be re-opened without specifying the filename. 124 void DiskIO::Close(void) { 125 if (isOpen) 126 if (close(fd) < 0) 127 cerr << "Warning! Problem closing file!\n"; 128 isOpen = 0; 129 openForWrite = 0; 130 } // DiskIO::Close() 131 132 // Returns block size of device pointed to by fd file descriptor. If the ioctl 133 // returns an error condition, print a warning but return a value of SECTOR_SIZE 134 // (512). If the disk can't be opened at all, return a value of 0. 135 int DiskIO::GetBlockSize(void) { 136 int err = -1, blockSize = 0; 137 #ifdef __sun__ 138 struct dk_minfo minfo; 139 #endif 140 141 // If disk isn't open, try to open it.... 142 if (!isOpen) { 143 OpenForRead(); 144 } // if 145 146 if (isOpen) { 147 #ifdef __APPLE__ 148 err = ioctl(fd, DKIOCGETBLOCKSIZE, &blockSize); 149 #endif 150 #ifdef __sun__ 151 err = ioctl(fd, DKIOCGMEDIAINFO, &minfo); 152 if (err == 0) 153 blockSize = minfo.dki_lbsize; 154 #endif 155 #if defined (__FreeBSD__) || defined (__FreeBSD_kernel__) 156 err = ioctl(fd, DIOCGSECTORSIZE, &blockSize); 157 #endif 158 #ifdef __linux__ 159 err = ioctl(fd, BLKSSZGET, &blockSize); 160 #endif 161 162 if (err == -1) { 163 blockSize = SECTOR_SIZE; 164 // ENOTTY = inappropriate ioctl; probably being called on a disk image 165 // file, so don't display the warning message.... 166 // 32-bit code returns EINVAL, I don't know why. I know I'm treading on 167 // thin ice here, but it should be OK in all but very weird cases.... 168 if ((errno != ENOTTY) && (errno != EINVAL)) { 169 cerr << "\aError " << errno << " when determining sector size! Setting sector size to " 170 << SECTOR_SIZE << "\n"; 171 cout << "Disk device is " << realFilename << "\n"; 172 } // if 173 } // if (err == -1) 174 } // if (isOpen) 175 176 return (blockSize); 177 } // DiskIO::GetBlockSize() 178 179 // Returns the number of heads, according to the kernel, or 255 if the 180 // correct value can't be determined. 181 uint32_t DiskIO::GetNumHeads(void) { 182 uint32_t numHeads = 255; 183 184 #ifdef HDIO_GETGEO 185 struct hd_geometry geometry; 186 187 // If disk isn't open, try to open it.... 188 if (!isOpen) 189 OpenForRead(); 190 191 if (!ioctl(fd, HDIO_GETGEO, &geometry)) 192 numHeads = (uint32_t) geometry.heads; 193 #endif 194 return numHeads; 195 } // DiskIO::GetNumHeads(); 196 197 // Returns the number of sectors per track, according to the kernel, or 63 198 // if the correct value can't be determined. 199 uint32_t DiskIO::GetNumSecsPerTrack(void) { 200 uint32_t numSecs = 63; 201 202 #ifdef HDIO_GETGEO 203 struct hd_geometry geometry; 204 205 // If disk isn't open, try to open it.... 206 if (!isOpen) 207 OpenForRead(); 208 209 if (!ioctl(fd, HDIO_GETGEO, &geometry)) 210 numSecs = (uint32_t) geometry.sectors; 211 #endif 212 return numSecs; 213 } // DiskIO::GetNumSecsPerTrack() 214 215 // Resync disk caches so the OS uses the new partition table. This code varies 216 // a lot from one OS to another. 217 // Returns 1 on success, 0 if the kernel continues to use the old partition table. 218 // (Note that for most OSes, the default of 0 is returned because I've not yet 219 // looked into how to test for success in the underlying system calls...) 220 int DiskIO::DiskSync(void) { 221 int i, retval = 0, platformFound = 0; 222 223 // If disk isn't open, try to open it.... 224 if (!isOpen) { 225 OpenForRead(); 226 } // if 227 228 if (isOpen) { 229 sync(); 230 #if defined(__APPLE__) || defined(__sun__) 231 cout << "Warning: The kernel may continue to use old or deleted partitions.\n" 232 << "You should reboot or remove the drive.\n"; 233 /* don't know if this helps 234 * it definitely will get things on disk though: 235 * http://topiks.org/mac-os-x/0321278542/ch12lev1sec8.html */ 236 #ifdef __sun__ 237 i = ioctl(fd, DKIOCFLUSHWRITECACHE); 238 #else 239 i = ioctl(fd, DKIOCSYNCHRONIZECACHE); 240 #endif 241 platformFound++; 242 #endif 243 #if defined (__FreeBSD__) || defined (__FreeBSD_kernel__) 244 sleep(2); 245 i = ioctl(fd, DIOCGFLUSH); 246 cout << "Warning: The kernel may continue to use old or deleted partitions.\n" 247 << "You should reboot or remove the drive.\n"; 248 platformFound++; 249 #endif 250 #ifdef __linux__ 251 sleep(1); // Theoretically unnecessary, but ioctl() fails sometimes if omitted.... 252 fsync(fd); 253 i = ioctl(fd, BLKRRPART); 254 if (i) { 255 cout << "Warning: The kernel is still using the old partition table.\n" 256 << "The new table will be used at the next reboot.\n"; 257 } else { 258 retval = 1; 259 } // if/else 260 platformFound++; 261 #endif 262 if (platformFound == 0) 263 cerr << "Warning: Platform not recognized!\n"; 264 if (platformFound > 1) 265 cerr << "\nWarning: We seem to be running on multiple platforms!\n"; 266 } // if (isOpen) 267 return retval; 268 } // DiskIO::DiskSync() 269 270 // Seek to the specified sector. Returns 1 on success, 0 on failure. 271 // Note that seeking beyond the end of the file is NOT detected as a failure! 272 int DiskIO::Seek(uint64_t sector) { 273 int retval = 1; 274 off64_t seekTo, sought; 275 276 // If disk isn't open, try to open it.... 277 if (!isOpen) { 278 retval = OpenForRead(); 279 } // if 280 281 if (isOpen) { 282 seekTo = sector * (uint64_t) GetBlockSize(); 283 sought = lseek64(fd, seekTo, SEEK_SET); 284 if (sought != seekTo) { 285 retval = 0; 286 } // if 287 } // if 288 return retval; 289 } // DiskIO::Seek() 290 291 // A variant on the standard read() function. Done to work around 292 // limitations in FreeBSD concerning the matching of the sector 293 // size with the number of bytes read. 294 // Returns the number of bytes read into buffer. 295 int DiskIO::Read(void* buffer, int numBytes) { 296 int blockSize, numBlocks, retval = 0; 297 char* tempSpace; 298 299 // If disk isn't open, try to open it.... 300 if (!isOpen) { 301 OpenForRead(); 302 } // if 303 304 if (isOpen) { 305 // Compute required space and allocate memory 306 blockSize = GetBlockSize(); 307 if (numBytes <= blockSize) { 308 numBlocks = 1; 309 tempSpace = new char [blockSize]; 310 } else { 311 numBlocks = numBytes / blockSize; 312 if ((numBytes % blockSize) != 0) 313 numBlocks++; 314 tempSpace = new char [numBlocks * blockSize]; 315 } // if/else 316 if (tempSpace == NULL) { 317 cerr << "Unable to allocate memory in DiskIO::Read()! Terminating!\n"; 318 exit(1); 319 } // if 320 321 // Read the data into temporary space, then copy it to buffer 322 retval = read(fd, tempSpace, numBlocks * blockSize); 323 memcpy(buffer, tempSpace, numBytes); 324 325 // Adjust the return value, if necessary.... 326 if (((numBlocks * blockSize) != numBytes) && (retval > 0)) 327 retval = numBytes; 328 329 delete[] tempSpace; 330 } // if (isOpen) 331 return retval; 332 } // DiskIO::Read() 333 334 // A variant on the standard write() function. Done to work around 335 // limitations in FreeBSD concerning the matching of the sector 336 // size with the number of bytes read. 337 // Returns the number of bytes written. 338 int DiskIO::Write(void* buffer, int numBytes) { 339 int blockSize = 512, i, numBlocks, retval = 0; 340 char* tempSpace; 341 342 // If disk isn't open, try to open it.... 343 if ((!isOpen) || (!openForWrite)) { 344 OpenForWrite(); 345 } // if 346 347 if (isOpen) { 348 // Compute required space and allocate memory 349 blockSize = GetBlockSize(); 350 if (numBytes <= blockSize) { 351 numBlocks = 1; 352 tempSpace = new char [blockSize]; 353 } else { 354 numBlocks = numBytes / blockSize; 355 if ((numBytes % blockSize) != 0) numBlocks++; 356 tempSpace = new char [numBlocks * blockSize]; 357 } // if/else 358 if (tempSpace == NULL) { 359 cerr << "Unable to allocate memory in DiskIO::Write()! Terminating!\n"; 360 exit(1); 361 } // if 362 363 // Copy the data to my own buffer, then write it 364 memcpy(tempSpace, buffer, numBytes); 365 for (i = numBytes; i < numBlocks * blockSize; i++) { 366 tempSpace[i] = 0; 367 } // for 368 retval = write(fd, tempSpace, numBlocks * blockSize); 369 370 // Adjust the return value, if necessary.... 371 if (((numBlocks * blockSize) != numBytes) && (retval > 0)) 372 retval = numBytes; 373 374 delete[] tempSpace; 375 } // if (isOpen) 376 return retval; 377 } // DiskIO:Write() 378 379 /************************************************************************************** 380 * * 381 * Below functions are lifted from various sources, as documented in comments before * 382 * each one. * 383 * * 384 **************************************************************************************/ 385 386 // The disksize function is taken from the Linux fdisk code and modified 387 // greatly since then to enable FreeBSD and MacOS support, as well as to 388 // return correct values for disk image files. 389 uint64_t DiskIO::DiskSize(int *err) { 390 uint64_t sectors = 0; // size in sectors 391 off_t bytes = 0; // size in bytes 392 struct stat64 st; 393 int platformFound = 0; 394 #ifdef __sun__ 395 struct dk_minfo minfo; 396 #endif 397 398 // If disk isn't open, try to open it.... 399 if (!isOpen) { 400 OpenForRead(); 401 } // if 402 403 if (isOpen) { 404 // Note to self: I recall testing a simplified version of 405 // this code, similar to what's in the __APPLE__ block, 406 // on Linux, but I had some problems. IIRC, it ran OK on 32-bit 407 // systems but not on 64-bit. Keep this in mind in case of 408 // 32/64-bit issues on MacOS.... 409 #ifdef __APPLE__ 410 *err = ioctl(fd, DKIOCGETBLOCKCOUNT, §ors); 411 platformFound++; 412 #endif 413 #ifdef __sun__ 414 *err = ioctl(fd, DKIOCGMEDIAINFO, &minfo); 415 if (*err == 0) 416 sectors = minfo.dki_capacity; 417 platformFound++; 418 #endif 419 #if defined (__FreeBSD__) || defined (__FreeBSD_kernel__) 420 *err = ioctl(fd, DIOCGMEDIASIZE, &bytes); 421 long long b = GetBlockSize(); 422 sectors = bytes / b; 423 platformFound++; 424 #endif 425 #ifdef __linux__ 426 long sz; 427 long long b; 428 *err = ioctl(fd, BLKGETSIZE, &sz); 429 if (*err) { 430 sectors = sz = 0; 431 } // if 432 if ((!*err) || (errno == EFBIG)) { 433 *err = ioctl(fd, BLKGETSIZE64, &b); 434 if (*err || b == 0 || b == sz) 435 sectors = sz; 436 else 437 sectors = (b >> 9); 438 } // if 439 // Unintuitively, the above returns values in 512-byte blocks, no 440 // matter what the underlying device's block size. Correct for this.... 441 sectors /= (GetBlockSize() / 512); 442 platformFound++; 443 #endif 444 if (platformFound != 1) 445 cerr << "Warning! We seem to be running on no known platform!\n"; 446 447 // The above methods have failed, so let's assume it's a regular 448 // file (a QEMU image, dd backup, or what have you) and see what 449 // fstat() gives us.... 450 if ((sectors == 0) || (*err == -1)) { 451 if (fstat64(fd, &st) == 0) { 452 bytes = st.st_size; 453 if ((bytes % UINT64_C(512)) != 0) 454 cerr << "Warning: File size is not a multiple of 512 bytes!" 455 << " Misbehavior is likely!\n\a"; 456 sectors = bytes / UINT64_C(512); 457 } // if 458 } // if 459 } // if (isOpen) 460 return sectors; 461 } // DiskIO::DiskSize() 462