Giter Site home page Giter Site logo

node-gpt's Introduction

GPT - GUID Partition Table

npm npm license npm downloads

Install via npm

$ npm install --save gpt

Used by

Related Modules

  • mbr – Parse / construct Master Boot Records
  • apple-partition-map – Parse / construct Apple Partition Maps
  • blockdevice – Read from / write to block devices
  • disk – Disk / image toolbox

What can I do with this?

  • Format disks / images
  • Fix a partition table, recover a deleted partition
  • Recover the GUID Partition Table from its backup
  • Locate partitions which are inaccessible / ignored by the OS
  • Verify the integrity of the primary GPT against its backup

Usage

var GPT = require( 'gpt' )

NOTE: For brevity in the examples, error handling may be omitted, and synchronous methods used.

Examples

The following usage examples are consolidated into example/verify.js and runnable, where device would be a block device (i.e. /dev/rdisk2 on Mac OS, \\.\PhysicalDrive2 on Windows, or /dev/sdb on Linux);

sudo node example/verify.js <device>
Example output
Master Boot Record: MODERN {
  physicalDrive: 0,
  timestamp: { seconds: 0, minutes: 0, hours: 0 },
  signature: 0,
  copyProtected: false,
  partitions:
   [ Partition {
       status: 0,
       type: 238,
       sectors: 60751871,
       firstLBA: 1,
       firstCHS: CHS { cylinder: 1023, head: 255, sector: 62 },
       lastCHS: CHS { cylinder: 1023, head: 255, sector: 62 } },
     Partition {
       status: 0,
       type: 0,
       sectors: 0,
       firstLBA: 0,
       firstCHS: CHS { cylinder: 0, head: 0, sector: 0 },
       lastCHS: CHS { cylinder: 0, head: 0, sector: 0 } },
     Partition {
       status: 0,
       type: 0,
       sectors: 0,
       firstLBA: 0,
       firstCHS: CHS { cylinder: 0, head: 0, sector: 0 },
       lastCHS: CHS { cylinder: 0, head: 0, sector: 0 } },
     Partition {
       status: 0,
       type: 0,
       sectors: 0,
       firstLBA: 0,
       firstCHS: CHS { cylinder: 0, head: 0, sector: 0 },
       lastCHS: CHS { cylinder: 0, head: 0, sector: 0 } } ],
  code:
   [ Code {
       offset: 0,
       data:
        <Buffer 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ... > },
     Code {
       offset: 224,
       data:
        <Buffer 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ... > } ] }

EFI Parition: Partition {
  status: 0,
  type: 238,
  sectors: 60751871,
  firstLBA: 1,
  firstCHS: CHS { cylinder: 1023, head: 255, sector: 62 },
  lastCHS: CHS { cylinder: 1023, head: 255, sector: 62 } }

Primary: GPT {
  blockSize: 512,
  guid: 'D871C3D8-25BA-4792-BE54-171138CFA926',
  revision: 65536,
  headerSize: 92,
  headerChecksum: 1129732062,
  currentLBA: 1,
  backupLBA: 60751871,
  firstLBA: 34,
  lastLBA: 60751838,
  tableOffset: 2,
  entries: 128,
  entrySize: 128,
  tableChecksum: 4191805727,
  partitions:
   [ PartitionEntry {
       type: 'C12A7328-F81F-11D2-BA4B-00A0C93EC93B',
       guid: 'BC7E4D81-59CC-40A6-84BF-43253C95AE0D',
       name: 'EFI System Partition',
       firstLBA: 40,
       lastLBA: 409639,
       attr: 0 },
     PartitionEntry {
       type: 'EBD0A0A2-B9E5-4433-87C0-68B6B72699C7',
       guid: '1885EDDC-5F6E-45CD-8C5C-E0485563F3CC',
       name: '',
       firstLBA: 411648,
       lastLBA: 60749823,
       attr: 0 } ] }

Backup: GPT {
  blockSize: 512,
  guid: 'D871C3D8-25BA-4792-BE54-171138CFA926',
  revision: 65536,
  headerSize: 92,
  headerChecksum: 4122036460,
  currentLBA: 60751871,
  backupLBA: 1,
  firstLBA: 34,
  lastLBA: 60751838,
  tableOffset: 60751839,
  entries: 128,
  entrySize: 128,
  tableChecksum: 4191805727,
  partitions:
   [ PartitionEntry {
       type: 'C12A7328-F81F-11D2-BA4B-00A0C93EC93B',
       guid: 'BC7E4D81-59CC-40A6-84BF-43253C95AE0D',
       name: 'EFI System Partition',
       firstLBA: 40,
       lastLBA: 409639,
       attr: 0 },
     PartitionEntry {
       type: 'EBD0A0A2-B9E5-4433-87C0-68B6B72699C7',
       guid: '1885EDDC-5F6E-45CD-8C5C-E0485563F3CC',
       name: '',
       firstLBA: 411648,
       lastLBA: 60749823,
       attr: 0 } ] }

