#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif
#ifdef HAVE_STDIO_H
#include <stdio.h>
#endif
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#ifdef HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
#ifdef HAVE_SYS_MOUNT_H
#include <sys/mount.h>
#endif
#ifdef HAVE_SYS_DISK_H
#include <sys/disk.h>
#endif
#ifdef HAVE_LINUX_FD_H
#include <linux/fd.h>
#endif
#ifdef HAVE_LINUX_HDREG_H
#include <linux/hdreg.h>
#endif
#ifdef ENABLE_HD
#include <hd.h>
#endif
#ifdef __HAIKU__
#include <Drivers.h>
#endif
#include "types.h"
#include "mst.h"
#include "debug.h"
#include "device.h"
#include "logging.h"
#include "misc.h"
#if defined(linux) && defined(_IO) && !defined(BLKGETSIZE)
#define BLKGETSIZE _IO(0x12,96)
#endif
#if defined(linux) && defined(_IOR) && !defined(BLKGETSIZE64)
#define BLKGETSIZE64 _IOR(0x12,114,size_t)
#endif
#if defined(linux) && !defined(HDIO_GETGEO)
#define HDIO_GETGEO 0x0301
#endif
#if defined(linux) && defined(_IO) && !defined(BLKSSZGET)
# define BLKSSZGET _IO(0x12,104)
#endif
#if defined(linux) && defined(_IO) && !defined(BLKBSZSET)
# define BLKBSZSET _IOW(0x12,113,size_t)
#endif
struct ntfs_device *ntfs_device_alloc(const char *name, const long state,
struct ntfs_device_operations *dops, void *priv_data)
{
struct ntfs_device *dev;
if (!name) {
errno = EINVAL;
return NULL;
}
dev = ntfs_malloc(sizeof(struct ntfs_device));
if (dev) {
if (!(dev->d_name = strdup(name))) {
int eo = errno;
free(dev);
errno = eo;
return NULL;
}
dev->d_ops = dops;
dev->d_state = state;
dev->d_private = priv_data;
dev->d_heads = -1;
dev->d_sectors_per_track = -1;
}
return dev;
}
int ntfs_device_free(struct ntfs_device *dev)
{
if (!dev) {
errno = EINVAL;
return -1;
}
if (NDevOpen(dev)) {
errno = EBUSY;
return -1;
}
free(dev->d_name);
free(dev);
return 0;
}
int ntfs_device_sync(struct ntfs_device *dev)
{
int ret;
struct ntfs_device_operations *dops;
if (NDevDirty(dev)) {
dops = dev->d_ops;
ret = dops->sync(dev);
} else
ret = 0;
return ret;
}
s64 ntfs_pread(struct ntfs_device *dev, const s64 pos, s64 count, void *b)
{
s64 br, total;
struct ntfs_device_operations *dops;
ntfs_log_trace("pos %lld, count %lld\n",(long long)pos,(long long)count);
if (!b || count < 0 || pos < 0) {
errno = EINVAL;
return -1;
}
if (!count)
return 0;
dops = dev->d_ops;
for (total = 0; count; count -= br, total += br) {
br = dops->pread(dev, (char*)b + total, count, pos + total);
if (br > 0)
continue;
if (!br || total)
return total;
return br;
}
return total;
}
s64 ntfs_pwrite(struct ntfs_device *dev, const s64 pos, s64 count,
const void *b)
{
s64 written, total, ret = -1;
struct ntfs_device_operations *dops;
ntfs_log_trace("pos %lld, count %lld\n",(long long)pos,(long long)count);
if (!b || count < 0 || pos < 0) {
errno = EINVAL;
goto out;
}
if (!count)
return 0;
if (NDevReadOnly(dev)) {
errno = EROFS;
goto out;
}
dops = dev->d_ops;
NDevSetDirty(dev);
for (total = 0; count; count -= written, total += written) {
written = dops->pwrite(dev, (const char*)b + total, count,
pos + total);
if (written > 0)
continue;
if (!written || total)
break;
total = written;
break;
}
if (NDevSync(dev) && total && dops->sync(dev)) {
total--;
}
ret = total;
out:
return ret;
}
s64 ntfs_mst_pread(struct ntfs_device *dev, const s64 pos, s64 count,
const u32 bksize, void *b)
{
s64 br, i;
if (bksize & (bksize - 1) || bksize % NTFS_BLOCK_SIZE) {
errno = EINVAL;
return -1;
}
br = ntfs_pread(dev, pos, count * bksize, b);
if (br < 0)
return br;
count = br / bksize;
for (i = 0; i < count; ++i)
ntfs_mst_post_read_fixup((NTFS_RECORD*)
((u8*)b + i * bksize), bksize);
return count;
}
s64 ntfs_mst_pwrite(struct ntfs_device *dev, const s64 pos, s64 count,
const u32 bksize, void *b)
{
s64 written, i;
if (count < 0 || bksize % NTFS_BLOCK_SIZE) {
errno = EINVAL;
return -1;
}
if (!count)
return 0;
for (i = 0; i < count; ++i) {
int err;
err = ntfs_mst_pre_write_fixup((NTFS_RECORD*)
((u8*)b + i * bksize), bksize);
if (err < 0) {
if (!i)
return err;
count = i;
break;
}
}
written = ntfs_pwrite(dev, pos, count * bksize, b);
for (i = 0; i < count; ++i)
ntfs_mst_post_write_fixup((NTFS_RECORD*)((u8*)b + i * bksize));
if (written <= 0)
return written;
return written / bksize;
}
s64 ntfs_cluster_read(const ntfs_volume *vol, const s64 lcn, const s64 count,
void *b)
{
s64 br;
if (!vol || lcn < 0 || count < 0) {
errno = EINVAL;
return -1;
}
if (vol->nr_clusters < lcn + count) {
errno = ESPIPE;
ntfs_log_perror("Trying to read outside of volume "
"(%lld < %lld)", (long long)vol->nr_clusters,
(long long)lcn + count);
return -1;
}
br = ntfs_pread(vol->dev, lcn << vol->cluster_size_bits,
count << vol->cluster_size_bits, b);
if (br < 0) {
ntfs_log_perror("Error reading cluster(s)");
return br;
}
return br >> vol->cluster_size_bits;
}
s64 ntfs_cluster_write(const ntfs_volume *vol, const s64 lcn,
const s64 count, const void *b)
{
s64 bw;
if (!vol || lcn < 0 || count < 0) {
errno = EINVAL;
return -1;
}
if (vol->nr_clusters < lcn + count) {
errno = ESPIPE;
ntfs_log_perror("Trying to write outside of volume "
"(%lld < %lld)", (long long)vol->nr_clusters,
(long long)lcn + count);
return -1;
}
if (!NVolReadOnly(vol))
bw = ntfs_pwrite(vol->dev, lcn << vol->cluster_size_bits,
count << vol->cluster_size_bits, b);
else
bw = count << vol->cluster_size_bits;
if (bw < 0) {
ntfs_log_perror("Error writing cluster(s)");
return bw;
}
return bw >> vol->cluster_size_bits;
}
static int ntfs_device_offset_valid(struct ntfs_device *dev, s64 ofs)
{
char ch;
if (dev->d_ops->seek(dev, ofs, SEEK_SET) >= 0 &&
dev->d_ops->read(dev, &ch, 1) == 1)
return 0;
return -1;
}
s64 ntfs_device_size_get(struct ntfs_device *dev, int block_size)
{
s64 high, low;
if (!dev || block_size <= 0 || (block_size - 1) & block_size) {
errno = EINVAL;
return -1;
}
#ifdef BLKGETSIZE64
{ u64 size;
if (dev->d_ops->ioctl(dev, BLKGETSIZE64, &size) >= 0) {
ntfs_log_debug("BLKGETSIZE64 nr bytes = %llu (0x%llx)\n",
(unsigned long long)size,
(unsigned long long)size);
return (s64)size / block_size;
}
}
#endif
#ifdef BLKGETSIZE
{ unsigned long size;
if (dev->d_ops->ioctl(dev, BLKGETSIZE, &size) >= 0) {
ntfs_log_debug("BLKGETSIZE nr 512 byte blocks = %lu (0x%lx)\n",
size, size);
return (s64)size * 512 / block_size;
}
}
#endif
#ifdef FDGETPRM
{ struct floppy_struct this_floppy;
if (dev->d_ops->ioctl(dev, FDGETPRM, &this_floppy) >= 0) {
ntfs_log_debug("FDGETPRM nr 512 byte blocks = %lu (0x%lx)\n",
(unsigned long)this_floppy.size,
(unsigned long)this_floppy.size);
return (s64)this_floppy.size * 512 / block_size;
}
}
#endif
#ifdef DIOCGMEDIASIZE
{
off_t size;
if (dev->d_ops->ioctl(dev, DIOCGMEDIASIZE, &size) >= 0) {
ntfs_log_debug("DIOCGMEDIASIZE nr bytes = %llu (0x%llx)\n",
(unsigned long long)size,
(unsigned long long)size);
return (s64)size / block_size;
}
}
#endif
#ifdef DKIOCGETBLOCKCOUNT
{
uint64_t blocks;
int sector_size;
sector_size = ntfs_device_sector_size_get(dev);
if (sector_size >= 0 && dev->d_ops->ioctl(dev,
DKIOCGETBLOCKCOUNT, &blocks) >= 0)
{
ntfs_log_debug("DKIOCGETBLOCKCOUNT nr blocks = %llu (0x%llx)\n",
(unsigned long long) blocks,
(unsigned long long) blocks);
return blocks * sector_size / block_size;
}
}
#endif
#ifdef __HAIKU__
{
off_t size = 0;
partition_info partitionInfo;
device_geometry geometry;
if (dev->d_ops->control(dev, B_GET_PARTITION_INFO, &partitionInfo) == 0)
size = partitionInfo.size;
else if (dev->d_ops->control(dev, B_GET_GEOMETRY, &geometry) == 0) {
size = (off_t)geometry.cylinder_count * geometry.sectors_per_track
* geometry.head_count * geometry.bytes_per_sector;
}
if (size > 0)
return (s64)size / block_size;
}
#endif
low = 0LL;
for (high = 1024LL; !ntfs_device_offset_valid(dev, high); high <<= 1)
low = high;
while (low < high - 1LL) {
const s64 mid = (low + high) / 2;
if (!ntfs_device_offset_valid(dev, mid))
low = mid;
else
high = mid;
}
dev->d_ops->seek(dev, 0LL, SEEK_SET);
return (low + 1LL) / block_size;
}
s64 ntfs_device_partition_start_sector_get(struct ntfs_device *dev)
{
if (!dev) {
errno = EINVAL;
return -1;
}
#ifdef HDIO_GETGEO
{ struct hd_geometry geo;
if (!dev->d_ops->ioctl(dev, HDIO_GETGEO, &geo)) {
ntfs_log_debug("HDIO_GETGEO start_sect = %lu (0x%lx)\n",
geo.start, geo.start);
return geo.start;
}
}
#else
errno = EOPNOTSUPP;
#endif
return -1;
}
static int ntfs_device_get_geo(struct ntfs_device *dev)
{
int err;
if (!dev) {
errno = EINVAL;
return -1;
}
err = EOPNOTSUPP;
#ifdef ENABLE_HD
{
hd_data_t *hddata;
hd_t *hd, *devlist, *partlist = NULL;
str_list_t *names;
hd_res_t *res;
const int d_name_len = strlen(dev->d_name) + 1;
int done = 0;
hddata = calloc(1, sizeof(*hddata));
if (!hddata) {
err = ENOMEM;
goto skip_hd;
}
devlist = hd_list(hddata, hw_disk, 1, NULL);
if (!devlist) {
free(hddata);
err = ENOMEM;
goto skip_hd;
}
for (hd = devlist; hd; hd = hd->next) {
if (hd->unix_dev_name && !strncmp(dev->d_name,
hd->unix_dev_name, d_name_len))
goto got_hd;
if (hd->unix_dev_name2 && !strncmp(dev->d_name,
hd->unix_dev_name2, d_name_len))
goto got_hd;
for (names = hd->unix_dev_names; names;
names = names->next) {
if (names->str && !strncmp(dev->d_name,
names->str, d_name_len))
goto got_hd;
}
}
partlist = hd_list(hddata, hw_partition, 1, NULL);
for (hd = partlist; hd; hd = hd->next) {
if (hd->unix_dev_name && !strncmp(dev->d_name,
hd->unix_dev_name, d_name_len))
goto got_part_hd;
if (hd->unix_dev_name2 && !strncmp(dev->d_name,
hd->unix_dev_name2, d_name_len))
goto got_part_hd;
for (names = hd->unix_dev_names; names;
names = names->next) {
if (names->str && !strncmp(dev->d_name,
names->str, d_name_len))
goto got_part_hd;
}
}
goto end_hd;
got_part_hd:
hd = hd_get_device_by_idx(hddata, hd->attached_to);
if (!hd)
goto end_hd;
got_hd:
for (res = hd->res; res; res = res->next) {
if (res->any.type != res_disk_geo ||
res->disk_geo.geotype != 3)
continue;
dev->d_heads = res->disk_geo.heads;
dev->d_sectors_per_track = res->disk_geo.sectors;
done = 1;
}
end_hd:
if (partlist)
hd_free_hd_list(partlist);
hd_free_hd_list(devlist);
hd_free_hd_data(hddata);
free(hddata);
if (done) {
ntfs_log_debug("EDD/BIOD legacy heads = %u, sectors "
"per track = %u\n", dev->d_heads,
dev->d_sectors_per_track);
return 0;
}
}
skip_hd:
#endif
#ifdef HDIO_GETGEO
{ struct hd_geometry geo;
if (!dev->d_ops->ioctl(dev, HDIO_GETGEO, &geo)) {
dev->d_heads = geo.heads;
dev->d_sectors_per_track = geo.sectors;
ntfs_log_debug("HDIO_GETGEO heads = %u, sectors per "
"track = %u\n", dev->d_heads,
dev->d_sectors_per_track);
return 0;
}
err = errno;
}
#endif
errno = err;
return -1;
}
int ntfs_device_heads_get(struct ntfs_device *dev)
{
if (!dev) {
errno = EINVAL;
return -1;
}
if (dev->d_heads == -1) {
if (ntfs_device_get_geo(dev) == -1)
return -1;
if (dev->d_heads == -1) {
errno = EINVAL;
return -1;
}
}
return dev->d_heads;
}
int ntfs_device_sectors_per_track_get(struct ntfs_device *dev)
{
if (!dev) {
errno = EINVAL;
return -1;
}
if (dev->d_sectors_per_track == -1) {
if (ntfs_device_get_geo(dev) == -1)
return -1;
if (dev->d_sectors_per_track == -1) {
errno = EINVAL;
return -1;
}
}
return dev->d_sectors_per_track;
}
int ntfs_device_sector_size_get(struct ntfs_device *dev)
{
if (!dev) {
errno = EINVAL;
return -1;
}
#ifdef BLKSSZGET
{
int sect_size = 0;
if (!dev->d_ops->ioctl(dev, BLKSSZGET, §_size)) {
ntfs_log_debug("BLKSSZGET sector size = %d bytes\n",
sect_size);
return sect_size;
}
}
#elif defined(DIOCGSECTORSIZE)
{
size_t sect_size = 0;
if (!dev->d_ops->ioctl(dev, DIOCGSECTORSIZE, §_size)) {
ntfs_log_debug("DIOCGSECTORSIZE sector size = %d bytes\n",
(int) sect_size);
return sect_size;
}
}
#elif defined(DKIOCGETBLOCKSIZE)
{
uint32_t sect_size = 0;
if (!dev->d_ops->ioctl(dev, DKIOCGETBLOCKSIZE, §_size)) {
ntfs_log_debug("DKIOCGETBLOCKSIZE sector size = %d bytes\n",
(int) sect_size);
return sect_size;
}
}
#else
errno = EOPNOTSUPP;
#endif
return -1;
}
int ntfs_device_block_size_set(struct ntfs_device *dev,
int block_size __attribute__((unused)))
{
if (!dev) {
errno = EINVAL;
return -1;
}
#ifdef BLKBSZSET
{
size_t s_block_size = block_size;
if (!dev->d_ops->ioctl(dev, BLKBSZSET, &s_block_size)) {
ntfs_log_debug("Used BLKBSZSET to set block size to "
"%d bytes.\n", block_size);
return 0;
}
if (!NDevBlock(dev))
return 0;
}
#else
if (!NDevBlock(dev))
return 0;
errno = EOPNOTSUPP;
#endif
return -1;
}