/* * boot1-install.c * boot1-install * * Created by Zenith432 on November 19th, 2014. * Copyright (c) 2014 Zenith432. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include struct buffer_t { unsigned char* _b; size_t _s; }; enum volume_kind_t { _undetected = 0, _exfat = 1, _hfs = 2, _msdos = 3, _ntfs = 4, _ext4 = 5, _other = 255 }; static int isVolumeMounted = 0; static int isMediaWhole = 0; static int isMediaLeaf = 0; static enum volume_kind_t daVolumeKind = _undetected; static struct buffer_t bpbBlob = { NULL, 0 }; static struct buffer_t bootBlob = { NULL, 0 }; static struct buffer_t outputBlob = { NULL, 0 }; static char const UnsupportedMessage[] = "Only exFAT, FAT32 or HFS+ volumes are supported\n"; static char const exfatID[] = "EXFAT "; static char const fat32ID[] = "FAT32 "; static char const devrdisk[] = "/dev/rdisk"; static char const devdisk[] = "/dev/disk"; static char const defaultBootFile_exfat[] = "./boot1x"; static char const defaultBootFile_hfs[] = "./boot1h"; static char const defaultBootFile_fat32[] = "./boot1f32"; static __used char const copyright[] = "Copyright 2014 Zenith432"; static int checkExfat(struct buffer_t const*); static int checkFat32(struct buffer_t const*); static int loadChunk(char const*, off_t, off_t, struct buffer_t*); static void unsupported(void); #pragma mark - #pragma mark Cleaners #pragma mark - static void free_buffer(struct buffer_t* pBuffer) { assert(pBuffer); if (pBuffer->_b) { free(pBuffer->_b); pBuffer->_b = NULL; pBuffer->_s = 0; } } /* * Uses statics */ static void cleanup(void) { free_buffer(&outputBlob); free_buffer(&bootBlob); free_buffer(&bpbBlob); } #pragma mark - #pragma mark ExFAT Processor #pragma mark - static unsigned VBRChecksum(unsigned char const* octets, size_t NumberOfBytes) { unsigned Checksum = 0; size_t Index; for (Index = 0; Index != NumberOfBytes; ++Index) { if (Index == 106 || Index == 107 || Index == 112) continue; Checksum = ((Checksum << 31) | (Checksum >> 1)) + (unsigned) octets[Index]; } return Checksum; } static int calcSum(struct buffer_t const* pBootBlob, struct buffer_t const* pBpbBlob, struct buffer_t* pOutputBlob, char const* pathName) { unsigned char *outBuffer, *p, *q; size_t outSize, toCopy, leftOver; unsigned Checksum; assert(pBootBlob && pBpbBlob); if (pBootBlob->_s > 9U * 512U) { fprintf(stderr, "Boot Code must be at most 4608 bytes\n"); return -1; } if (pBpbBlob->_s < 113U) { fprintf(stderr, "BPB must be at least 113 bytes\n"); return -1; } if (!checkExfat(pBpbBlob)) { fprintf(stderr, "BPB does not contain proper exFAT signature\n"); return -1; } outSize = 12U * 512U; outBuffer = malloc(outSize); if (!outBuffer) { fprintf(stderr, "%s: Memory allocation failed\n", __FUNCTION__); return -1; } memset(outBuffer, 0, outSize); memcpy(outBuffer, pBootBlob->_b, pBootBlob->_s); memcpy(&outBuffer[3], &pBpbBlob->_b[3], 8); memset(&outBuffer[11], 0, 53); toCopy = 120; if (pBpbBlob->_s < toCopy) toCopy = pBpbBlob->_s; leftOver = 120 - toCopy; memcpy(&outBuffer[64], &pBpbBlob->_b[64], toCopy - 64); if (leftOver) memset(&outBuffer[120 - leftOver], 0, leftOver); for (toCopy = 0; toCopy != 9; ++toCopy) { p = outBuffer + toCopy * 512U + 508U; p[2] = 0x55U; p[3] = 0xAAU; if (toCopy) { p[0] = 0U; p[1] = 0U; } } if (pathName) { /* * Copy OEM Parameters record */ struct buffer_t auxBlob = { NULL, 0 }; if (loadChunk(pathName, 9 * 512 , 512, &auxBlob) >= 0) { memcpy(&outBuffer[9 * 512], &auxBlob._b[0], 512); free_buffer(&auxBlob); } } Checksum = VBRChecksum(outBuffer, 11U * 512U); p = outBuffer + 11U * 512U; q = p + 512U; for (; p < q; p += 4) { *(unsigned*) p = Checksum; } if (pOutputBlob) { pOutputBlob->_b = outBuffer; pOutputBlob->_s = outSize; } else free(outBuffer); return 0; } #pragma mark - #pragma mark FAT32 Processor #pragma mark - static int fat32Layout(struct buffer_t const* pBootBlob, struct buffer_t const* pBpbBlob, struct buffer_t* pOutputBlob) { unsigned char *outBuffer; size_t outSize; assert(pBootBlob && pBpbBlob); if (pBootBlob->_s > 512U) { fprintf(stderr, "Boot Code must be at most 512 bytes\n"); return -1; } if (pBpbBlob->_s < 90U) { fprintf(stderr, "BPB must be at least 90 bytes\n"); return -1; } if (!checkFat32(pBpbBlob)) { fprintf(stderr, "BPB does not contain proper FAT32 signature\n"); return -1; } outSize = 512U; outBuffer = malloc(outSize); if (!outBuffer) { fprintf(stderr, "%s: Memory allocation failed\n", __FUNCTION__); return -1; } memset(outBuffer, 0, outSize); memcpy(outBuffer, pBootBlob->_b, pBootBlob->_s); memcpy(&outBuffer[3], &pBpbBlob->_b[3], 87); outBuffer[510] = 0x55U; outBuffer[511] = 0xAAU; if (pOutputBlob) { pOutputBlob->_b = outBuffer; pOutputBlob->_s = outSize; } else free(outBuffer); return 0; } #pragma mark - #pragma mark File Operations #pragma mark - static void writeVBR(char const* pathName, struct buffer_t const* pBuffer, int numCopies, size_t expectedSize, char const* volumeType) { int fd, j; assert(pathName && pBuffer && volumeType); if (pBuffer->_s != expectedSize) { fprintf(stderr, "Unexpected %s VBR size %lu (expected %lu)\n", volumeType, pBuffer->_s, expectedSize); return; } fd = open(pathName, O_WRONLY); if (fd < 0) { fprintf(stderr, "Unable to write boot record to %s, %s\n", pathName, strerror(errno)); } for (j = 0; j != numCopies; ++j) write(fd, pBuffer->_b, pBuffer->_s); close(fd); } static int loadChunk(char const* pathName, off_t startOffset, off_t bytesToRead, struct buffer_t* pBuffer) { int fd; ssize_t rc; unsigned char* p; struct stat buf; assert(pathName); fd = open(pathName, O_RDONLY); if (fd < 0) { fprintf(stderr, "Unable to open %s, %s\n", pathName, strerror(errno)); return -1; } if (bytesToRead > 0) buf.st_size = bytesToRead; else if (fstat(fd, &buf) < 0) { fprintf(stderr, "Unable to fstat %s, %s\n", pathName, strerror(errno)); close(fd); return -1; } if (startOffset > 0) { off_t t = lseek(fd, startOffset, SEEK_SET); if (t < 0) { fprintf(stderr, "Unable to lseek %s, %s\n", pathName, strerror(errno)); close(fd); return -1; } if (t != startOffset) { fprintf(stderr, "lseek %s returned wrong value %lld instead of %lld\n", pathName, t, startOffset); close(fd); return -1; } if (bytesToRead <= 0) buf.st_size -= t; } p = malloc((size_t) buf.st_size); if (!p) { fprintf(stderr, "%s: Memory allocation failed\n", __FUNCTION__); close(fd); return -1; } rc = read(fd, p, (size_t) buf.st_size); if (rc < 0) { fprintf(stderr, "Unable to read from %s, %s\n", pathName, strerror(errno)); free(p); close(fd); return -1; } close(fd); if (rc != buf.st_size) { fprintf(stderr, "Unable to read entire chunk from %s, read %ld/%lld\n", pathName, rc, buf.st_size); free(p); return -1; } if (pBuffer) { pBuffer->_b = p; pBuffer->_s = (size_t) rc; } else free(p); return 0; } #pragma mark - #pragma mark DiskArbitration Helpers #pragma mark - static char const* toBSDName(char const* pathName) { assert(pathName); return strncmp(pathName, &devrdisk[0], 10) ? pathName : &pathName[6]; } static char const* daReturnStr(DAReturn v) { if (unix_err(err_get_code(v)) == v) return strerror(err_get_code(v)); switch (v) { case kDAReturnError: return "Error"; case kDAReturnBusy: return "Busy"; case kDAReturnBadArgument: return "Bad Argument"; case kDAReturnExclusiveAccess: return "Exclusive Access"; case kDAReturnNoResources: return "No Resources"; case kDAReturnNotFound: return "Not Found"; case kDAReturnNotMounted: return "Not Mounted"; case kDAReturnNotPermitted: return "Not Permitted"; case kDAReturnNotPrivileged: return "Not Privileged"; case kDAReturnNotReady: return "Not Ready"; case kDAReturnNotWritable: return "Not Writable"; case kDAReturnUnsupported: return "Unsupported"; default: return "Unknown"; } } static int getDASessionAndDisk(char const* pathName, DASessionRef* pSession, DADiskRef* pDisk) { DASessionRef session; DADiskRef disk; assert(pathName); session = DASessionCreate(kCFAllocatorDefault); if (!session) { fprintf(stderr, "DASessionCreate returned NULL\n"); return -1; } disk = DADiskCreateFromBSDName(kCFAllocatorDefault, session, toBSDName(pathName)); if (!disk) { CFRelease(session); fprintf(stderr, "DADiskCreateFromBSDName(%s) returned NULL\n", pathName); return -1; } if (pDisk) *pDisk = disk; else CFRelease(disk); if (pSession) *pSession = session; else CFRelease(session); return 0; } #pragma mark - #pragma mark Mount/UMount #pragma mark - static void umountCallback(DADiskRef disk __unused, DADissenterRef dissenter, void *context) { if (context && dissenter != NULL) { *(int*) context = -1; fprintf(stderr, "umount unsuccessful, status %s\n", daReturnStr(DADissenterGetStatus(dissenter))); } CFRunLoopStop(CFRunLoopGetCurrent()); } static int umount(char const* pathName) { DASessionRef session; DADiskRef disk; int rc; assert(pathName); if (getDASessionAndDisk(pathName, &session, &disk) < 0) return -1; rc = 0; DASessionScheduleWithRunLoop(session, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); DADiskUnmount(disk, kDADiskUnmountOptionDefault, umountCallback, &rc); CFRunLoopRun(); DASessionUnscheduleFromRunLoop(session, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); CFRelease(disk); CFRelease(session); return rc; } static void mountCallback(DADiskRef disk __unused, DADissenterRef dissenter, void *context) { if (context && dissenter != NULL) { *(int*) context = -1; fprintf(stderr, "mount unsuccessful, status %s\n", daReturnStr(DADissenterGetStatus(dissenter))); } CFRunLoopStop(CFRunLoopGetCurrent()); } static int mount(char const* pathName) { DASessionRef session; DADiskRef disk; int rc; assert(pathName); if (getDASessionAndDisk(pathName, &session, &disk) < 0) return -1; rc = 0; DASessionScheduleWithRunLoop(session, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); DADiskMount(disk, NULL, kDADiskMountOptionDefault, mountCallback, &rc); CFRunLoopRun(); DASessionUnscheduleFromRunLoop(session, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); CFRelease(disk); CFRelease(session); return rc; } #pragma mark - #pragma mark Analyze Volume #pragma mark - static int checkExfat(struct buffer_t const* pBpbBlob) { assert(pBpbBlob); return !memcmp(&pBpbBlob->_b[3], &exfatID[0], 8); } static int checkHFS(struct buffer_t const* pBpbBlob) { uint16_t sig; assert(pBpbBlob); sig = OSSwapBigToHostInt16(*(uint16_t const*)&pBpbBlob->_b[0]); return sig == 0x4244 || sig == 0x482B || sig == 0x4858; /* 'BD', 'H+', 'HX' */ } static int checkFat32(struct buffer_t const* pBpbBlob) { uint16_t bytesPerSector, rootEntCnt; uint8_t sectorsPerCluster; assert(pBpbBlob); bytesPerSector = OSSwapLittleToHostInt16(*(uint16_t const*)&pBpbBlob->_b[11]); if ((bytesPerSector & (bytesPerSector - 1U)) || bytesPerSector < 0x200U || bytesPerSector > 0x1000U) return 0; sectorsPerCluster = pBpbBlob->_b[13]; if (!sectorsPerCluster || (sectorsPerCluster & (sectorsPerCluster - 1U))) return 0; rootEntCnt = OSSwapLittleToHostInt16(*(uint16_t const*)&pBpbBlob->_b[17]); if (rootEntCnt) return 0; return !memcmp(&pBpbBlob->_b[82], &fat32ID[0], 8); } static int checkSupportedVolume(enum volume_kind_t* pKind, struct buffer_t const* pBpbBlob, char const* pathName) { int rc; assert(pKind && pBpbBlob); rc = -1; switch (*pKind) { case _undetected: if (checkExfat(pBpbBlob)) { *pKind = _exfat; rc = 0; } else if (checkFat32(pBpbBlob)) { *pKind = _msdos; rc = 0; } else if (pathName) { struct buffer_t auxBlob = { NULL, 0 }; if (loadChunk(pathName, 1024 , 512, &auxBlob) >= 0) { if (checkHFS(&auxBlob)) { *pKind = _hfs; rc = 0; } free_buffer(&auxBlob); } } break; case _exfat: if (checkExfat(pBpbBlob)) rc = 0; else *pKind = _other; break; case _hfs: if (checkHFS(pBpbBlob)) rc = 0; else *pKind = _other; break; case _msdos: if (checkFat32(pBpbBlob)) rc = 0; else *pKind = _other; break; case _ntfs: rc = 0; break; default: break; } if (rc < 0) unsupported(); return rc; } /* * Uses statics */ static int checkDevicePath2(char const* pathName) { DASessionRef session; DADiskRef disk; CFDictionaryRef descDict; CFStringRef s_ref; CFBooleanRef b_ref; assert(pathName); if (getDASessionAndDisk(pathName, &session, &disk) < 0) return -1; descDict = DADiskCopyDescription(disk); if (!descDict) { CFRelease(disk); CFRelease(session); fprintf(stderr, "DADiskCopyDescription(%s) returned NULL\n", pathName); return -1; } if (CFDictionaryGetValueIfPresent(descDict, kDADiskDescriptionMediaWholeKey, (void const**) &b_ref) && CFBooleanGetValue(b_ref)) isMediaWhole = 1; if (CFDictionaryGetValueIfPresent(descDict, kDADiskDescriptionMediaLeafKey, (void const**) &b_ref) && CFBooleanGetValue(b_ref)) isMediaLeaf = 1; if (CFDictionaryContainsKey(descDict, kDADiskDescriptionVolumePathKey)) isVolumeMounted = 1; if (CFDictionaryGetValueIfPresent(descDict, kDADiskDescriptionVolumeKindKey, (void const**) &s_ref)) { static char cstr_buffer[64]; char const* cstr = CFStringGetCStringPtr(s_ref, kCFStringEncodingUTF8); if (!cstr) { CFStringGetCString(s_ref, &cstr_buffer[0], (CFIndex) sizeof cstr_buffer, kCFStringEncodingUTF8); cstr = &cstr_buffer[0]; } #if 0 printf("DAVolumeKind %s\n", cstr); #endif if (!strcmp(cstr, "exfat")) daVolumeKind = _exfat; else if (!strcmp(cstr, "hfs")) daVolumeKind = _hfs; else if (!strcmp(cstr, "msdos")) daVolumeKind = _msdos; else if (!strcmp(cstr, "ntfs")) daVolumeKind = _ntfs; else daVolumeKind = _other; } #if 0 printf(stderr, "whole %c, leaf %c, mounted %c\n", isMediaWhole ? 'Y' : 'N', isMediaLeaf ? 'Y' : 'N', isVolumeMounted ? 'Y' : 'N'); #endif #if 0 CFShow(descDict); #endif CFRelease(descDict); CFRelease(disk); CFRelease(session); return 0; } static int checkDevicePath(char const* pathName) { struct stat buf; assert(pathName); if (strncmp(pathName, &devdisk[0], 9) != 0 && strncmp(pathName, &devrdisk[0], 10) != 0) { fprintf(stderr, "disk must be of form /dev/rdiskUsS or /dev/diskUsS\n"); return -1; } if (stat(pathName, &buf) < 0) { fprintf(stderr, "stat on %s failed, %s\n", pathName, strerror(errno)); return -1; } if (!(buf.st_mode & (S_IFCHR | S_IFBLK))) { fprintf(stderr, "%s is not a block or character special device\n", pathName); return -1; } /* * FIXME: milk information from st_rdev - what's in it? */ #if 0 printf("size of buf is %lu\n", sizeof buf); printf("st_dev %#x\n", buf.st_dev); printf("st_ino %llu\n", buf.st_ino); printf("st_mode %#o\n", buf.st_mode); printf("st_nlink %u\n", buf.st_nlink); printf("st_uid %u\n", buf.st_uid); printf("st_gid %u\n", buf.st_gid); printf("st_rdev %#x\n", buf.st_rdev); printf("st_size %llu\n", buf.st_size); printf("st_blocks %llu\n", buf.st_blocks); printf("st_blksize %u\n", buf.st_blksize); printf("st_flags %#x\n", buf.st_flags); printf("st_gen %u\n", buf.st_gen); #endif return 0; } #pragma mark - #pragma mark Usage #pragma mark - static void usage(char const* self) { assert(self); fprintf(stderr, "Usage: %s [-yM] [-f boot_code_file] disk\n", self); fprintf(stderr, " boot_code_file is an optional boot template\n"); fprintf(stderr, " -y: don't ask any questions\n"); fprintf(stderr, " -M: keep volume mounted while proceeding (useful for root filesystem)\n"); fprintf(stderr, "disk is of the form /dev/rdiskUsS or /dev/diskUsS\n"); fprintf(stderr, "default boot files are\n"); fprintf(stderr, " boot1h for HFS+\n"); fprintf(stderr, " boot1f32 for FAT32\n"); fprintf(stderr, " boot1x for exFAT\n"); } static void unsupported(void) { fprintf(stderr, "%s", &UnsupportedMessage[0]); } #pragma mark - #pragma mark Main #pragma mark - int main(int argc, char* const argv[]) { int ch; char const* bootFile = NULL; char const* devicePath = NULL; int dontAsk = 0; int keepMounted = 0; while ((ch = getopt(argc, argv, "yMf:")) != -1) switch (ch) { case 'y': dontAsk = 1; break; case 'M': keepMounted = 1; break; case 'f': bootFile = optarg; break; default: goto usage_and_error; } if (optind + 1 > argc) goto usage_and_error; devicePath = argv[optind]; if (geteuid() != 0) { fprintf(stderr, "This program must be run as root\n"); return -1; } #if 0 printf("bootFile %s, devicePath %s, dontAsk %d\n", bootFile, devicePath, dontAsk); #endif if (checkDevicePath(devicePath) < 0) return -1; if (checkDevicePath2(devicePath) >= 0) { if (isMediaWhole && !isMediaLeaf) { fprintf(stderr, "%s is a whole disk\n", devicePath); return -1; } switch (daVolumeKind) { case _undetected: case _exfat: case _hfs: case _msdos: case _ntfs: break; default: unsupported(); return -1; } if (isVolumeMounted && keepMounted) isVolumeMounted = 0; if (isVolumeMounted && umount(devicePath) < 0) { fprintf(stderr, "Unable to umount %s, please 'diskutil umount' manually before running this program\n", devicePath); return -1; } } /* * Note: * Reading a non-multiple of 512 does not work on /dev/rdisk */ if (loadChunk(devicePath, daVolumeKind == _hfs ? 1024 : 0, 512, &bpbBlob) < 0) goto remount_and_error; if (checkSupportedVolume(&daVolumeKind, &bpbBlob, devicePath) < 0) goto cleanup_and_error; if (!bootFile) { switch (daVolumeKind) { case _exfat: bootFile = &defaultBootFile_exfat[0]; break; case _hfs: bootFile = &defaultBootFile_hfs[0]; break; case _msdos: bootFile = &defaultBootFile_fat32[0]; break; default: unsupported(); return -1; } printf("Using %s as default boot template\n", bootFile); } if (loadChunk(bootFile, 0, 0, &bootBlob) < 0) goto cleanup_and_error; switch (daVolumeKind) { case _exfat: if (calcSum(&bootBlob, &bpbBlob, &outputBlob, devicePath) < 0) goto cleanup_and_error; break; case _hfs: free_buffer(&bpbBlob); if (bootBlob._s != 1024U) { fprintf(stderr, "Boot Code size must be 1024 bytes\n"); goto cleanup_and_error; } break; case _msdos: if (fat32Layout(&bootBlob, &bpbBlob, &outputBlob) < 0) goto cleanup_and_error; break; default: assert(0); break; } if (!dontAsk) { printf("About to write new boot record on %s, Are You Sure (Y/N)?", devicePath); ch = 0; while (ch != 'Y' && ch != 'N') ch = getchar(); if (ch != 'Y') { printf("Aborted due to user request\n"); goto cleanup_and_exit; } } switch (daVolumeKind) { case _exfat: writeVBR(devicePath, &outputBlob, 2, 12U * 512U, "exFAT"); break; case _hfs: writeVBR(devicePath, &bootBlob, 1, 1024U, "HFS+"); break; case _msdos: writeVBR(devicePath, &outputBlob, 1, 512U, "FAT32"); break; default: assert(0); break; } cleanup_and_exit: cleanup(); if (isVolumeMounted) mount(devicePath); return 0; cleanup_and_error: cleanup(); remount_and_error: if (isVolumeMounted) mount(devicePath); return -1; usage_and_error: usage(argv[0]); return -1; }