[OK]

Finding the EFI Partition

The indicator for a GPT formatted device is a protective or hybrid Master Boot Record, containing a partition marked with a type of either 0xEE or 0xEF respectively. Thus we need to read & parse the MBR to determine whether a GPT is present or not:

var MBR = require( 'mbr' )
function readMBR() {
  var buffer = Buffer.alloc( 512 )
  fs.readSync( fd, buffer, 0, buffer.length, 0 )
  return MBR.parse( buffer )
}

var mbr = readMBR()
var efiPart = mbr.getEFIPart()

if( efiPart == null ) {
  console.log( 'No EFI partition found' )
}

Reading the Primary GPT

function readPrimaryGPT( efiPart ) {
  
  // NOTE: You'll need to know / determine the logical block size of the storage device;
  // For the sake of brevity, we'll just go with the still most common 512 bytes
  var gpt = new GPT({ blockSize: 512 })
  
  // NOTE: For protective GPTs (0xEF), the MBR's partitions
  // attempt to span as much of the device as they can to protect
  // against systems attempting to action on the device,
  // so the GPT is then located at LBA 1, not the EFI partition's first LBA
  var offset = efiPart.type == 0xEE ?
    efiPart.firstLBA * gpt.blockSize :
    gpt.blockSize
  
  // The default GPT is 33 blocks in length (1 block header, 32 block table)
  var buffer = Buffer.alloc( 33 * gpt.blockSize )
  
  fs.readSync( fd, buffer, 0, buffer.length, offset )
  
  return GPT.parse( buffer )
  
}

var primaryGPT = readPrimaryGPT( efiPart )

NOTE: Reading & parsing a GPT like above will just work in most cases, but to cover all situations (i.e. padding between header & table, custom table entry sizes, etc.), see the "Accounting for everything" example below:

Accounting for everything
function readPrimaryGPT( efiPart ) {
  
  // NOTE: You'll need to know / determine the logical block size of the storage device;
  // For the sake of brevity, we'll just go with the still most common 512 bytes
  var gpt = new GPT({ blockSize: 512 })
  
  // NOTE: For protective GPTs (0xEF), the MBR's partitions
  // attempt to span as much of the device as they can to protect
  // against systems attempting to action on the device,
  // so the GPT is then located at LBA 1, not the EFI partition's first LBA
  var offset = efiPart.type == 0xEE ?
    efiPart.firstLBA * gpt.blockSize :
    gpt.blockSize
  
  // First, we need to read & parse the GPT header, which will declare various
  // sizes and offsets for us to calculate where & how long the table and backup are
  var headerBuffer = Buffer.alloc( gpt.blockSize )
  
  fs.readSync( fd, headerBuffer, 0, headerBuffer.length, offset )
  gpt.parseHeader( headerBuffer )
  
  // Now on to reading the actual partition table
  var tableBuffer = Buffer.alloc( gpt.tableSize )
  var tableOffset = gpt.tableOffset * gpt.blockSize
  
  fs.readSync( fd, tableBuffer, 0, tableBuffer.length, tableOffset )
  
  // We need to parse the first 4 partition entries & the rest separately
  // as the first 4 table entries always occupy one block,
  // with the rest following in subsequent blocks
  gpt.parseTable( tableBuffer, 0, gpt.blockSize )
  gpt.parseTable( tableBuffer, gpt.blockSize, gpt.tableSize )
  
  return gpt
  
}

var primaryGPT = readPrimaryGPT( efiPart )

Reading the Backup GPT

function readBackupGPT(primaryGPT) {
  
  var backupGPT = new GPT({ blockSize: primaryGPT.blockSize })
  var buffer = Buffer.alloc( 33 * primaryGPT.blockSize )
  var offset = ( ( primaryGPT.backupLBA - 32 ) * blockSize )
  
  fs.readSync( fd, buffer, 0, buffer.length, offset )
  backupGPT.parseBackup( buffer )
  
  return backupGPT
  
}

var backupGPT = readBackupGPT(primaryGPT)
Accounting for everything
function readBackupGPT(primaryGPT) {
  
  var backupGPT = new GPT({ blockSize: primaryGPT.blockSize })
  
  return backupGPT
  
}

var backupGPT = readBackupGPT(primaryGPT)

Verifying the GPT

// Check header & table checksums for primary and backup GPT
var isPrimaryValid = primaryGPT.verify() // true
var isBackupValid = backupGPT.verify() // true

