| /** |
| * 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. |
| */ |
| #define _LARGEFILE64_SOURCE |
| |
| #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, struct blk_zone *blkzone) |
| { |
| struct one_zone_report { |
| struct blk_zone_report rep; |
| struct blk_zone zone; |
| } *rep; |
| int ret = -1; |
| |
| static_assert(sizeof(*rep) == sizeof(rep->rep) + sizeof(rep->zone), ""); |
| |
| rep = calloc(1, sizeof(*rep)); |
| if (!rep) { |
| ERR_MSG("No memory for report zones\n"); |
| return -ENOMEM; |
| } |
| |
| rep->rep = (struct blk_zone_report){ |
| .sector = sector, |
| .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; |
| } |
| |
| *blkzone = rep->zone; |
| 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), |
| struct blk_zone *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 |
| |