mirror of
https://git.code.sf.net/p/ntfs-3g/ntfs-3g.git
synced 2024-11-23 18:14:24 +08:00
b68c27ea74
Adjust string lengths to the worst case estimated by the compiler, even though they cannot be reached.
419 lines
10 KiB
C
419 lines
10 KiB
C
/**
|
|
* ioctl.c - Processing of ioctls
|
|
*
|
|
* This module is part of ntfs-3g library
|
|
*
|
|
* Copyright (c) 2014-2019 Jean-Pierre Andre
|
|
* Copyright (c) 2014 Red Hat, Inc.
|
|
*
|
|
* 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
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#ifdef HAVE_STDIO_H
|
|
#include <stdio.h>
|
|
#endif
|
|
#ifdef HAVE_INTTYPES_H
|
|
#include <inttypes.h>
|
|
#endif
|
|
#ifdef HAVE_STRING_H
|
|
#include <string.h>
|
|
#endif
|
|
#ifdef HAVE_ERRNO_H
|
|
#include <errno.h>
|
|
#endif
|
|
#ifdef HAVE_FCNTL_H
|
|
#include <fcntl.h>
|
|
#endif
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
#ifdef HAVE_STDLIB_H
|
|
#include <stdlib.h>
|
|
#endif
|
|
#ifdef HAVE_LIMITS_H
|
|
#include <limits.h>
|
|
#endif
|
|
#include <syslog.h>
|
|
#ifdef HAVE_SYS_TYPES_H
|
|
#include <sys/types.h>
|
|
#endif
|
|
#ifdef MAJOR_IN_MKDEV
|
|
#include <sys/mkdev.h>
|
|
#endif
|
|
#ifdef MAJOR_IN_SYSMACROS
|
|
#include <sys/sysmacros.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_SYS_STAT_H
|
|
#include <sys/stat.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_LINUX_FS_H
|
|
#include <linux/fs.h>
|
|
#endif
|
|
|
|
#include "compat.h"
|
|
#include "debug.h"
|
|
#include "bitmap.h"
|
|
#include "attrib.h"
|
|
#include "inode.h"
|
|
#include "layout.h"
|
|
#include "volume.h"
|
|
#include "index.h"
|
|
#include "logging.h"
|
|
#include "ntfstime.h"
|
|
#include "unistr.h"
|
|
#include "dir.h"
|
|
#include "security.h"
|
|
#include "ioctl.h"
|
|
#include "misc.h"
|
|
|
|
#if defined(FITRIM) && defined(BLKDISCARD)
|
|
|
|
/* Issue a TRIM request to the underlying device for the given clusters. */
|
|
static int fstrim_clusters(ntfs_volume *vol, LCN lcn, s64 length)
|
|
{
|
|
struct ntfs_device *dev = vol->dev;
|
|
uint64_t range[2];
|
|
|
|
ntfs_log_debug("fstrim_clusters: %lld length %lld\n",
|
|
(long long) lcn, (long long) length);
|
|
|
|
range[0] = lcn << vol->cluster_size_bits;
|
|
range[1] = length << vol->cluster_size_bits;
|
|
|
|
if (dev->d_ops->ioctl(dev, BLKDISCARD, range) == -1) {
|
|
ntfs_log_debug("fstrim_one_cluster: ioctl failed: %m\n");
|
|
return -errno;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int read_line(const char *path, char *line, size_t max_bytes)
|
|
{
|
|
FILE *fp;
|
|
|
|
fp = fopen(path, "r");
|
|
if (fp == NULL)
|
|
return -errno;
|
|
if (fgets(line, max_bytes, fp) == NULL) {
|
|
int ret = -EIO; /* fgets doesn't set errno */
|
|
fclose(fp);
|
|
return ret;
|
|
}
|
|
fclose (fp);
|
|
return 0;
|
|
}
|
|
|
|
static int read_u64(const char *path, u64 *n)
|
|
{
|
|
char line[64];
|
|
int ret;
|
|
|
|
ret = read_line(path, line, sizeof line);
|
|
if (ret)
|
|
return ret;
|
|
if (sscanf(line, "%" SCNu64, n) != 1)
|
|
return -EINVAL;
|
|
return 0;
|
|
}
|
|
|
|
/* Find discard limits for current backing device.
|
|
*/
|
|
static int fstrim_limits(ntfs_volume *vol,
|
|
u64 *discard_alignment,
|
|
u64 *discard_granularity,
|
|
u64 *discard_max_bytes)
|
|
{
|
|
struct stat statbuf;
|
|
char path1[40]; /* holds "/sys/dev/block/%d:%d" */
|
|
char path2[40 + sizeof(path1)]; /* less than 40 bytes more than path1 */
|
|
int ret;
|
|
|
|
/* Stat the backing device. Caller has ensured it is a block device. */
|
|
if (stat(vol->dev->d_name, &statbuf) == -1) {
|
|
ntfs_log_debug("fstrim_limits: could not stat %s\n",
|
|
vol->dev->d_name);
|
|
return -errno;
|
|
}
|
|
|
|
/* For whole devices,
|
|
* /sys/dev/block/MAJOR:MINOR/discard_alignment
|
|
* /sys/dev/block/MAJOR:MINOR/queue/discard_granularity
|
|
* /sys/dev/block/MAJOR:MINOR/queue/discard_max_bytes
|
|
* will exist.
|
|
* For partitions, we also need to check the parent device:
|
|
* /sys/dev/block/MAJOR:MINOR/../queue/discard_granularity
|
|
* /sys/dev/block/MAJOR:MINOR/../queue/discard_max_bytes
|
|
*/
|
|
snprintf(path1, sizeof path1, "/sys/dev/block/%d:%d",
|
|
major(statbuf.st_rdev), minor(statbuf.st_rdev));
|
|
|
|
snprintf(path2, sizeof path2, "%s/discard_alignment", path1);
|
|
ret = read_u64(path2, discard_alignment);
|
|
if (ret) {
|
|
if (ret != -ENOENT)
|
|
return ret;
|
|
else
|
|
/* We would expect this file to exist on all
|
|
* modern kernels. But for the sake of very
|
|
* old kernels:
|
|
*/
|
|
goto not_found;
|
|
}
|
|
|
|
snprintf(path2, sizeof path2, "%s/queue/discard_granularity", path1);
|
|
ret = read_u64(path2, discard_granularity);
|
|
if (ret) {
|
|
if (ret != -ENOENT)
|
|
return ret;
|
|
else {
|
|
snprintf(path2, sizeof path2,
|
|
"%s/../queue/discard_granularity", path1);
|
|
ret = read_u64(path2, discard_granularity);
|
|
if (ret) {
|
|
if (ret != -ENOENT)
|
|
return ret;
|
|
else
|
|
goto not_found;
|
|
}
|
|
}
|
|
}
|
|
|
|
snprintf(path2, sizeof path2, "%s/queue/discard_max_bytes", path1);
|
|
ret = read_u64(path2, discard_max_bytes);
|
|
if (ret) {
|
|
if (ret != -ENOENT)
|
|
return ret;
|
|
else {
|
|
snprintf(path2, sizeof path2,
|
|
"%s/../queue/discard_max_bytes", path1);
|
|
ret = read_u64(path2, discard_max_bytes);
|
|
if (ret) {
|
|
if (ret != -ENOENT)
|
|
return ret;
|
|
else
|
|
goto not_found;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
not_found:
|
|
/* If we reach here then we didn't find the device. This is
|
|
* not an error, but set discard_max_bytes = 0 to indicate
|
|
* that discard is not available.
|
|
*/
|
|
*discard_alignment = 0;
|
|
*discard_granularity = 0;
|
|
*discard_max_bytes = 0;
|
|
return 0;
|
|
}
|
|
|
|
static inline LCN align_up(ntfs_volume *vol, LCN lcn, u64 granularity)
|
|
{
|
|
u64 aligned;
|
|
|
|
aligned = (lcn << vol->cluster_size_bits) + granularity - 1;
|
|
aligned -= aligned % granularity;
|
|
return (aligned >> vol->cluster_size_bits);
|
|
}
|
|
|
|
static inline u64 align_down(ntfs_volume *vol, u64 count, u64 granularity)
|
|
{
|
|
u64 aligned;
|
|
|
|
aligned = count << vol->cluster_size_bits;
|
|
aligned -= aligned % granularity;
|
|
return (aligned >> vol->cluster_size_bits);
|
|
}
|
|
|
|
#define FSTRIM_BUFSIZ 4096
|
|
|
|
/* Trim the filesystem.
|
|
*
|
|
* Free blocks between 'start' and 'start+len-1' (both byte offsets)
|
|
* are found and TRIM requests are sent to the block device. 'minlen'
|
|
* is the minimum continguous free range to discard.
|
|
*/
|
|
static int fstrim(ntfs_volume *vol, void *data, u64 *trimmed)
|
|
{
|
|
struct fstrim_range *range = data;
|
|
u64 start = range->start;
|
|
u64 len = range->len;
|
|
u64 minlen = range->minlen;
|
|
u64 discard_alignment, discard_granularity, discard_max_bytes;
|
|
u8 *buf = NULL;
|
|
LCN start_buf;
|
|
int ret;
|
|
|
|
ntfs_log_debug("fstrim: start=%llu len=%llu minlen=%llu\n",
|
|
(unsigned long long) start,
|
|
(unsigned long long) len,
|
|
(unsigned long long) minlen);
|
|
|
|
*trimmed = 0;
|
|
|
|
/* Fail if user tries to use the fstrim -o/-l/-m options.
|
|
* XXX We could fix these limitations in future.
|
|
*/
|
|
if (start != 0 || len != (uint64_t)-1) {
|
|
ntfs_log_error("fstrim: setting start or length is not supported\n");
|
|
return -EINVAL;
|
|
}
|
|
if (minlen > vol->cluster_size) {
|
|
ntfs_log_error("fstrim: minlen > cluster size is not supported\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Only block devices are supported. It would be possible to
|
|
* support backing files (ie. without using loop) but the
|
|
* ioctls used to punch holes in files are completely
|
|
* different.
|
|
*/
|
|
if (!NDevBlock(vol->dev)) {
|
|
ntfs_log_error("fstrim: not supported for non-block-device\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
ret = fstrim_limits(vol, &discard_alignment,
|
|
&discard_granularity, &discard_max_bytes);
|
|
if (ret)
|
|
return ret;
|
|
if (discard_alignment != 0) {
|
|
ntfs_log_error("fstrim: backing device is not aligned for discards\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
if (discard_max_bytes == 0) {
|
|
ntfs_log_error("fstrim: backing device does not support discard (discard_max_bytes == 0)\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
/* Sync the device before doing anything. */
|
|
ret = ntfs_device_sync(vol->dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Read through the bitmap. */
|
|
buf = ntfs_malloc(FSTRIM_BUFSIZ);
|
|
if (buf == NULL)
|
|
return -errno;
|
|
for (start_buf = 0; start_buf < vol->nr_clusters;
|
|
start_buf += FSTRIM_BUFSIZ * 8) {
|
|
s64 count;
|
|
s64 br;
|
|
LCN end_buf, start_lcn;
|
|
|
|
/* start_buf is LCN of first cluster in the current buffer.
|
|
* end_buf is LCN of last cluster + 1 in the current buffer.
|
|
*/
|
|
end_buf = start_buf + FSTRIM_BUFSIZ*8;
|
|
if (end_buf > vol->nr_clusters)
|
|
end_buf = vol->nr_clusters;
|
|
count = (end_buf - start_buf) / 8;
|
|
|
|
br = ntfs_attr_pread(vol->lcnbmp_na, start_buf/8, count, buf);
|
|
if (br != count) {
|
|
if (br >= 0)
|
|
ret = -EIO;
|
|
else
|
|
ret = -errno;
|
|
goto free_out;
|
|
}
|
|
|
|
/* Trim the clusters in large as possible blocks, but
|
|
* not larger than discard_max_bytes, and compatible
|
|
* with the supported trim granularity.
|
|
*/
|
|
for (start_lcn = start_buf; start_lcn < end_buf; ++start_lcn) {
|
|
if (!ntfs_bit_get(buf, start_lcn-start_buf)) {
|
|
LCN end_lcn;
|
|
LCN aligned_lcn;
|
|
u64 aligned_count;
|
|
|
|
/* Cluster 'start_lcn' is not in use,
|
|
* find end of this run.
|
|
*/
|
|
end_lcn = start_lcn+1;
|
|
while (end_lcn < end_buf &&
|
|
(u64) (end_lcn-start_lcn) << vol->cluster_size_bits
|
|
< discard_max_bytes &&
|
|
!ntfs_bit_get(buf, end_lcn-start_buf))
|
|
end_lcn++;
|
|
aligned_lcn = align_up(vol, start_lcn,
|
|
discard_granularity);
|
|
if (aligned_lcn >= end_lcn)
|
|
aligned_count = 0;
|
|
else {
|
|
aligned_count =
|
|
align_down(vol,
|
|
end_lcn - aligned_lcn,
|
|
discard_granularity);
|
|
}
|
|
if (aligned_count) {
|
|
ret = fstrim_clusters(vol,
|
|
aligned_lcn, aligned_count);
|
|
if (ret)
|
|
goto free_out;
|
|
|
|
*trimmed += aligned_count
|
|
<< vol->cluster_size_bits;
|
|
}
|
|
start_lcn = end_lcn-1;
|
|
}
|
|
}
|
|
}
|
|
|
|
ret = 0;
|
|
free_out:
|
|
free(buf);
|
|
return ret;
|
|
}
|
|
|
|
#endif /* FITRIM && BLKDISCARD */
|
|
|
|
int ntfs_ioctl(ntfs_inode *ni, int cmd, void *arg __attribute__((unused)),
|
|
unsigned int flags __attribute__((unused)), void *data)
|
|
{
|
|
int ret = 0;
|
|
|
|
switch (cmd) {
|
|
#if defined(FITRIM) && defined(BLKDISCARD)
|
|
case FITRIM:
|
|
if (!ni || !data)
|
|
ret = -EINVAL;
|
|
else {
|
|
u64 trimmed;
|
|
struct fstrim_range *range = (struct fstrim_range*)data;
|
|
|
|
ret = fstrim(ni->vol, data, &trimmed);
|
|
range->len = trimmed;
|
|
}
|
|
break;
|
|
#else
|
|
#warning Trimming not supported : FITRIM or BLKDISCARD not defined
|
|
#endif
|
|
default :
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
return (ret);
|
|
}
|