Skip to content

Commit

Permalink
LDM: Fix for Windows Vista dynamic disks
Browse files Browse the repository at this point in the history
This fixes the LDM driver so that it works with Windows Vista dynamic
disks which are subtly different to Windows 2000/XP ones.

The patch was needed to get a Vista formatted dynamic disk to be
recognized and parsed successfully.

Thanks go to Chris Teachworth for the report and testing.

Cc: Richard Russon <[email protected]>
Signed-off-by: Anton Altaparmakov <[email protected]>
Signed-off-by: Linus Torvalds <[email protected]>
  • Loading branch information
AntonAltaparmakov authored and Linus Torvalds committed May 21, 2007
1 parent 1730438 commit dde3334
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 107 deletions.
21 changes: 14 additions & 7 deletions Documentation/ldm.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@
LDM - Logical Disk Manager (Dynamic Disks)
------------------------------------------

Originally Written by FlatCap - Richard Russon <[email protected]>.
Last Updated by Anton Altaparmakov on 30 March 2007 for Windows Vista.

Overview
--------

Windows 2000 and XP use a new partitioning scheme. It is a complete
Windows 2000, XP, and Vista use a new partitioning scheme. It is a complete
replacement for the MSDOS style partitions. It stores its information in a
1MiB journalled database at the end of the physical disk. The size of
partitions is limited only by disk space. The maximum number of partitions is
Expand All @@ -23,7 +26,11 @@ Once the LDM driver has divided up the disk, you can use the MD driver to
assemble any multi-partition volumes, e.g. Stripes, RAID5.

To prevent legacy applications from repartitioning the disk, the LDM creates a
dummy MSDOS partition containing one disk-sized partition.
dummy MSDOS partition containing one disk-sized partition. This is what is
supported with the Linux LDM driver.

A newer approach that has been implemented with Vista is to put LDM on top of a
GPT label disk. This is not supported by the Linux LDM driver yet.


Example
Expand Down Expand Up @@ -88,13 +95,13 @@ and cannot boot from a Dynamic Disk.
More Documentation
------------------

There is an Overview of the LDM online together with complete Technical
Documentation. It can also be downloaded in html.
There is an Overview of the LDM together with complete Technical Documentation.
It is available for download.

http://linux-ntfs.sourceforge.net/ldm/index.html
http://linux-ntfs.sourceforge.net/downloads.html
http://www.linux-ntfs.org/content/view/19/37/

If you have any LDM questions that aren't answered on the website, email me.
If you have any LDM questions that aren't answered in the documentation, email
me.

Cheers,
FlatCap - Richard Russon
Expand Down
6 changes: 3 additions & 3 deletions MAINTAINERS
Original file line number Diff line number Diff line change
Expand Up @@ -2231,11 +2231,11 @@ M: [email protected]
L: [email protected]
S: Maintained

LOGICAL DISK MANAGER SUPPORT (LDM, Windows 2000/XP Dynamic Disks)
LOGICAL DISK MANAGER SUPPORT (LDM, Windows 2000/XP/Vista Dynamic Disks)
P: Richard Russon (FlatCap)
M: [email protected]
L: ldm-devel@lists.sourceforge.net
W: http://ldm.sourceforge.net
L: linux-ntfs-dev@lists.sourceforge.net
W: http://www.linux-ntfs.org/content/view/19/37/
S: Maintained

LSILOGIC MPT FUSION DRIVERS (FC/SAS/SPI)
Expand Down
12 changes: 8 additions & 4 deletions fs/partitions/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -166,17 +166,21 @@ config LDM_PARTITION
depends on PARTITION_ADVANCED
---help---
Say Y here if you would like to use hard disks under Linux which
were partitioned using Windows 2000's or XP's Logical Disk Manager.
They are also known as "Dynamic Disks".
were partitioned using Windows 2000's/XP's or Vista's Logical Disk
Manager. They are also known as "Dynamic Disks".

Note this driver only supports Dynamic Disks with a protective MBR
label, i.e. DOS partition table. It does not support GPT labelled
Dynamic Disks yet as can be created with Vista.

Windows 2000 introduced the concept of Dynamic Disks to get around
the limitations of the PC's partitioning scheme. The Logical Disk
Manager allows the user to repartition a disk and create spanned,
mirrored, striped or RAID volumes, all without the need for
rebooting.

Normal partitions are now called Basic Disks under Windows 2000 and
XP.
Normal partitions are now called Basic Disks under Windows 2000, XP,
and Vista.

