mirror of
https://gitee.com/openharmony/third_party_f2fs-tools
synced 2024-11-23 10:10:00 +00:00
72de6cc108
Signed-off-by: Jaegeuk Kim <jaegeuk@kernel.org>
593 lines
13 KiB
C
593 lines
13 KiB
C
/**
|
|
* libf2fs_zoned.c
|
|
*
|
|
* Copyright (c) 2016 Western Digital Corporation.
|
|
* Written by: Damien Le Moal <damien.lemoal@wdc.com>
|
|
*
|
|
* Dual licensed under the GPL or LGPL version 2 licenses.
|
|
*/
|
|
#ifndef _LARGEFILE64_SOURCE
|
|
#define _LARGEFILE64_SOURCE
|
|
#endif
|
|
|
|
#include <f2fs_fs.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <sys/stat.h>
|
|
#ifdef HAVE_SYS_SYSMACROS_H
|
|
#include <sys/sysmacros.h>
|
|
#endif
|
|
#ifdef HAVE_LINUX_LIMITS_H
|
|
#include <linux/limits.h>
|
|
#endif
|
|
#ifdef HAVE_SYS_IOCTL_H
|
|
#include <sys/ioctl.h>
|
|
#endif
|
|
#include <libgen.h>
|
|
|
|
#ifdef HAVE_LINUX_BLKZONED_H
|
|
|
|
int get_sysfs_path(struct device_info *dev, const char *attr,
|
|
char *buf, size_t buflen)
|
|
{
|
|
struct stat statbuf;
|
|
char str[PATH_MAX];
|
|
char sysfs_path[PATH_MAX];
|
|
ssize_t len;
|
|
char *delim;
|
|
int ret;
|
|
|
|
if (stat(dev->path, &statbuf) < 0)
|
|
return -1;
|
|
|
|
snprintf(str, sizeof(str), "/sys/dev/block/%d:%d",
|
|
major(statbuf.st_rdev), minor(statbuf.st_rdev));
|
|
len = readlink(str, buf, buflen - 1);
|
|
if (len < 0)
|
|
return -1;
|
|
buf[len] = '\0';
|
|
|
|
ret = snprintf(sysfs_path, sizeof(sysfs_path),
|
|
"/sys/dev/block/%s", buf);
|
|
if (ret >= sizeof(sysfs_path))
|
|
return -1;
|
|
|
|
/* Test if the device is a partition */
|
|
ret = snprintf(str, sizeof(str), "%s/partition", sysfs_path);
|
|
if (ret >= sizeof(str))
|
|
return -1;
|
|
ret = stat(str, &statbuf);
|
|
if (ret) {
|
|
if (errno == ENOENT) {
|
|
/* Not a partition */
|
|
goto out;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* The device is a partition: remove the device name from the
|
|
* attribute file path to obtain the sysfs path of the holder device.
|
|
* e.g.: /sys/dev/block/.../sda/sda1 -> /sys/dev/block/.../sda
|
|
*/
|
|
delim = strrchr(sysfs_path, '/');
|
|
if (!delim)
|
|
return -1;
|
|
*delim = '\0';
|
|
|
|
out:
|
|
ret = snprintf(buf, buflen, "%s/%s", sysfs_path, attr);
|
|
if (ret >= buflen)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int f2fs_get_zoned_model(int i)
|
|
{
|
|
struct device_info *dev = c.devices + i;
|
|
char str[PATH_MAX];
|
|
FILE *file;
|
|
int res;
|
|
|
|
/* Check that this is a zoned block device */
|
|
res = get_sysfs_path(dev, "queue/zoned", str, sizeof(str));
|
|
if (res != 0) {
|
|
MSG(0, "\tInfo: can't find /sys, assuming normal block device\n");
|
|
dev->zoned_model = F2FS_ZONED_NONE;
|
|
return 0;
|
|
}
|
|
|
|
file = fopen(str, "r");
|
|
if (!file) {
|
|
/*
|
|
* The kernel does not support zoned block devices, but we have
|
|
* a block device file. This means that if the zoned file is
|
|
* not found, then the device is not zoned or is zoned but can
|
|
* be randomly written (i.e. host-aware zoned model).
|
|
* Treat the device as a regular block device. Otherwise, signal
|
|
* the failure to verify the disk zone model.
|
|
*/
|
|
if (errno == ENOENT) {
|
|
dev->zoned_model = F2FS_ZONED_NONE;
|
|
return 0;
|
|
}
|
|
MSG(0, "\tError: Failed to check the device zoned model\n");
|
|
return -1;
|
|
}
|
|
|
|
memset(str, 0, sizeof(str));
|
|
res = fscanf(file, "%s", str);
|
|
fclose(file);
|
|
|
|
if (res != 1) {
|
|
MSG(0, "\tError: Failed to parse the device zoned model\n");
|
|
return -1;
|
|
}
|
|
|
|
if (strcmp(str, "none") == 0) {
|
|
/* Regular block device */
|
|
dev->zoned_model = F2FS_ZONED_NONE;
|
|
} else if (strcmp(str, "host-aware") == 0) {
|
|
/* Host-aware zoned block device: can be randomly written */
|
|
dev->zoned_model = F2FS_ZONED_HA;
|
|
} else if (strcmp(str, "host-managed") == 0) {
|
|
/* Host-managed zoned block device: sequential writes needed */
|
|
dev->zoned_model = F2FS_ZONED_HM;
|
|
} else {
|
|
MSG(0, "\tError: Unsupported device zoned model\n");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint32_t f2fs_get_zone_chunk_sectors(struct device_info *dev)
|
|
{
|
|
uint32_t sectors;
|
|
char str[PATH_MAX];
|
|
FILE *file;
|
|
int res;
|
|
|
|
res = get_sysfs_path(dev, "queue/chunk_sectors", str, sizeof(str));
|
|
if (res != 0) {
|
|
MSG(0, "\tError: Failed to get device sysfs attribute path\n");
|
|
return 0;
|
|
}
|
|
|
|
file = fopen(str, "r");
|
|
if (!file)
|
|
return 0;
|
|
|
|
memset(str, 0, sizeof(str));
|
|
res = fscanf(file, "%s", str);
|
|
fclose(file);
|
|
|
|
if (res != 1)
|
|
return 0;
|
|
|
|
sectors = atoi(str);
|
|
|
|
return sectors;
|
|
}
|
|
|
|
int f2fs_get_zone_blocks(int i)
|
|
{
|
|
struct device_info *dev = c.devices + i;
|
|
uint64_t sectors;
|
|
|
|
/* Get zone size */
|
|
dev->zone_blocks = 0;
|
|
|
|
sectors = f2fs_get_zone_chunk_sectors(dev);
|
|
if (!sectors)
|
|
return -1;
|
|
|
|
dev->zone_size = sectors << SECTOR_SHIFT;
|
|
dev->zone_blocks = sectors >> (F2FS_BLKSIZE_BITS - SECTOR_SHIFT);
|
|
sectors = dev->zone_size / c.sector_size;
|
|
|
|
/*
|
|
* Total number of zones: there may
|
|
* be a last smaller runt zone.
|
|
*/
|
|
dev->nr_zones = dev->total_sectors / sectors;
|
|
if (dev->total_sectors % sectors)
|
|
dev->nr_zones++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int f2fs_report_zone(int i, uint64_t sector, void *blkzone)
|
|
{
|
|
struct blk_zone *blkz = (struct blk_zone *)blkzone;
|
|
struct blk_zone_report *rep;
|
|
int ret = -1;
|
|
|
|
rep = malloc(sizeof(struct blk_zone_report) + sizeof(struct blk_zone));
|
|
if (!rep) {
|
|
ERR_MSG("No memory for report zones\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
rep->sector = sector;
|
|
rep->nr_zones = 1;
|
|
ret = ioctl(c.devices[i].fd, BLKREPORTZONE, rep);
|
|
if (ret != 0) {
|
|
ret = -errno;
|
|
ERR_MSG("ioctl BLKREPORTZONE failed: errno=%d\n", errno);
|
|
goto out;
|
|
}
|
|
|
|
*blkz = *(struct blk_zone *)(rep + 1);
|
|
out:
|
|
free(rep);
|
|
return ret;
|
|
}
|
|
|
|
#define F2FS_REPORT_ZONES_BUFSZ 524288
|
|
|
|
int f2fs_report_zones(int j, report_zones_cb_t *report_zones_cb, void *opaque)
|
|
{
|
|
struct device_info *dev = c.devices + j;
|
|
struct blk_zone_report *rep;
|
|
struct blk_zone *blkz;
|
|
unsigned int i, n = 0;
|
|
uint64_t total_sectors = (dev->total_sectors * c.sector_size)
|
|
>> SECTOR_SHIFT;
|
|
uint64_t sector = 0;
|
|
int ret = -1;
|
|
|
|
rep = malloc(F2FS_REPORT_ZONES_BUFSZ);
|
|
if (!rep) {
|
|
ERR_MSG("No memory for report zones\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
while (sector < total_sectors) {
|
|
|
|
/* Get zone info */
|
|
rep->sector = sector;
|
|
rep->nr_zones = (F2FS_REPORT_ZONES_BUFSZ - sizeof(struct blk_zone_report))
|
|
/ sizeof(struct blk_zone);
|
|
|
|
ret = ioctl(dev->fd, BLKREPORTZONE, rep);
|
|
if (ret != 0) {
|
|
ret = -errno;
|
|
ERR_MSG("ioctl BLKREPORTZONE failed: errno=%d\n",
|
|
errno);
|
|
goto out;
|
|
}
|
|
|
|
if (!rep->nr_zones) {
|
|
ret = -EIO;
|
|
ERR_MSG("Unexpected ioctl BLKREPORTZONE result\n");
|
|
goto out;
|
|
}
|
|
|
|
blkz = (struct blk_zone *)(rep + 1);
|
|
for (i = 0; i < rep->nr_zones; i++) {
|
|
ret = report_zones_cb(n, blkz, opaque);
|
|
if (ret)
|
|
goto out;
|
|
sector = blk_zone_sector(blkz) + blk_zone_length(blkz);
|
|
n++;
|
|
blkz++;
|
|
}
|
|
}
|
|
out:
|
|
free(rep);
|
|
return ret;
|
|
}
|
|
|
|
int f2fs_check_zones(int j)
|
|
{
|
|
struct device_info *dev = c.devices + j;
|
|
struct blk_zone_report *rep;
|
|
struct blk_zone *blkz;
|
|
unsigned int i, n = 0;
|
|
uint64_t total_sectors;
|
|
uint64_t sector;
|
|
int last_is_conv = 1;
|
|
int ret = -1;
|
|
|
|
rep = malloc(F2FS_REPORT_ZONES_BUFSZ);
|
|
if (!rep) {
|
|
ERR_MSG("No memory for report zones\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
dev->zone_cap_blocks = malloc(dev->nr_zones * sizeof(size_t));
|
|
if (!dev->zone_cap_blocks) {
|
|
ERR_MSG("No memory for zone capacity list.\n");
|
|
return -ENOMEM;
|
|
}
|
|
memset(dev->zone_cap_blocks, 0, (dev->nr_zones * sizeof(size_t)));
|
|
|
|
dev->nr_rnd_zones = 0;
|
|
sector = 0;
|
|
total_sectors = (dev->total_sectors * c.sector_size) >> 9;
|
|
|
|
while (sector < total_sectors) {
|
|
|
|
/* Get zone info */
|
|
memset(rep, 0, F2FS_REPORT_ZONES_BUFSZ);
|
|
rep->sector = sector;
|
|
rep->nr_zones = (F2FS_REPORT_ZONES_BUFSZ - sizeof(struct blk_zone_report))
|
|
/ sizeof(struct blk_zone);
|
|
|
|
ret = ioctl(dev->fd, BLKREPORTZONE, rep);
|
|
if (ret != 0) {
|
|
ret = -errno;
|
|
ERR_MSG("ioctl BLKREPORTZONE failed\n");
|
|
goto out;
|
|
}
|
|
|
|
if (!rep->nr_zones)
|
|
break;
|
|
|
|
blkz = (struct blk_zone *)(rep + 1);
|
|
for (i = 0; i < rep->nr_zones && sector < total_sectors; i++) {
|
|
|
|
if (blk_zone_cond(blkz) == BLK_ZONE_COND_READONLY ||
|
|
blk_zone_cond(blkz) == BLK_ZONE_COND_OFFLINE)
|
|
last_is_conv = 0;
|
|
if (blk_zone_conv(blkz) ||
|
|
blk_zone_seq_pref(blkz)) {
|
|
if (last_is_conv)
|
|
dev->nr_rnd_zones++;
|
|
} else {
|
|
last_is_conv = 0;
|
|
}
|
|
|
|
if (blk_zone_conv(blkz)) {
|
|
DBG(2,
|
|
"Zone %05u: Conventional, cond 0x%x (%s), sector %llu, %llu sectors\n",
|
|
n,
|
|
blk_zone_cond(blkz),
|
|
blk_zone_cond_str(blkz),
|
|
blk_zone_sector(blkz),
|
|
blk_zone_length(blkz));
|
|
dev->zone_cap_blocks[n] =
|
|
blk_zone_length(blkz) >>
|
|
(F2FS_BLKSIZE_BITS - SECTOR_SHIFT);
|
|
} else {
|
|
DBG(2,
|
|
"Zone %05u: type 0x%x (%s), cond 0x%x (%s),"
|
|
" need_reset %d, non_seq %d, sector %llu,"
|
|
" %llu sectors, capacity %llu,"
|
|
" wp sector %llu\n",
|
|
n,
|
|
blk_zone_type(blkz),
|
|
blk_zone_type_str(blkz),
|
|
blk_zone_cond(blkz),
|
|
blk_zone_cond_str(blkz),
|
|
blk_zone_need_reset(blkz),
|
|
blk_zone_non_seq(blkz),
|
|
blk_zone_sector(blkz),
|
|
blk_zone_length(blkz),
|
|
blk_zone_capacity(blkz, rep->flags),
|
|
blk_zone_wp_sector(blkz));
|
|
dev->zone_cap_blocks[n] =
|
|
blk_zone_capacity(blkz, rep->flags) >>
|
|
(F2FS_BLKSIZE_BITS - SECTOR_SHIFT);
|
|
}
|
|
|
|
sector = blk_zone_sector(blkz) + blk_zone_length(blkz);
|
|
n++;
|
|
blkz++;
|
|
}
|
|
}
|
|
|
|
if (sector != total_sectors) {
|
|
ERR_MSG("Invalid zones: last sector reported is %llu, expected %llu\n",
|
|
(unsigned long long)(sector << 9) / c.sector_size,
|
|
(unsigned long long)dev->total_sectors);
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (n != dev->nr_zones) {
|
|
ERR_MSG("Inconsistent number of zones: expected %u zones, got %u\n",
|
|
dev->nr_zones, n);
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* For a multi-device volume, fixed position metadata blocks are
|
|
* stored * only on the first device of the volume. Checking for the
|
|
* presence of * conventional zones (randomly writeabl zones) for
|
|
* storing these blocks * on a host-managed device is thus needed only
|
|
* for the device index 0.
|
|
*/
|
|
if (j == 0 && dev->zoned_model == F2FS_ZONED_HM &&
|
|
!dev->nr_rnd_zones) {
|
|
ERR_MSG("No conventional zone for super block\n");
|
|
ret = -1;
|
|
}
|
|
out:
|
|
free(rep);
|
|
return ret;
|
|
}
|
|
|
|
int f2fs_reset_zone(int i, void *blkzone)
|
|
{
|
|
struct blk_zone *blkz = (struct blk_zone *)blkzone;
|
|
struct device_info *dev = c.devices + i;
|
|
struct blk_zone_range range;
|
|
int ret;
|
|
|
|
if (!blk_zone_seq(blkz) || blk_zone_empty(blkz))
|
|
return 0;
|
|
|
|
/* Non empty sequential zone: reset */
|
|
range.sector = blk_zone_sector(blkz);
|
|
range.nr_sectors = blk_zone_length(blkz);
|
|
ret = ioctl(dev->fd, BLKRESETZONE, &range);
|
|
if (ret != 0) {
|
|
ret = -errno;
|
|
ERR_MSG("ioctl BLKRESETZONE failed: errno=%d\n", errno);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int f2fs_reset_zones(int j)
|
|
{
|
|
struct device_info *dev = c.devices + j;
|
|
struct blk_zone_report *rep;
|
|
struct blk_zone *blkz;
|
|
struct blk_zone_range range;
|
|
uint64_t total_sectors;
|
|
uint64_t sector;
|
|
unsigned int i;
|
|
int ret = -1;
|
|
|
|
rep = malloc(F2FS_REPORT_ZONES_BUFSZ);
|
|
if (!rep) {
|
|
ERR_MSG("No memory for report zones\n");
|
|
return -1;
|
|
}
|
|
|
|
sector = 0;
|
|
total_sectors = (dev->total_sectors * c.sector_size) >> 9;
|
|
while (sector < total_sectors) {
|
|
|
|
/* Get zone info */
|
|
memset(rep, 0, F2FS_REPORT_ZONES_BUFSZ);
|
|
rep->sector = sector;
|
|
rep->nr_zones = (F2FS_REPORT_ZONES_BUFSZ - sizeof(struct blk_zone_report))
|
|
/ sizeof(struct blk_zone);
|
|
|
|
ret = ioctl(dev->fd, BLKREPORTZONE, rep);
|
|
if (ret != 0) {
|
|
ret = -errno;
|
|
ERR_MSG("ioctl BLKREPORTZONES failed\n");
|
|
goto out;
|
|
}
|
|
|
|
if (!rep->nr_zones)
|
|
break;
|
|
|
|
blkz = (struct blk_zone *)(rep + 1);
|
|
for (i = 0; i < rep->nr_zones && sector < total_sectors; i++) {
|
|
if (blk_zone_seq(blkz) &&
|
|
!blk_zone_empty(blkz)) {
|
|
/* Non empty sequential zone: reset */
|
|
range.sector = blk_zone_sector(blkz);
|
|
range.nr_sectors = blk_zone_length(blkz);
|
|
ret = ioctl(dev->fd, BLKRESETZONE, &range);
|
|
if (ret != 0) {
|
|
ret = -errno;
|
|
ERR_MSG("ioctl BLKRESETZONE failed\n");
|
|
goto out;
|
|
}
|
|
}
|
|
sector = blk_zone_sector(blkz) + blk_zone_length(blkz);
|
|
blkz++;
|
|
}
|
|
}
|
|
out:
|
|
free(rep);
|
|
if (!ret)
|
|
MSG(0, "Info: Discarded %"PRIu64" MB\n", (sector << 9) >> 20);
|
|
return ret;
|
|
}
|
|
|
|
uint32_t f2fs_get_usable_segments(struct f2fs_super_block *sb)
|
|
{
|
|
#ifdef HAVE_BLK_ZONE_REP_V2
|
|
int i, j;
|
|
uint32_t usable_segs = 0, zone_segs;
|
|
|
|
if (c.func == RESIZE)
|
|
return get_sb(segment_count_main);
|
|
|
|
for (i = 0; i < c.ndevs; i++) {
|
|
if (c.devices[i].zoned_model != F2FS_ZONED_HM) {
|
|
usable_segs += c.devices[i].total_segments;
|
|
continue;
|
|
}
|
|
for (j = 0; j < c.devices[i].nr_zones; j++) {
|
|
zone_segs = c.devices[i].zone_cap_blocks[j] >>
|
|
get_sb(log_blocks_per_seg);
|
|
if (c.devices[i].zone_cap_blocks[j] %
|
|
DEFAULT_BLOCKS_PER_SEGMENT)
|
|
usable_segs += zone_segs + 1;
|
|
else
|
|
usable_segs += zone_segs;
|
|
}
|
|
}
|
|
usable_segs -= (get_sb(main_blkaddr) - get_sb(segment0_blkaddr)) >>
|
|
get_sb(log_blocks_per_seg);
|
|
return usable_segs;
|
|
#endif
|
|
return get_sb(segment_count_main);
|
|
}
|
|
|
|
#else
|
|
|
|
int f2fs_report_zone(int i, uint64_t UNUSED(sector), void *UNUSED(blkzone))
|
|
{
|
|
ERR_MSG("%d: Unsupported zoned block device\n", i);
|
|
return -1;
|
|
}
|
|
|
|
int f2fs_report_zones(int i, report_zones_cb_t *UNUSED(report_zones_cb),
|
|
void *UNUSED(opaque))
|
|
{
|
|
ERR_MSG("%d: Unsupported zoned block device\n", i);
|
|
return -1;
|
|
}
|
|
|
|
int f2fs_get_zoned_model(int i)
|
|
{
|
|
struct device_info *dev = c.devices + i;
|
|
|
|
c.zoned_mode = 0;
|
|
dev->zoned_model = F2FS_ZONED_NONE;
|
|
return 0;
|
|
}
|
|
|
|
int f2fs_get_zone_blocks(int i)
|
|
{
|
|
struct device_info *dev = c.devices + i;
|
|
|
|
c.zoned_mode = 0;
|
|
dev->nr_zones = 0;
|
|
dev->zone_blocks = 0;
|
|
dev->zoned_model = F2FS_ZONED_NONE;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int f2fs_check_zones(int i)
|
|
{
|
|
ERR_MSG("%d: Unsupported zoned block device\n", i);
|
|
return -1;
|
|
}
|
|
|
|
int f2fs_reset_zone(int i, void *UNUSED(blkzone))
|
|
{
|
|
ERR_MSG("%d: Unsupported zoned block device\n", i);
|
|
return -1;
|
|
}
|
|
|
|
int f2fs_reset_zones(int i)
|
|
{
|
|
ERR_MSG("%d: Unsupported zoned block device\n", i);
|
|
return -1;
|
|
}
|
|
|
|
uint32_t f2fs_get_usable_segments(struct f2fs_super_block *sb)
|
|
{
|
|
return get_sb(segment_count_main);
|
|
}
|
|
#endif
|
|
|