diff --git a/CREDITS b/CREDITS index 09698845..cc8a0dc6 100644 --- a/CREDITS +++ b/CREDITS @@ -19,6 +19,7 @@ Marcin GibuĊ‚a Christophe Grenier Csaba Henk Ian Jackson +Max Khon Carmelo Kintana Jan Kratochvil Lode Leroy diff --git a/ChangeLog b/ChangeLog index 8dabb45b..0034c3bf 100644 --- a/ChangeLog +++ b/ChangeLog @@ -115,6 +115,8 @@ xx/12/2006 - 2.0.0 - ntfsmount sports full r/w and ntfsresize supports Vista. - mkntfs: As announced, remove the deprecated support for creation of NTFS 1.2/3.0 volumes. We now create NTFS 3.1 volumes only. (Anton) - mkntfs: Remove lots of unused/unneeded debugging code. (Anton) + - libntfs: Add support for FreeBSD 5.0+ sector aligned access + requirements. (Max Khon) 21/06/2006 - 1.13.1 - Various fixes. diff --git a/TODO.libntfs b/TODO.libntfs index 3c303b1f..6183520d 100644 --- a/TODO.libntfs +++ b/TODO.libntfs @@ -30,6 +30,9 @@ previous mount left the block size to larger value and we expect 512 bytes as that would be incredibly inefficient +- fix freebsd_io.c and win32_io.c to support device operations ->pread() and + ->pwrite() to improve their performance + ******************* * MEDIUM priority * ******************* diff --git a/libntfs/device_io.c b/libntfs/device_io.c index 9a044d61..706e935f 100644 --- a/libntfs/device_io.c +++ b/libntfs/device_io.c @@ -1,7 +1,7 @@ /* * device_io.c - Default device io operations. Part of the Linux-NTFS project. * - * Copyright (c) 2003 Anton Altaparmakov + * Copyright (c) 2003-2006 Anton Altaparmakov * * This program/include file is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as published @@ -23,16 +23,24 @@ #ifndef NO_NTFS_DEVICE_DEFAULT_IO_OPS -#ifndef __CYGWIN32__ - -/* Not on Cygwin; use standard Unix style low level device operations. */ -#include "unix_io.c" - -#else /* __CYGWIN32__ */ +#if defined(__CYGWIN32__) /* On Cygwin; use Win32 low level device operations. */ #include "win32_io.c" -#endif /* __CYGWIN32__ */ +#elif defined(__FreeBSD__) + +/* On FreeBSD; need to use sector aligned i/o. */ +#include "freebsd_io.c" + +#else + +/* + * Not on Cygwin or FreeBSD; use standard Unix style low level device + * operations. + */ +#include "unix_io.c" + +#endif #endif /* NO_NTFS_DEVICE_DEFAULT_IO_OPS */ diff --git a/libntfs/freebsd_io.c b/libntfs/freebsd_io.c new file mode 100644 index 00000000..cd348d59 --- /dev/null +++ b/libntfs/freebsd_io.c @@ -0,0 +1,555 @@ +/** + * freebsd_io.c - FreeBSD disk io functions. Originated from the Linux-NTFS + * project. + * + * FreeBSD 5.0+ does not have character devices and requires + * read/writes from/to block devices to be sector aligned. + * + * Copyright (c) 2006 Max Khon + * Copyright (c) 2006 Anton Altaparmakov + * + * This program/include file 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 Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (in the main directory of the NTFS-3G + * distribution in the file COPYING); if not, write to the Free Software + * Foundation,Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_STDIO_H +#include +#endif +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_SYS_STAT_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#endif +#ifdef HAVE_SYS_IOCTL_H +#include +#endif +#ifdef HAVE_LINUX_FD_H +#include +#endif +#include + +#include "types.h" +#include "mst.h" +#include "debug.h" +#include "device.h" +#include "logging.h" + +typedef struct { + int fd; + s64 pos; + s32 block_size; + s64 media_size; +} freebsd_fd; + +#define DEV_FD(dev) (((freebsd_fd *) dev->d_private)) +#define RAW_IO_ALIGNED(dev, offset, count) \ + (DEV_FD(dev)->block_size == 0 || \ + ((offset) % DEV_FD(dev)->block_size == 0 && \ + (count) % DEV_FD(dev)->block_size == 0)) +#define RAW_IO_ALIGN(dev, offset) \ + ((offset) / DEV_FD(dev)->block_size * DEV_FD(dev)->block_size) +#define RAW_IO_MAX_SIZE (128 * 1024 * 1024) + +/* Define to nothing if not present on this system. */ +#ifndef O_EXCL +# define O_EXCL 0 +#endif + +/** + * Get block_size and media_size + */ +static int freebsd_get_size(struct ntfs_device *dev) +{ + off_t ms; + int bs; + struct stat sb; + + if (fstat(DEV_FD(dev)->fd, &sb) < 0) { + ntfs_log_perror("Failed to stat '%s'", dev->d_name); + return -1; + } + if (S_ISREG(sb.st_mode)) { + DEV_FD(dev)->media_size = sb.st_size; + ntfs_log_trace("%s: regular file (media_size %lld)\n", + dev->d_name, DEV_FD(dev)->media_size); + return 0; + } + if (ioctl(DEV_FD(dev)->fd, DIOCGSECTORSIZE, &bs) < 0) { + ntfs_log_perror("Failed to ioctl(DIOCGSECTORSIZE) '%s'", + dev->d_name); + return -1; + } + DEV_FD(dev)->block_size = bs; + ntfs_log_trace("%s: block size %d\n", dev->d_name, bs); + if (ioctl(DEV_FD(dev)->fd, DIOCGMEDIASIZE, &ms) < 0) { + ntfs_log_perror("Failed to ioctl(DIOCGMEDIASIZE) '%s'", + dev->d_name); + return -1; + } + DEV_FD(dev)->media_size = ms; + ntfs_log_trace("%s: media size %lld\n", dev->d_name, ms); + return 0; +} + +/** + * freebsd_pread - Aligned read + */ +static inline ssize_t freebsd_pread(struct ntfs_device *dev, char *buf, + size_t count, s64 offset) +{ + return pread(DEV_FD(dev)->fd, buf, count, offset); +} + +/** + * freebsd_pwrite - Aligned write + */ +static inline ssize_t freebsd_pwrite(struct ntfs_device *dev, const char *buf, + size_t count, s64 offset) +{ + return pwrite(DEV_FD(dev)->fd, buf, count, offset); +} + +/** + * ntfs_device_freebsd_io_open - Open a device and lock it exclusively + * @dev: + * @flags: + * + * Description... + * + * Returns: + */ +static int ntfs_device_freebsd_io_open(struct ntfs_device *dev, int flags) +{ +#if 0 + struct flock flk; +#endif + struct stat sbuf; + int err; + + if (NDevOpen(dev)) { + errno = EBUSY; + return -1; + } + if (stat(dev->d_name, &sbuf)) { + ntfs_log_perror("Failed to access '%s'", dev->d_name); + return -1; + } + if (S_ISBLK(sbuf.st_mode) || S_ISCHR(sbuf.st_mode)) + NDevSetBlock(dev); + + dev->d_private = malloc(sizeof(freebsd_fd)); + if (!dev->d_private) + return -1; + DEV_FD(dev)->fd = -1; + DEV_FD(dev)->pos = 0; + DEV_FD(dev)->block_size = 0; + DEV_FD(dev)->media_size = 0; + + /* + * Open file for exclusive access if mounting r/w. + * Fuseblk takes care about block devices. + * FIXME: This is totally wrong. ntfsmount needs to just disabled the + * O_EXCL otherwise all other utilities are not mounting O_EXCL when + * they definitely should be doing it! + */ + if (!NDevBlock(dev) && (flags & O_RDWR) == O_RDWR) + flags |= O_EXCL; + DEV_FD(dev)->fd = open(dev->d_name, flags); + if (DEV_FD(dev)->fd == -1) { + err = errno; + goto err_out; + } + + if ((flags & O_RDWR) != O_RDWR) + NDevSetReadOnly(dev); + +#if 0 + memset(&flk, 0, sizeof(flk)); + if (NDevReadOnly(dev)) + flk.l_type = F_RDLCK; + else + flk.l_type = F_WRLCK; + flk.l_whence = SEEK_SET; + flk.l_start = flk.l_len = 0LL; + if (fcntl(DEV_FD(dev)->fd, F_SETLK, &flk)) { + err = errno; + ntfs_log_perror("Failed to %s lock '%s'", NDevReadOnly(dev) ? + "read" : "write", dev->d_name); + if (close(DEV_FD(dev)->fd)) + ntfs_log_perror("Failed to close '%s'", dev->d_name); + goto err_out; + } +#endif + + if (freebsd_get_size(dev) < 0) { + err = errno; + close(DEV_FD(dev)->fd); + goto err_out; + } + + NDevSetOpen(dev); + return 0; +err_out: + free(dev->d_private); + dev->d_private = NULL; + errno = err; + return -1; +} + +/** + * ntfs_device_freebsd_io_close - Close the device, releasing the lock + * @dev: + * + * Description... + * + * Returns: + */ +static int ntfs_device_freebsd_io_close(struct ntfs_device *dev) +{ +#if 0 + struct flock flk; +#endif + + if (!NDevOpen(dev)) { + errno = EBADF; + return -1; + } + if (NDevDirty(dev)) + fsync(DEV_FD(dev)->fd); + +#if 0 + /* Release exclusive (mandatory) lock on the whole device. */ + memset(&flk, 0, sizeof(flk)); + flk.l_type = F_UNLCK; + flk.l_whence = SEEK_SET; + flk.l_start = flk.l_len = 0LL; + if (fcntl(DEV_FD(dev)->fd, F_SETLK, &flk)) + ntfs_log_perror("ntfs_device_freebsd_io_close: Warning: Could not " + "unlock %s", dev->d_name); +#endif + + /* Close the file descriptor and clear our open flag. */ + if (close(DEV_FD(dev)->fd)) + return -1; + NDevClearOpen(dev); + free(dev->d_private); + dev->d_private = NULL; + return 0; +} + +/** + * ntfs_device_freebsd_io_seek - Seek to a place on the device + * @dev: + * @offset: + * @whence: + * + * Description... + * + * Returns: + */ +static s64 ntfs_device_freebsd_io_seek(struct ntfs_device *dev, s64 offset, + int whence) +{ + s64 abs_pos; + + ntfs_log_trace("seek offset = 0x%llx, whence = %d.\n", offset, whence); + switch (whence) { + case SEEK_SET: + abs_pos = offset; + break; + + case SEEK_CUR: + abs_pos = DEV_FD(dev)->pos + offset; + break; + + case SEEK_END: + abs_pos = DEV_FD(dev)->media_size + offset; + break; + + default: + ntfs_log_trace("Wrong mode %d.\n", whence); + errno = EINVAL; + return -1; + } + + if (abs_pos < 0 || abs_pos > DEV_FD(dev)->media_size) { + ntfs_log_trace("Seeking outsize seekable area.\n"); + errno = EINVAL; + return -1; + } + DEV_FD(dev)->pos = abs_pos; + return abs_pos; +} + +/** + * ntfs_device_freebsd_io_read - Read from the device, from the current location + * @dev: + * @buf: + * @count: + * + * Description... + * + * Returns: + */ +static s64 ntfs_device_freebsd_io_read(struct ntfs_device *dev, void *buf, + s64 count) +{ + s64 start, start_aligned; + s64 end, end_aligned; + size_t count_aligned; + char *buf_aligned; + ssize_t nr; + + /* short-circuit for regular files */ + start = DEV_FD(dev)->pos; + if (count > RAW_IO_MAX_SIZE) + count = RAW_IO_MAX_SIZE; + if (RAW_IO_ALIGNED(dev, start, count)) { + nr = freebsd_pread(dev, buf, count, start); + if (nr <= 0) + return nr; + + DEV_FD(dev)->pos += nr; + return nr; + } + + /* + * +- start_aligned +- end_aligned + * | | + * | +- start +- end | + * v v v v + * |----------|----------|----------| + * ^ ^ + * +----- count ------+ + * ^ ^ + * +-------- count_aligned ---------+ + */ + start_aligned = RAW_IO_ALIGN(dev, start); + end = start + count; + end_aligned = RAW_IO_ALIGN(dev, end) + + (RAW_IO_ALIGNED(dev, end, 0) ? 0 : DEV_FD(dev)->block_size); + count_aligned = end_aligned - start_aligned; + ntfs_log_trace( + "%s: count = 0x%llx/0x%x, start = 0x%llx/0x%llx, end = 0x%llx/0x%llx\n", + dev->d_name, count, count_aligned, + start, start_aligned, end, end_aligned); + + /* allocate buffer */ + buf_aligned = malloc(count_aligned); + if (buf_aligned == NULL) { + ntfs_log_trace("malloc(%d) failed\n", count_aligned); + return -1; + } + + /* read aligned data */ + nr = freebsd_pread(dev, buf_aligned, count_aligned, start_aligned); + if (nr == 0) + return 0; + if (nr < 0 || nr < start - start_aligned) { + free(buf_aligned); + return -1; + } + + /* copy out */ + memcpy(buf, buf_aligned + (start - start_aligned), count); + free(buf_aligned); + + nr -= start - start_aligned; + if (nr > count) + nr = count; + DEV_FD(dev)->pos += nr; + return nr; +} + +/** + * ntfs_device_freebsd_io_write - Write to the device, at the current location + * @dev: + * @buf: + * @count: + * + * Description... + * + * Returns: + */ +static s64 ntfs_device_freebsd_io_write(struct ntfs_device *dev, const void *buf, + s64 count) +{ + s64 start, start_aligned; + s64 end, end_aligned; + size_t count_aligned; + char *buf_aligned; + ssize_t nw; + + if (NDevReadOnly(dev)) { + errno = EROFS; + return -1; + } + NDevSetDirty(dev); + + /* short-circuit for regular files */ + start = DEV_FD(dev)->pos; + if (count > RAW_IO_MAX_SIZE) + count = RAW_IO_MAX_SIZE; + if (RAW_IO_ALIGNED(dev, start, count)) { + nw = freebsd_pwrite(dev, buf, count, start); + if (nw <= 0) + return nw; + + DEV_FD(dev)->pos += nw; + return nw; + } + + /* + * +- start_aligned +- end_aligned + * | | + * | +- start +- end | + * v v v v + * |----------|----------|----------| + * ^ ^ + * +----- count ------+ + * ^ ^ + * +-------- count_aligned ---------+ + */ + start_aligned = RAW_IO_ALIGN(dev, start); + end = start + count; + end_aligned = RAW_IO_ALIGN(dev, end) + + (RAW_IO_ALIGNED(dev, end, 0) ? 0 : DEV_FD(dev)->block_size); + count_aligned = end_aligned - start_aligned; + ntfs_log_trace( + "%s: count = 0x%llx/0x%x, start = 0x%llx/0x%llx, end = 0x%llx/0x%llx\n", + dev->d_name, count, count_aligned, + start, start_aligned, end, end_aligned); + + /* allocate buffer */ + buf_aligned = malloc(count_aligned); + if (buf_aligned == NULL) { + ntfs_log_trace("malloc(%d) failed\n", count_aligned); + return -1; + } + + /* read aligned lead-in */ + if (freebsd_pread(dev, buf_aligned, DEV_FD(dev)->block_size, start_aligned) != DEV_FD(dev)->block_size) { + ntfs_log_trace("read lead-in failed\n"); + free(buf_aligned); + return -1; + } + + /* read aligned lead-out */ + if (end != end_aligned && count_aligned > DEV_FD(dev)->block_size) { + if (freebsd_pread(dev, buf_aligned + count_aligned - DEV_FD(dev)->block_size, DEV_FD(dev)->block_size, end_aligned - DEV_FD(dev)->block_size) != DEV_FD(dev)->block_size) { + ntfs_log_trace("read lead-out failed\n"); + free(buf_aligned); + return -1; + } + } + + /* copy data to write */ + memcpy(buf_aligned + (start - start_aligned), buf, count); + + /* write aligned data */ + nw = freebsd_pwrite(dev, buf_aligned, count_aligned, start_aligned); + free(buf_aligned); + if (nw < 0 || nw < start - start_aligned) + return -1; + + nw -= start - start_aligned; + if (nw > count) + nw = count; + DEV_FD(dev)->pos += nw; + return nw; +} + +/** + * ntfs_device_freebsd_io_sync - Flush any buffered changes to the device + * @dev: + * + * Description... + * + * Returns: + */ +static int ntfs_device_freebsd_io_sync(struct ntfs_device *dev) +{ + if (!NDevReadOnly(dev)) { + int res = fsync(DEV_FD(dev)->fd); + if (!res) + NDevClearDirty(dev); + return res; + } + return 0; +} + +/** + * ntfs_device_freebsd_io_stat - Get information about the device + * @dev: + * @buf: + * + * Description... + * + * Returns: + */ +static int ntfs_device_freebsd_io_stat(struct ntfs_device *dev, struct stat *buf) +{ + return fstat(DEV_FD(dev)->fd, buf); +} + +/** + * ntfs_device_freebsd_io_ioctl - Perform an ioctl on the device + * @dev: + * @request: + * @argp: + * + * Description... + * + * Returns: + */ +static int ntfs_device_freebsd_io_ioctl(struct ntfs_device *dev, int request, + void *argp) +{ + return ioctl(DEV_FD(dev)->fd, request, argp); +} + +/** + * Device operations for working with unix style devices and files. + */ +struct ntfs_device_operations ntfs_device_unix_io_ops = { + .open = ntfs_device_freebsd_io_open, + .close = ntfs_device_freebsd_io_close, + .seek = ntfs_device_freebsd_io_seek, + .read = ntfs_device_freebsd_io_read, + .write = ntfs_device_freebsd_io_write, + .sync = ntfs_device_freebsd_io_sync, + .stat = ntfs_device_freebsd_io_stat, + .ioctl = ntfs_device_freebsd_io_ioctl, +};