--- /dev/null
+/*
+ * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <inttypes.h>
+
+#include "superblocks.h"
+
+struct ntfs_bios_parameters {
+ uint16_t sector_size; /* Size of a sector in bytes. */
+ uint8_t sectors_per_cluster; /* Size of a cluster in sectors. */
+ uint16_t reserved_sectors; /* zero */
+ uint8_t fats; /* zero */
+ uint16_t root_entries; /* zero */
+ uint16_t sectors; /* zero */
+ uint8_t media_type; /* 0xf8 = hard disk */
+ uint16_t sectors_per_fat; /* zero */
+ uint16_t sectors_per_track; /* irrelevant */
+ uint16_t heads; /* irrelevant */
+ uint32_t hidden_sectors; /* zero */
+ uint32_t large_sectors; /* zero */
+} __attribute__ ((__packed__));
+
+struct ntfs_super_block {
+ uint8_t jump[3];
+ uint8_t oem_id[8]; /* magic string */
+
+ struct ntfs_bios_parameters bpb;
+
+ uint16_t unused[2];
+ uint64_t number_of_sectors;
+ uint64_t mft_cluster_location;
+ uint64_t mft_mirror_cluster_location;
+ int8_t clusters_per_mft_record;
+ uint8_t reserved1[3];
+ int8_t cluster_per_index_record;
+ uint8_t reserved2[3];
+ uint64_t volume_serial;
+ uint32_t checksum;
+} __attribute__((packed));
+
+struct master_file_table_record {
+ uint32_t magic;
+ uint16_t usa_ofs;
+ uint16_t usa_count;
+ uint64_t lsn;
+ uint16_t sequence_number;
+ uint16_t link_count;
+ uint16_t attrs_offset;
+ uint16_t flags;
+ uint32_t bytes_in_use;
+ uint32_t bytes_allocated;
+} __attribute__((__packed__));
+
+struct file_attribute {
+ uint32_t type;
+ uint32_t len;
+ uint8_t non_resident;
+ uint8_t name_len;
+ uint16_t name_offset;
+ uint16_t flags;
+ uint16_t instance;
+ uint32_t value_len;
+ uint16_t value_offset;
+} __attribute__((__packed__));
+
+#define MFT_RECORD_VOLUME 3
+#define NTFS_MAX_CLUSTER_SIZE (64 * 1024)
+
+enum {
+ MFT_RECORD_ATTR_VOLUME_NAME = 0x60,
+ MFT_RECORD_ATTR_END = 0xffffffff
+};
+
+static int probe_ntfs(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct ntfs_super_block *ns;
+#if 0
+ struct master_file_table_record *mft;
+#endif
+
+ uint32_t sectors_per_cluster, mft_record_size;
+ uint16_t sector_size;
+ uint64_t nr_clusters, off; //, attr_off;
+ unsigned char *buf_mft;
+
+ ns = blkid_probe_get_sb(pr, mag, struct ntfs_super_block);
+ if (!ns)
+ return errno ? -errno : 1;
+
+ /*
+ * Check bios parameters block
+ */
+ sector_size = le16_to_cpu(ns->bpb.sector_size);
+ sectors_per_cluster = ns->bpb.sectors_per_cluster;
+
+ if (sector_size < 256 || sector_size > 4096)
+ return 1;
+
+ switch (sectors_per_cluster) {
+ case 1: case 2: case 4: case 8: case 16: case 32: case 64: case 128:
+ break;
+ default:
+ return 1;
+ }
+
+ if ((uint16_t) le16_to_cpu(ns->bpb.sector_size) *
+ ns->bpb.sectors_per_cluster > NTFS_MAX_CLUSTER_SIZE)
+ return 1;
+
+ /* Unused fields must be zero */
+ if (le16_to_cpu(ns->bpb.reserved_sectors)
+ || le16_to_cpu(ns->bpb.root_entries)
+ || le16_to_cpu(ns->bpb.sectors)
+ || le16_to_cpu(ns->bpb.sectors_per_fat)
+ || le32_to_cpu(ns->bpb.large_sectors)
+ || ns->bpb.fats)
+ return 1;
+
+ if ((uint8_t) ns->clusters_per_mft_record < 0xe1
+ || (uint8_t) ns->clusters_per_mft_record > 0xf7) {
+
+ switch (ns->clusters_per_mft_record) {
+ case 1: case 2: case 4: case 8: case 16: case 32: case 64:
+ break;
+ default:
+ return 1;
+ }
+ }
+
+ if (ns->clusters_per_mft_record > 0)
+ mft_record_size = ns->clusters_per_mft_record *
+ sectors_per_cluster * sector_size;
+ else
+ mft_record_size = 1 << (0 - ns->clusters_per_mft_record);
+
+ nr_clusters = le64_to_cpu(ns->number_of_sectors) / sectors_per_cluster;
+
+ if ((le64_to_cpu(ns->mft_cluster_location) > nr_clusters) ||
+ (le64_to_cpu(ns->mft_mirror_cluster_location) > nr_clusters))
+ return 1;
+
+
+ off = le64_to_cpu(ns->mft_cluster_location) * sector_size *
+ sectors_per_cluster;
+
+ DBG(LOWPROBE, ul_debug("NTFS: sector_size=%"PRIu16", mft_record_size=%"PRIu32", "
+ "sectors_per_cluster=%"PRIu32", nr_clusters=%"PRIu64" "
+ "cluster_offset=%"PRIu64"",
+ sector_size, mft_record_size,
+ sectors_per_cluster, nr_clusters,
+ off));
+
+ buf_mft = blkid_probe_get_buffer(pr, off, mft_record_size);
+ if (!buf_mft)
+ return errno ? -errno : 1;
+
+ if (memcmp(buf_mft, "FILE", 4))
+ return 1;
+
+ off += MFT_RECORD_VOLUME * mft_record_size;
+
+ buf_mft = blkid_probe_get_buffer(pr, off, mft_record_size);
+ if (!buf_mft)
+ return errno ? -errno : 1;
+
+ if (memcmp(buf_mft, "FILE", 4))
+ return 1;
+
+#if 0
+ mft = (struct master_file_table_record *) buf_mft;
+ attr_off = le16_to_cpu(mft->attrs_offset);
+
+ while (attr_off + sizeof(struct file_attribute) <= mft_record_size &&
+ attr_off <= le32_to_cpu(mft->bytes_allocated)) {
+
+ uint32_t attr_len;
+ struct file_attribute *attr;
+
+ attr = (struct file_attribute *) (buf_mft + attr_off);
+ attr_len = le32_to_cpu(attr->len);
+ if (!attr_len)
+ break;
+
+ if (le32_to_cpu(attr->type) == MFT_RECORD_ATTR_END)
+ break;
+ if (le32_to_cpu(attr->type) == MFT_RECORD_ATTR_VOLUME_NAME) {
+ unsigned int val_off = le16_to_cpu(attr->value_offset);
+ unsigned int val_len = le32_to_cpu(attr->value_len);
+ unsigned char *val = ((uint8_t *) attr) + val_off;
+
+ if (attr_off + val_off + val_len <= mft_record_size)
+ blkid_probe_set_utf8label(pr, val, val_len,
+ BLKID_ENC_UTF16LE);
+ break;
+ }
+
+ attr_off += attr_len;
+ }
+#endif
+
+ blkid_probe_sprintf_uuid(pr,
+ (unsigned char *) &ns->volume_serial,
+ sizeof(ns->volume_serial),
+ "%016" PRIX64, le64_to_cpu(ns->volume_serial));
+ return 0;
+}
+
+
+const struct blkid_idinfo ntfs_idinfo =
+{
+ .name = "ntfs",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_ntfs,
+ .magics =
+ {
+ { .magic = "NTFS ", .len = 8, .sboff = 3 },
+ { NULL }
+ }
+};
+