1/**
2 * libf2fs_zoned.c
3 *
4 * Copyright (c) 2016 Western Digital Corporation.
5 * Written by: Damien Le Moal <damien.lemoal@wdc.com>
6 *
7 * Dual licensed under the GPL or LGPL version 2 licenses.
8 */
9#ifndef _LARGEFILE64_SOURCE
10#define _LARGEFILE64_SOURCE
11#endif
12
13#include <f2fs_fs.h>
14#include <stdio.h>
15#include <stdlib.h>
16#include <string.h>
17#include <errno.h>
18#include <unistd.h>
19#include <fcntl.h>
20#include <sys/stat.h>
21#ifdef HAVE_SYS_SYSMACROS_H
22#include <sys/sysmacros.h>
23#endif
24#ifdef HAVE_LINUX_LIMITS_H
25#include <linux/limits.h>
26#endif
27#ifdef HAVE_SYS_IOCTL_H
28#include <sys/ioctl.h>
29#endif
30#include <libgen.h>
31
32#ifdef HAVE_LINUX_BLKZONED_H
33
34int get_sysfs_path(struct device_info *dev, const char *attr,
35		   char *buf, size_t buflen)
36{
37	struct stat statbuf;
38	char str[PATH_MAX];
39	char sysfs_path[PATH_MAX];
40	ssize_t len;
41	char *delim;
42	int ret;
43
44	if (stat(dev->path, &statbuf) < 0)
45		return -1;
46
47	snprintf(str, sizeof(str), "/sys/dev/block/%d:%d",
48		 major(statbuf.st_rdev), minor(statbuf.st_rdev));
49	len = readlink(str, buf, buflen - 1);
50	if (len < 0)
51		return -1;
52	buf[len] = '\0';
53
54	ret = snprintf(sysfs_path, sizeof(sysfs_path),
55		       "/sys/dev/block/%s", buf);
56	if (ret >= sizeof(sysfs_path))
57		return -1;
58
59	/* Test if the device is a partition */
60	ret = snprintf(str, sizeof(str), "%s/partition", sysfs_path);
61	if (ret >= sizeof(str))
62		return -1;
63	ret = stat(str, &statbuf);
64	if (ret) {
65		if (errno == ENOENT) {
66			/* Not a partition */
67			goto out;
68		}
69		return -1;
70	}
71
72	/*
73	 * The device is a partition: remove the device name from the
74	 * attribute file path to obtain the sysfs path of the holder device.
75	 *   e.g.:  /sys/dev/block/.../sda/sda1 -> /sys/dev/block/.../sda
76	 */
77	delim = strrchr(sysfs_path, '/');
78	if (!delim)
79		return -1;
80	*delim = '\0';
81
82out:
83	ret = snprintf(buf, buflen, "%s/%s", sysfs_path, attr);
84	if (ret >= buflen)
85		return -1;
86
87	return 0;
88}
89
90int f2fs_get_zoned_model(int i)
91{
92	struct device_info *dev = c.devices + i;
93	char str[PATH_MAX];
94	FILE *file;
95	int res;
96
97	/* Check that this is a zoned block device */
98	res = get_sysfs_path(dev, "queue/zoned", str, sizeof(str));
99	if (res != 0) {
100		MSG(0, "\tInfo: can't find /sys, assuming normal block device\n");
101		dev->zoned_model = F2FS_ZONED_NONE;
102		return 0;
103	}
104
105	file = fopen(str, "r");
106	if (!file) {
107		/*
108		 * The kernel does not support zoned block devices, but we have
109		 * a block device file. This means that if the zoned file is
110		 * not found, then the device is not zoned or is zoned but can
111		 * be randomly written (i.e. host-aware zoned model).
112		 * Treat the device as a regular block device. Otherwise, signal
113		 * the failure to verify the disk zone model.
114		 */
115		if (errno == ENOENT) {
116			dev->zoned_model = F2FS_ZONED_NONE;
117			return 0;
118		}
119		MSG(0, "\tError: Failed to check the device zoned model\n");
120		return -1;
121	}
122
123	memset(str, 0, sizeof(str));
124	res = fscanf(file, "%s", str);
125	fclose(file);
126
127	if (res != 1) {
128		MSG(0, "\tError: Failed to parse the device zoned model\n");
129		return -1;
130	}
131
132	if (strcmp(str, "none") == 0) {
133		/* Regular block device */
134		dev->zoned_model = F2FS_ZONED_NONE;
135	} else if (strcmp(str, "host-aware") == 0) {
136		/* Host-aware zoned block device: can be randomly written */
137		dev->zoned_model = F2FS_ZONED_HA;
138	} else if (strcmp(str, "host-managed") == 0) {
139		/* Host-managed zoned block device: sequential writes needed */
140		dev->zoned_model = F2FS_ZONED_HM;
141	} else {
142		MSG(0, "\tError: Unsupported device zoned model\n");
143		return -1;
144	}
145
146	return 0;
147}
148
149uint32_t f2fs_get_zone_chunk_sectors(struct device_info *dev)
150{
151	uint32_t sectors;
152	char str[PATH_MAX];
153	FILE *file;
154	int res;
155
156	res = get_sysfs_path(dev, "queue/chunk_sectors", str, sizeof(str));
157	if (res != 0) {
158		MSG(0, "\tError: Failed to get device sysfs attribute path\n");
159		return 0;
160	}
161
162	file = fopen(str, "r");
163	if (!file)
164		return 0;
165
166	memset(str, 0, sizeof(str));
167	res = fscanf(file, "%s", str);
168	fclose(file);
169
170	if (res != 1)
171		return 0;
172
173	sectors = atoi(str);
174
175	return sectors;
176}
177
178int f2fs_get_zone_blocks(int i)
179{
180	struct device_info *dev = c.devices + i;
181	uint64_t sectors;
182
183	/* Get zone size */
184	dev->zone_blocks = 0;
185
186	sectors = f2fs_get_zone_chunk_sectors(dev);
187	if (!sectors)
188		return -1;
189
190	dev->zone_size = sectors << SECTOR_SHIFT;
191	dev->zone_blocks = sectors >> (F2FS_BLKSIZE_BITS - SECTOR_SHIFT);
192	sectors = dev->zone_size / c.sector_size;
193
194	/*
195	 * Total number of zones: there may
196	 * be a last smaller runt zone.
197	 */
198	dev->nr_zones = dev->total_sectors / sectors;
199	if (dev->total_sectors % sectors)
200		dev->nr_zones++;
201
202	return 0;
203}
204
205int f2fs_report_zone(int i, uint64_t sector, struct blk_zone *blkzone)
206{
207	struct one_zone_report {
208		struct blk_zone_report	rep;
209		struct blk_zone		zone;
210	} *rep;
211	int ret = -1;
212
213	static_assert(sizeof(*rep) == sizeof(rep->rep) + sizeof(rep->zone), "");
214
215	rep = calloc(1, sizeof(*rep));
216	if (!rep) {
217		ERR_MSG("No memory for report zones\n");
218		return -ENOMEM;
219	}
220
221	rep->rep = (struct blk_zone_report){
222		.sector = sector,
223		.nr_zones = 1,
224	};
225	ret = ioctl(c.devices[i].fd, BLKREPORTZONE, rep);
226	if (ret != 0) {
227		ret = -errno;
228		ERR_MSG("ioctl BLKREPORTZONE failed: errno=%d\n", errno);
229		goto out;
230	}
231
232	*blkzone = rep->zone;
233out:
234	free(rep);
235	return ret;
236}
237
238#define F2FS_REPORT_ZONES_BUFSZ	524288
239
240int f2fs_report_zones(int j, report_zones_cb_t *report_zones_cb, void *opaque)
241{
242	struct device_info *dev = c.devices + j;
243	struct blk_zone_report *rep;
244	struct blk_zone *blkz;
245	unsigned int i, n = 0;
246	uint64_t total_sectors = (dev->total_sectors * c.sector_size)
247		>> SECTOR_SHIFT;
248	uint64_t sector = 0;
249	int ret = -1;
250
251	rep = malloc(F2FS_REPORT_ZONES_BUFSZ);
252	if (!rep) {
253		ERR_MSG("No memory for report zones\n");
254		return -ENOMEM;
255	}
256
257	while (sector < total_sectors) {
258
259		/* Get zone info */
260		rep->sector = sector;
261		rep->nr_zones = (F2FS_REPORT_ZONES_BUFSZ - sizeof(struct blk_zone_report))
262			/ sizeof(struct blk_zone);
263
264		ret = ioctl(dev->fd, BLKREPORTZONE, rep);
265		if (ret != 0) {
266			ret = -errno;
267			ERR_MSG("ioctl BLKREPORTZONE failed: errno=%d\n",
268				errno);
269			goto out;
270		}
271
272		if (!rep->nr_zones) {
273			ret = -EIO;
274			ERR_MSG("Unexpected ioctl BLKREPORTZONE result\n");
275			goto out;
276		}
277
278		blkz = (struct blk_zone *)(rep + 1);
279		for (i = 0; i < rep->nr_zones; i++) {
280			ret = report_zones_cb(n, blkz, opaque);
281			if (ret)
282				goto out;
283			sector = blk_zone_sector(blkz) + blk_zone_length(blkz);
284			n++;
285			blkz++;
286		}
287	}
288out:
289	free(rep);
290	return ret;
291}
292
293int f2fs_check_zones(int j)
294{
295	struct device_info *dev = c.devices + j;
296	struct blk_zone_report *rep;
297	struct blk_zone *blkz;
298	unsigned int i, n = 0;
299	uint64_t total_sectors;
300	uint64_t sector;
301	int last_is_conv = 1;
302	int ret = -1;
303
304	rep = malloc(F2FS_REPORT_ZONES_BUFSZ);
305	if (!rep) {
306		ERR_MSG("No memory for report zones\n");
307		return -ENOMEM;
308	}
309
310	dev->zone_cap_blocks = malloc(dev->nr_zones * sizeof(size_t));
311	if (!dev->zone_cap_blocks) {
312		ERR_MSG("No memory for zone capacity list.\n");
313		return -ENOMEM;
314	}
315	memset(dev->zone_cap_blocks, 0, (dev->nr_zones * sizeof(size_t)));
316
317	dev->nr_rnd_zones = 0;
318	sector = 0;
319	total_sectors = (dev->total_sectors * c.sector_size) >> 9;
320
321	while (sector < total_sectors) {
322
323		/* Get zone info */
324		memset(rep, 0, F2FS_REPORT_ZONES_BUFSZ);
325		rep->sector = sector;
326		rep->nr_zones = (F2FS_REPORT_ZONES_BUFSZ - sizeof(struct blk_zone_report))
327			/ sizeof(struct blk_zone);
328
329		ret = ioctl(dev->fd, BLKREPORTZONE, rep);
330		if (ret != 0) {
331			ret = -errno;
332			ERR_MSG("ioctl BLKREPORTZONE failed\n");
333			goto out;
334		}
335
336		if (!rep->nr_zones)
337			break;
338
339		blkz = (struct blk_zone *)(rep + 1);
340		for (i = 0; i < rep->nr_zones && sector < total_sectors; i++) {
341
342			if (blk_zone_cond(blkz) == BLK_ZONE_COND_READONLY ||
343			    blk_zone_cond(blkz) == BLK_ZONE_COND_OFFLINE)
344				last_is_conv = 0;
345			if (blk_zone_conv(blkz) ||
346			    blk_zone_seq_pref(blkz)) {
347				if (last_is_conv)
348					dev->nr_rnd_zones++;
349			} else {
350				last_is_conv = 0;
351			}
352
353			if (blk_zone_conv(blkz)) {
354				DBG(2,
355				    "Zone %05u: Conventional, cond 0x%x (%s), sector %llu, %llu sectors\n",
356				    n,
357				    blk_zone_cond(blkz),
358				    blk_zone_cond_str(blkz),
359				    blk_zone_sector(blkz),
360				    blk_zone_length(blkz));
361				dev->zone_cap_blocks[n] =
362					blk_zone_length(blkz) >>
363					(F2FS_BLKSIZE_BITS - SECTOR_SHIFT);
364			} else {
365				DBG(2,
366				    "Zone %05u: type 0x%x (%s), cond 0x%x (%s),"
367				    " need_reset %d, non_seq %d, sector %llu,"
368				    " %llu sectors, capacity %llu,"
369				    " wp sector %llu\n",
370				    n,
371				    blk_zone_type(blkz),
372				    blk_zone_type_str(blkz),
373				    blk_zone_cond(blkz),
374				    blk_zone_cond_str(blkz),
375				    blk_zone_need_reset(blkz),
376				    blk_zone_non_seq(blkz),
377				    blk_zone_sector(blkz),
378				    blk_zone_length(blkz),
379				    blk_zone_capacity(blkz, rep->flags),
380				    blk_zone_wp_sector(blkz));
381				dev->zone_cap_blocks[n] =
382					blk_zone_capacity(blkz, rep->flags) >>
383					(F2FS_BLKSIZE_BITS - SECTOR_SHIFT);
384			}
385
386			sector = blk_zone_sector(blkz) + blk_zone_length(blkz);
387			n++;
388			blkz++;
389		}
390	}
391
392	if (sector != total_sectors) {
393		ERR_MSG("Invalid zones: last sector reported is %llu, expected %llu\n",
394			(unsigned long long)(sector << 9) / c.sector_size,
395			(unsigned long long)dev->total_sectors);
396		ret = -1;
397		goto out;
398	}
399
400	if (n != dev->nr_zones) {
401		ERR_MSG("Inconsistent number of zones: expected %u zones, got %u\n",
402			dev->nr_zones, n);
403		ret = -1;
404		goto out;
405	}
406
407	/*
408	 * For a multi-device volume, fixed position metadata blocks are
409	 * stored * only on the first device of the volume. Checking for the
410	 * presence of * conventional zones (randomly writeabl zones) for
411	 * storing these blocks * on a host-managed device is thus needed only
412	 * for the device index 0.
413	 */
414	if (j == 0 && dev->zoned_model == F2FS_ZONED_HM &&
415			!dev->nr_rnd_zones) {
416		ERR_MSG("No conventional zone for super block\n");
417		ret = -1;
418	}
419out:
420	free(rep);
421	return ret;
422}
423
424int f2fs_reset_zone(int i, void *blkzone)
425{
426	struct blk_zone *blkz = (struct blk_zone *)blkzone;
427	struct device_info *dev = c.devices + i;
428	struct blk_zone_range range;
429	int ret;
430
431	if (!blk_zone_seq(blkz) || blk_zone_empty(blkz))
432		return 0;
433
434	/* Non empty sequential zone: reset */
435	range.sector = blk_zone_sector(blkz);
436	range.nr_sectors = blk_zone_length(blkz);
437	ret = ioctl(dev->fd, BLKRESETZONE, &range);
438	if (ret != 0) {
439		ret = -errno;
440		ERR_MSG("ioctl BLKRESETZONE failed: errno=%d\n", errno);
441	}
442
443	return ret;
444}
445
446int f2fs_reset_zones(int j)
447{
448	struct device_info *dev = c.devices + j;
449	struct blk_zone_report *rep;
450	struct blk_zone *blkz;
451	struct blk_zone_range range;
452	uint64_t total_sectors;
453	uint64_t sector;
454	unsigned int i;
455	int ret = -1;
456
457	rep = malloc(F2FS_REPORT_ZONES_BUFSZ);
458	if (!rep) {
459		ERR_MSG("No memory for report zones\n");
460		return -1;
461	}
462
463	sector = 0;
464	total_sectors = (dev->total_sectors * c.sector_size) >> 9;
465	while (sector < total_sectors) {
466
467		/* Get zone info */
468		memset(rep, 0, F2FS_REPORT_ZONES_BUFSZ);
469		rep->sector = sector;
470		rep->nr_zones = (F2FS_REPORT_ZONES_BUFSZ - sizeof(struct blk_zone_report))
471			/ sizeof(struct blk_zone);
472
473		ret = ioctl(dev->fd, BLKREPORTZONE, rep);
474		if (ret != 0) {
475			ret = -errno;
476			ERR_MSG("ioctl BLKREPORTZONES failed\n");
477			goto out;
478		}
479
480		if (!rep->nr_zones)
481			break;
482
483		blkz = (struct blk_zone *)(rep + 1);
484		for (i = 0; i < rep->nr_zones && sector < total_sectors; i++) {
485			if (blk_zone_seq(blkz) &&
486			    !blk_zone_empty(blkz)) {
487				/* Non empty sequential zone: reset */
488				range.sector = blk_zone_sector(blkz);
489				range.nr_sectors = blk_zone_length(blkz);
490				ret = ioctl(dev->fd, BLKRESETZONE, &range);
491				if (ret != 0) {
492					ret = -errno;
493					ERR_MSG("ioctl BLKRESETZONE failed\n");
494					goto out;
495				}
496			}
497			sector = blk_zone_sector(blkz) + blk_zone_length(blkz);
498			blkz++;
499		}
500	}
501out:
502	free(rep);
503	if (!ret)
504		MSG(0, "Info: Discarded %"PRIu64" MB\n", (sector << 9) >> 20);
505	return ret;
506}
507
508uint32_t f2fs_get_usable_segments(struct f2fs_super_block *sb)
509{
510#ifdef HAVE_BLK_ZONE_REP_V2
511	int i, j;
512	uint32_t usable_segs = 0, zone_segs;
513
514	if (c.func == RESIZE)
515		return get_sb(segment_count_main);
516
517	for (i = 0; i < c.ndevs; i++) {
518		if (c.devices[i].zoned_model != F2FS_ZONED_HM) {
519			usable_segs += c.devices[i].total_segments;
520			continue;
521		}
522		for (j = 0; j < c.devices[i].nr_zones; j++) {
523			zone_segs = c.devices[i].zone_cap_blocks[j] >>
524					get_sb(log_blocks_per_seg);
525			if (c.devices[i].zone_cap_blocks[j] %
526						DEFAULT_BLOCKS_PER_SEGMENT)
527				usable_segs += zone_segs + 1;
528			else
529				usable_segs += zone_segs;
530		}
531	}
532	usable_segs -= (get_sb(main_blkaddr) - get_sb(segment0_blkaddr)) >>
533						get_sb(log_blocks_per_seg);
534	return usable_segs;
535#endif
536	return get_sb(segment_count_main);
537}
538
539#else
540
541int f2fs_report_zone(int i, uint64_t UNUSED(sector),
542		     struct blk_zone *UNUSED(blkzone))
543{
544	ERR_MSG("%d: Unsupported zoned block device\n", i);
545	return -1;
546}
547
548int f2fs_report_zones(int i, report_zones_cb_t *UNUSED(report_zones_cb),
549					void *UNUSED(opaque))
550{
551	ERR_MSG("%d: Unsupported zoned block device\n", i);
552	return -1;
553}
554
555int f2fs_get_zoned_model(int i)
556{
557	struct device_info *dev = c.devices + i;
558
559	c.zoned_mode = 0;
560	dev->zoned_model = F2FS_ZONED_NONE;
561	return 0;
562}
563
564int f2fs_get_zone_blocks(int i)
565{
566	struct device_info *dev = c.devices + i;
567
568	c.zoned_mode = 0;
569	dev->nr_zones = 0;
570	dev->zone_blocks = 0;
571	dev->zoned_model = F2FS_ZONED_NONE;
572
573	return 0;
574}
575
576int f2fs_check_zones(int i)
577{
578	ERR_MSG("%d: Unsupported zoned block device\n", i);
579	return -1;
580}
581
582int f2fs_reset_zone(int i, void *UNUSED(blkzone))
583{
584	ERR_MSG("%d: Unsupported zoned block device\n", i);
585	return -1;
586}
587
588int f2fs_reset_zones(int i)
589{
590	ERR_MSG("%d: Unsupported zoned block device\n", i);
591	return -1;
592}
593
594uint32_t f2fs_get_usable_segments(struct f2fs_super_block *sb)
595{
596	return get_sb(segment_count_main);
597}
598#endif
599
600