// Compare primary & backup GPT table checksums to
// verify both are in the same state
// NOTE: If there's a `gpt.headerChecksum` match, something's wrong,
// as the primary and backup should refer to each other in offsets
var checksumsMatch = primaryGPT.tableChecksum === backupGPT.tableChecksum &&
  primaryGPT.headerChecksum !== backupGPT.headerChecksum

if( isPrimaryValid && isBackupValid && checksumsMatch ) {
  console.log('Everything\'s alright')
}

Creating a GPT

var gpt = new GPT({
  // The block size of the device
  // the GPT will be written to
  // (optional, defaults to 512)
  blockSize: 512,
  // The GUID of the GPT (needs to be generated when creating)
  guid: '00000000-0000-0000-0000-000000000000'
  // Header size in bytes
  // (min. 92, defaults to 92)
  headerSize: 92,
  // LBA of current GPT copy
  currentLBA: 1n,
  // LBA of backup GPT
  backupLBA: 123456n,
  // LBA of first "user-space" block
  firstLBA: 34n,
  // LBA of last "user-space" block
  lastLBA: 556789n,
  // LBA of partition table
  // (defaults to 2)
  tableOffset: 2n,
  // Number of partition entries
  // (min. 128, defaults to 128)
  entries: 128,
  // Size of partition entry in bytes
  // (defaults to 128)
  entrySize: 128,
})
gpt.partitions.push( new GPT.PartitionEntry({
  type: 'C12A7328-F81F-11D2-BA4B-00A0C93EC93B',
  guid: 'BC7E4D81-59CC-40A6-84BF-43253C95AE0D',
  name: 'EFI System Partition',
  firstLBA: 40n,
  lastLBA: 409639n,
}))

Writing the GPT

You can either let the module create the buffers for you;

var primaryGPTBuffer = gpt.write()
var backupGPTBuffer = gpt.writeBackupFromPrimary()

Or pass in a buffer & optional offset (i.e. to write MBR & primary GPT in one go);

var buffer = Buffer.alloc( blockSize + ( 33 * blockSize ) )
var backupGPTBuffer = Buffer.alloc( 33 * blockSize )

mbr.write( buffer )

gpt.write( buffer, blockSize )
gpt.writeBackupFromPrimary( backupGPTBuffer )
// Write out MBR & primary GPT
fs.writeSync( fd, buffer, 0, buffer.length, 0 )

// Write out backup GPT to end of device
// NOTE: remeber to set `gpt.backupLBA` accordingly
fs.writeSync( fd, backupGPTBuffer, 0, backupGPTBuffer.length, gpt.backupLBA * blockSize )

node-gpt's People

Contributors

elboulangero avatar jhermsmeier avatar lurch avatar zvin avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

node-gpt's Issues

Method of detecting a GPT disk is incorrect (AFAICT)

Hello @jhermsmeier ,

I've been doing some more reading-up about GPT (I'm not entirely sure why 😆 ) and I've found that the way you detect whether a disk has GPT partitioning or not is slightly incorrect.

It's a bit confusing due to the way that it's all lumped together in the UEFI specs, but it seems that BIOS / MBR / EFI / GPT are actually all different things, and can be used in various combinations.

  • In the "olden days" computers would boot using BIOS, and a HDD would only ever use MBR partitioning (with all the fun & games of extended and logical partitions that that involves).
  • In the "modern times" computers will boot using UEFI, and a HDD would only ever use GPT partitioning (with one of those partitions being an "EFI System Partition") - this requires both the computer's firmware and its OS to understand both GPT and EFI.
  • But in practice, other middle-grounds are possible, such as a BIOS booting an MBR-partitioned HDD containing an EFI System Partition (I had a Windows7 laptop that worked like this), or a GPT-partitioned disk not containing an EFI System Partition (e.g. a secondary data-disk with no OS).

Protective / Hybrid MBRs
Wikipedia is unfortunately a bit light on the details, but I found this page which has a better description. In summary: every GPT-partitioned disk will always have the first partition of type 0xEE. The difference between Protective and Hybrid is that a Protective MBR only has a a single partition (which covers the entire disk or 2TB, whichever is smaller) whereas a Hybrid MBR will also have MBR-mapped partitions that define (a subset of?) the same partitions as the GPT-mapped partitions, in order to support OSes which only understand MBR but not GPT.