For a fuller description read <file:Documentation/ldm.txt>.

Expand Down
206 changes: 116 additions & 90 deletions fs/partitions/ldm.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
* ldm - Support for Windows Logical Disk Manager (Dynamic Disks)
*
* Copyright (C) 2001,2002 Richard Russon <[email protected]>
* Copyright (c) 2001-2004 Anton Altaparmakov
* Copyright (c) 2001-2007 Anton Altaparmakov
* Copyright (C) 2001,2002 Jakob Kemi <[email protected]>
*
* Documentation is available at http://linux-ntfs.sf.net/ldm
* Documentation is available at http://www.linux-ntfs.org/content/view/19/37/
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
Expand Down Expand Up @@ -62,7 +62,6 @@ static void _ldm_printk (const char *level, const char *function,
printk ("%s%s(): %s\n", level, function, buf);
}


/**
* ldm_parse_hexbyte - Convert a ASCII hex number to a byte
* @src: Pointer to at least 2 characters to convert.
Expand Down Expand Up @@ -118,7 +117,6 @@ static bool ldm_parse_guid (const u8 *src, u8 *dest)
return true;
}


/**
* ldm_parse_privhead - Read the LDM Database PRIVHEAD structure
* @data: Raw database PRIVHEAD structure loaded from the device
Expand All @@ -130,46 +128,48 @@ static bool ldm_parse_guid (const u8 *src, u8 *dest)
* Return: 'true' @ph contains the PRIVHEAD data
* 'false' @ph contents are undefined
*/
static bool ldm_parse_privhead (const u8 *data, struct privhead *ph)
static bool ldm_parse_privhead(const u8 *data, struct privhead *ph)
{
BUG_ON (!data || !ph);
bool is_vista = false;

if (MAGIC_PRIVHEAD != BE64 (data)) {
ldm_error ("Cannot find PRIVHEAD structure. LDM database is"
BUG_ON(!data || !ph);
if (MAGIC_PRIVHEAD != BE64(data)) {
ldm_error("Cannot find PRIVHEAD structure. LDM database is"
" corrupt. Aborting.");
return false;
}

ph->ver_major = BE16 (data + 0x000C);
ph->ver_minor = BE16 (data + 0x000E);
ph->logical_disk_start = BE64 (data + 0x011B);
ph->logical_disk_size = BE64 (data + 0x0123);
ph->config_start = BE64 (data + 0x012B);
ph->config_size = BE64 (data + 0x0133);

if ((ph->ver_major != 2) || (ph->ver_minor != 11)) {
ldm_error ("Expected PRIVHEAD version %d.%d, got %d.%d."
" Aborting.", 2, 11, ph->ver_major, ph->ver_minor);
ph->ver_major = BE16(data + 0x000C);
ph->ver_minor = BE16(data + 0x000E);
ph->logical_disk_start = BE64(data + 0x011B);
ph->logical_disk_size = BE64(data + 0x0123);
ph->config_start = BE64(data + 0x012B);
ph->config_size = BE64(data + 0x0133);
/* Version 2.11 is Win2k/XP and version 2.12 is Vista. */
if (ph->ver_major == 2 && ph->ver_minor == 12)
is_vista = true;
if (!is_vista && (ph->ver_major != 2 || ph->ver_minor != 11)) {
ldm_error("Expected PRIVHEAD version 2.11 or 2.12, got %d.%d."
" Aborting.", ph->ver_major, ph->ver_minor);
return false;
}
ldm_debug("PRIVHEAD version %d.%d (Windows %s).", ph->ver_major,
ph->ver_minor, is_vista ? "Vista" : "2000/XP");
if (ph->config_size != LDM_DB_SIZE) { /* 1 MiB in sectors. */
/* Warn the user and continue, carefully */
ldm_info ("Database is normally %u bytes, it claims to "
/* Warn the user and continue, carefully. */
ldm_info("Database is normally %u bytes, it claims to "
"be %llu bytes.", LDM_DB_SIZE,
(unsigned long long)ph->config_size );
udunsigned long long)ph->config_size);
}
if ((ph->logical_disk_size == 0) ||
(ph->logical_disk_start + ph->logical_disk_size > ph->config_start)) {
ldm_error ("PRIVHEAD disk size doesn't match real disk size");
if ((ph->logical_disk_size == 0) || (ph->logical_disk_start +
ph->logical_disk_size > ph->config_start)) {
ldm_error("PRIVHEAD disk size doesn't match real disk size");
return false;
}

if (!ldm_parse_guid (data + 0x0030, ph->disk_id)) {
ldm_error ("PRIVHEAD contains an invalid GUID.");
if (!ldm_parse_guid(data + 0x0030, ph->disk_id)) {
ldm_error("PRIVHEAD contains an invalid GUID.");
return false;
}

ldm_debug ("Parsed PRIVHEAD successfully.");
ldm_debug("Parsed PRIVHEAD successfully.");
return true;
}

Expand Down Expand Up @@ -409,62 +409,65 @@ static bool ldm_validate_privheads (struct block_device *bdev,
* Return: 'true' @toc1 contains validated TOCBLOCK info
* 'false' @toc1 contents are undefined
*/
static bool ldm_validate_tocblocks (struct block_device *bdev,
static bool ldm_validate_tocblocks(struct block_device *bdev,
unsigned long base, struct ldmdb *ldb)
{
static const int off[4] = { OFF_TOCB1, OFF_TOCB2, OFF_TOCB3, OFF_TOCB4};
struct tocblock *tb[4];
struct privhead *ph;
Sector sect;
u8 *data;
int i, nr_tbs;
bool result = false;
int i;

BUG_ON (!bdev || !ldb);

ph = &ldb->ph;
BUG_ON(!bdev || !ldb);
ph = &ldb->ph;
tb[0] = &ldb->toc;
tb[1] = kmalloc (sizeof (*tb[1]), GFP_KERNEL);
tb[2] = kmalloc (sizeof (*tb[2]), GFP_KERNEL);
tb[3] = kmalloc (sizeof (*tb[3]), GFP_KERNEL);
if (!tb[1] || !tb[2] || !tb[3]) {
ldm_crit ("Out of memory.");
goto out;
tb[1] = kmalloc(sizeof(*tb[1]) * 3, GFP_KERNEL);
if (!tb[1]) {
ldm_crit("Out of memory.");
goto err;
}

for (i = 0; i < 4; i++) /* Read and parse all four toc's. */
{
data = read_dev_sector (bdev, base + off[i], &sect);
tb[2] = (struct tocblock*)((u8*)tb[1] + sizeof(*tb[1]));
tb[3] = (struct tocblock*)((u8*)tb[2] + sizeof(*tb[2]));
/*
* Try to read and parse all four TOCBLOCKs.
*
* Windows Vista LDM v2.12 does not always have all four TOCBLOCKs so
* skip any that fail as long as we get at least one valid TOCBLOCK.
*/
for (nr_tbs = i = 0; i < 4; i++) {
data = read_dev_sector(bdev, base + off[i], &sect);
if (!data) {
ldm_crit ("Disk read failed.");
goto out;
ldm_error("Disk read failed for TOCBLOCK %d.", i);
continue;
}
result = ldm_parse_tocblock (data, tb[i]);
put_dev_sector (sect);
if (!result)
goto out; /* Already logged */
if (ldm_parse_tocblock(data, tb[nr_tbs]))
nr_tbs++;
put_dev_sector(sect);
}

/* Range check the toc against a privhead. */
if (!nr_tbs) {
ldm_crit("Failed to find a valid TOCBLOCK.");
goto err;
}
/* Range check the TOCBLOCK against a privhead. */
if (((tb[0]->bitmap1_start + tb[0]->bitmap1_size) > ph->config_size) ||
((tb[0]->bitmap2_start + tb[0]->bitmap2_size) > ph->config_size)) {
ldm_crit ("The bitmaps are out of range. Giving up.");
goto out;
((tb[0]->bitmap2_start + tb[0]->bitmap2_size) >
ph->config_size)) {
ldm_crit("The bitmaps are out of range. Giving up.");
goto err;
}

if (!ldm_compare_tocblocks (tb[0], tb[1]) || /* Compare all tocs. */
!ldm_compare_tocblocks (tb[0], tb[2]) ||
!ldm_compare_tocblocks (tb[0], tb[3])) {
ldm_crit ("The TOCBLOCKs don't match.");
goto out;
/* Compare all loaded TOCBLOCKs. */
for (i = 1; i < nr_tbs; i++) {
if (!ldm_compare_tocblocks(tb[0], tb[i])) {
ldm_crit("TOCBLOCKs 0 and %d do not match.", i);
goto err;
}
}

ldm_debug ("Validated TOCBLOCKs successfully.");
ldm_debug("Validated %d TOCBLOCKs successfully.", nr_tbs);
result = true;
out:
kfree (tb[1]);
kfree (tb[2]);
kfree (tb[3]);
err:
kfree(tb[1]);
return result;
}

Expand Down Expand Up @@ -566,7 +569,7 @@ static bool ldm_validate_partition_table (struct block_device *bdev)

p = (struct partition*)(data + 0x01BE);
for (i = 0; i < 4; i++, p++)
if (SYS_IND (p) == WIN2K_DYNAMIC_PARTITION) {
if (SYS_IND (p) == LDM_PARTITION) {
result = true;
break;
}
Expand Down Expand Up @@ -975,44 +978,68 @@ static bool ldm_parse_dsk4 (const u8 *buffer, int buflen, struct vblk *vb)
* Return: 'true' @vb contains a Partition VBLK
* 'false' @vb contents are not defined
*/
static bool ldm_parse_prt3 (const u8 *buffer, int buflen, struct vblk *vb)
static bool ldm_parse_prt3(const u8 *buffer, int buflen, struct vblk *vb)
{
int r_objid, r_name, r_size, r_parent, r_diskid, r_index, len;
struct vblk_part *part;

BUG_ON (!buffer || !vb);

r_objid = ldm_relative (buffer, buflen, 0x18, 0);
r_name = ldm_relative (buffer, buflen, 0x18, r_objid);
r_size = ldm_relative (buffer, buflen, 0x34, r_name);
r_parent = ldm_relative (buffer, buflen, 0x34, r_size);
r_diskid = ldm_relative (buffer, buflen, 0x34, r_parent);

BUG_ON(!buffer || !vb);
r_objid = ldm_relative(buffer, buflen, 0x18, 0);
if (r_objid < 0) {
ldm_error("r_objid %d < 0", r_objid);
return false;
}
r_name = ldm_relative(buffer, buflen, 0x18, r_objid);
if (r_name < 0) {
ldm_error("r_name %d < 0", r_name);
return false;
}
r_size = ldm_relative(buffer, buflen, 0x34, r_name);
if (r_size < 0) {
ldm_error("r_size %d < 0", r_size);
return false;
}
r_parent = ldm_relative(buffer, buflen, 0x34, r_size);
if (r_parent < 0) {
ldm_error("r_parent %d < 0", r_parent);
return false;
}
r_diskid = ldm_relative(buffer, buflen, 0x34, r_parent);
if (r_diskid < 0) {
ldm_error("r_diskid %d < 0", r_diskid);
return false;
}
if (buffer[0x12] & VBLK_FLAG_PART_INDEX) {
r_index = ldm_relative (buffer, buflen, 0x34, r_diskid);
r_index = ldm_relative(buffer, buflen, 0x34, r_diskid);
if (r_index < 0) {
ldm_error("r_index %d < 0", r_index);
return false;
}
len = r_index;
} else {
r_index = 0;
len = r_diskid;
}
if (len < 0)
if (len < 0) {
ldm_error("len %d < 0", len);
return false;

}
len += VBLK_SIZE_PRT3;
if (len != BE32 (buffer + 0x14))
if (len > BE32(buffer + 0x14)) {
ldm_error("len %d > BE32(buffer + 0x14) %d", len,
BE32(buffer + 0x14));
return false;

}
part = &vb->vblk.part;
part->start = BE64 (buffer + 0x24 + r_name);
part->volume_offset = BE64 (buffer + 0x2C + r_name);
part->size = ldm_get_vnum (buffer + 0x34 + r_name);
part->parent_id = ldm_get_vnum (buffer + 0x34 + r_size);
part->disk_id = ldm_get_vnum (buffer + 0x34 + r_parent);
part->start = BE64(buffer + 0x24 + r_name);
part->volume_offset = BE64(buffer + 0x2C + r_name);
part->size = ldm_get_vnum(buffer + 0x34 + r_name);
part->parent_id = ldm_get_vnum(buffer + 0x34 + r_size);
part->disk_id = ldm_get_vnum(buffer + 0x34 + r_parent);
if (vb->flags & VBLK_FLAG_PART_INDEX)
part->partnum = buffer[0x35 + r_diskid];
else
part->partnum = 0;

return true;
}

Expand Down Expand Up @@ -1475,4 +1502,3 @@ int ldm_partition (struct parsed_partitions *pp, struct block_device *bdev)
kfree (ldb);
return result;
}

Loading

0 comments on commit dde3334

Please sign in to comment.