/** * freebsd_io.c - FreeBSD disk io functions. Part of the Linux-NTFS project. * * FreeBSD 5.0+ does not have block devices and requires read/writes from/to * character devices to be sector aligned. * * Copyright (c) 2006 Max Khon * Copyright (c) 2006 Anton Altaparmakov * Copyright (c) 2006 Yura Pakhuchiy * * 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; 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, };