EFI System Partition
Wikipedia description - "The EFI system partition (ESP) is formatted with a file system whose specification is based on the FAT file system" ... "An ESP contains the boot loaders or kernel images for all installed operating systems" ... "The GUID for the EFI system partition in the GPT scheme is C12A7328-F81F-11D2-BA4B-00A0C93EC93B, while its ID in the MBR partition-table scheme is 0xEF." ... "Both GPT- and MBR-partitioned disks can contain an EFI system partition"
So an MBR that contains an 0xEF partition-type is either an MBR-partitioned disk that contains an ESP, or (if the first MBR partition has type 0xEE) then it's a GPT-partitioned disk with a Hybrid MBR that contains an ESP.
(Without a Hybrid MBR, a GPT-partitioned disk could still contain an ESP but it'd be invisible to the MBR)

https://github.com/jhermsmeier/node-gpt/blob/master/README.md says: "The indicator for a GPT formatted device is a protective or hybrid Master Boot Record, containing a partition marked with a type of either 0xEE or 0xEF respectively." - AFAICT, this isn't true. I believe it should say "The indicator for a GPT partitioned device is a protective or hybrid Master Boot Record, containing one-or-more partitions, with the first partition marked with a type of 0xEE"
I therefore believe that mbr.getEFIPart() should actually be named mbr.hasGPTPartitioning() with the method simply doing

return this.partitions.length >= 1 && this.partitions[0].type === 0xEE

(mbr.getEFIPart() should probably be deprecated?)

https://github.com/jhermsmeier/node-gpt/blob/master/README.md goes on to say:

  // NOTE: For protective GPTs (0xEF), the MBR's partitions
  // attempt to span as much of the device as they can to protect
  // against systems attempting to action on the device,
  // so the GPT is then located at LBA 1, not the EFI partition's first LBA
  var offset = efiPart.type == 0xEE ?
    efiPart.firstLBA * gpt.blockSize :
    gpt.blockSize

Again this appears to be wrong. Protective MBR partitions are 0xEE, and the GPT is always located at LBA 1, the ESP doesn't have anything to do with the disk's partitioning. So the line of code should be just var offset = gpt.blockSize. The code only worked previously because as already mentioned an MBR partition of type 0xEE will always be the first partition on the disk (and start at LBA1 in order to protect the GPT structures from any MBR-tools that might otherwise see LBA1 as "unused diskspace").

I hope that's all useful, and I hope it makes sense!

As a slight addendum, perhaps it also makes sense for your gpt class to be able to generate the protective-MBR it would require?

Addendum2: Oooh, I've just looked at the EFI specs again, and they actually specify that you should "Check for GUID Partition Table Headers" (i.e. on LBA1 and LBA[disksize-1]) before you "check LBA 0 for a legacy MBR partition table." 👀
But I've obviously got no idea if that's the same logic that all partition-related tools use 😉 🤷‍♂️

122 partitions detected on FreeNAS iso

node-gpt detects 122 partitions with incoherent sizes, offsets and names on this iso: https://download.freenas.org/11.3/STABLE/U3.2/x64/FreeNAS-11.3-U3.2.iso

fdisk sees this:

The backup GPT table is corrupt, but the primary appears OK, so that will be used.
Disk FreeNAS-11.3-U3.2.iso: 748.16 MiB, 784496640 bytes, 1532220 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: B05BDCDF-A44F-11EA-A1B4-0CC47A1228F8

Device                 Start   End Sectors  Size Type
FreeNAS-11.3-U3.2.iso1    80  1679    1600  800K EFI System
FreeNAS-11.3-U3.2.iso2     3    32      30   15K FreeBSD boot

Partition table entries are not in disk order.

Change the prototype of functions that deal with backup GPT?

Currently, the functions that deal with backup GPTs (i.e. parseBackup and writeBackup) have a function-prototype which accepts a buffer and an offset.

The parseBackup function reads the table-entries at offset and the header at buffer.length - this.blockSize - however this seems to assume that offset is set to exactly the right amount of distance before the end of the buffer, which feels incredibly fragile?

The writeBackup function writes the table-entries at offset and the header at offset + tableSize which is a subtle (but IMHO fundamentally different) method of operation from parseBackup. Again this is fragile as it assumes that the offset is positioned just right, and that the buffer is big enough to hold offset + tableSize + blockSize.

From reading the GPT specs, it feels (to me) like the offset passed into both the parseBackup and writeBackup functions ought to be the headerOffset of the backup GPT header (i.e. which is typically the last sector on the disk). However this assumes that the buffer being passed in is seek()-able - is that a safe assumption to make?
When reading the backupGPT, you need to first read the "Starting LBA of array of partition entries" from the header in order to calculate how far backwards to seek to begin reading partition-entries from, and when writing the backupGPT you need to know how far backwards you need to seek so that you start writing the partition-entries at "the right place".

However these changes would obviously result in a change-of-API, and so would necessitate a major version-bump.

As an example of why this matters: imagine that you have a disk with a totally corrupt primaryGPT but a valid backupGPT - with the way that node-gpt currently works you'd have no idea what offset value to pass to parseBackup. (remember that GPT allows an unlimited number of partitions, so the partition-table-array may be larger than 16KB)

I don't mean to sound picky, I'm just trying to make the code more robust 🙂

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.