xref: /aosp_15_r20/external/f2fs-tools/lib/libf2fs_zoned.c (revision 59bfda1f02d633cd6b8b69f31eee485d40f6eef6)
1 /**
2  * libf2fs_zoned.c
3  *
4  * Copyright (c) 2016 Western Digital Corporation.
5  * Written by: Damien Le Moal <[email protected]>
6  *
7  * Dual licensed under the GPL or LGPL version 2 licenses.
8  */
9 
10 #include <f2fs_fs.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <errno.h>
15 #include <unistd.h>
16 #include <fcntl.h>
17 #include <sys/stat.h>
18 #ifdef HAVE_SYS_SYSMACROS_H
19 #include <sys/sysmacros.h>
20 #endif
21 #ifdef HAVE_LINUX_LIMITS_H
22 #include <linux/limits.h>
23 #endif
24 #ifdef HAVE_SYS_IOCTL_H
25 #include <sys/ioctl.h>
26 #endif
27 #include <libgen.h>
28 
29 #ifdef HAVE_LINUX_BLKZONED_H
30 #ifndef BLKFINISHZONE
31 #define BLKFINISHZONE   _IOW(0x12, 136, struct blk_zone_range)
32 #endif
33 
get_sysfs_path(struct device_info * dev,const char * attr,char * buf,size_t buflen)34 int 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 
82 out:
83 	ret = snprintf(buf, buflen, "%s/%s", sysfs_path, attr);
84 	if (ret >= buflen)
85 		return -1;
86 
87 	return 0;
88 }
89 
f2fs_get_zoned_model(int i)90 int 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 
f2fs_get_zone_chunk_sectors(struct device_info * dev)149 uint32_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 
f2fs_get_zone_blocks(int i)178 int 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 
f2fs_report_zone(int i,uint64_t sector,struct blk_zone * blkzone)205 int 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;
233 out:
234 	free(rep);
235 	return ret;
236 }
237 
238 #define F2FS_REPORT_ZONES_BUFSZ	524288
239 
f2fs_report_zones(int j,report_zones_cb_t * report_zones_cb,void * opaque)240 int 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 	}
288 out:
289 	free(rep);
290 	return ret;
291 }
292 
f2fs_check_zones(int j)293 int 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 		free(rep);
313 		ERR_MSG("No memory for zone capacity list.\n");
314 		return -ENOMEM;
315 	}
316 	memset(dev->zone_cap_blocks, 0, (dev->nr_zones * sizeof(size_t)));
317 
318 	dev->nr_rnd_zones = 0;
319 	sector = 0;
320 	total_sectors = (dev->total_sectors * c.sector_size) >> 9;
321 
322 	while (sector < total_sectors) {
323 
324 		/* Get zone info */
325 		memset(rep, 0, F2FS_REPORT_ZONES_BUFSZ);
326 		rep->sector = sector;
327 		rep->nr_zones = (F2FS_REPORT_ZONES_BUFSZ - sizeof(struct blk_zone_report))
328 			/ sizeof(struct blk_zone);
329 
330 		ret = ioctl(dev->fd, BLKREPORTZONE, rep);
331 		if (ret != 0) {
332 			ret = -errno;
333 			ERR_MSG("ioctl BLKREPORTZONE failed\n");
334 			goto out;
335 		}
336 
337 		if (!rep->nr_zones)
338 			break;
339 
340 		blkz = (struct blk_zone *)(rep + 1);
341 		for (i = 0; i < rep->nr_zones && sector < total_sectors; i++) {
342 
343 			if (blk_zone_cond(blkz) == BLK_ZONE_COND_READONLY ||
344 			    blk_zone_cond(blkz) == BLK_ZONE_COND_OFFLINE)
345 				last_is_conv = 0;
346 			if (blk_zone_conv(blkz) ||
347 			    blk_zone_seq_pref(blkz)) {
348 				if (last_is_conv)
349 					dev->nr_rnd_zones++;
350 			} else {
351 				last_is_conv = 0;
352 			}
353 
354 			if (blk_zone_conv(blkz)) {
355 				DBG(2,
356 				    "Zone %05u: Conventional, cond 0x%x (%s), sector %llu, %llu sectors\n",
357 				    n,
358 				    blk_zone_cond(blkz),
359 				    blk_zone_cond_str(blkz),
360 				    blk_zone_sector(blkz),
361 				    blk_zone_length(blkz));
362 				dev->zone_cap_blocks[n] =
363 					blk_zone_length(blkz) >>
364 					(F2FS_BLKSIZE_BITS - SECTOR_SHIFT);
365 			} else {
366 				DBG(2,
367 				    "Zone %05u: type 0x%x (%s), cond 0x%x (%s),"
368 				    " need_reset %d, non_seq %d, sector %llu,"
369 				    " %llu sectors, capacity %llu,"
370 				    " wp sector %llu\n",
371 				    n,
372 				    blk_zone_type(blkz),
373 				    blk_zone_type_str(blkz),
374 				    blk_zone_cond(blkz),
375 				    blk_zone_cond_str(blkz),
376 				    blk_zone_need_reset(blkz),
377 				    blk_zone_non_seq(blkz),
378 				    blk_zone_sector(blkz),
379 				    blk_zone_length(blkz),
380 				    blk_zone_capacity(blkz, rep->flags),
381 				    blk_zone_wp_sector(blkz));
382 				dev->zone_cap_blocks[n] =
383 					blk_zone_capacity(blkz, rep->flags) >>
384 					(F2FS_BLKSIZE_BITS - SECTOR_SHIFT);
385 			}
386 
387 			sector = blk_zone_sector(blkz) + blk_zone_length(blkz);
388 			n++;
389 			blkz++;
390 		}
391 	}
392 
393 	if (sector != total_sectors) {
394 		ERR_MSG("Invalid zones: last sector reported is %llu, expected %llu\n",
395 			(unsigned long long)(sector << 9) / c.sector_size,
396 			(unsigned long long)dev->total_sectors);
397 		ret = -1;
398 		goto out;
399 	}
400 
401 	if (n != dev->nr_zones) {
402 		ERR_MSG("Inconsistent number of zones: expected %u zones, got %u\n",
403 			dev->nr_zones, n);
404 		ret = -1;
405 		goto out;
406 	}
407 
408 	/*
409 	 * For a multi-device volume, fixed position metadata blocks are
410 	 * stored * only on the first device of the volume. Checking for the
411 	 * presence of * conventional zones (randomly writeabl zones) for
412 	 * storing these blocks * on a host-managed device is thus needed only
413 	 * for the device index 0.
414 	 */
415 	if (j == 0 && dev->zoned_model == F2FS_ZONED_HM &&
416 			!dev->nr_rnd_zones) {
417 		ERR_MSG("No conventional zone for super block\n");
418 		ret = -1;
419 	}
420 out:
421 	free(rep);
422 	return ret;
423 }
424 
f2fs_reset_zone(int i,void * blkzone)425 int f2fs_reset_zone(int i, void *blkzone)
426 {
427 	struct blk_zone *blkz = (struct blk_zone *)blkzone;
428 	struct device_info *dev = c.devices + i;
429 	struct blk_zone_range range;
430 	int ret;
431 
432 	if (!blk_zone_seq(blkz) || blk_zone_empty(blkz))
433 		return 0;
434 
435 	/* Non empty sequential zone: reset */
436 	range.sector = blk_zone_sector(blkz);
437 	range.nr_sectors = blk_zone_length(blkz);
438 	ret = ioctl(dev->fd, BLKRESETZONE, &range);
439 	if (ret != 0) {
440 		ret = -errno;
441 		ERR_MSG("ioctl BLKRESETZONE failed: errno=%d\n", errno);
442 	}
443 
444 	return ret;
445 }
446 
f2fs_reset_zones(int j)447 int f2fs_reset_zones(int j)
448 {
449 	struct device_info *dev = c.devices + j;
450 	struct blk_zone_report *rep;
451 	struct blk_zone *blkz;
452 	struct blk_zone_range range;
453 	uint64_t total_sectors;
454 	uint64_t sector;
455 	unsigned int i;
456 	int ret = -1;
457 
458 	rep = malloc(F2FS_REPORT_ZONES_BUFSZ);
459 	if (!rep) {
460 		ERR_MSG("No memory for report zones\n");
461 		return -1;
462 	}
463 
464 	sector = 0;
465 	total_sectors = (dev->total_sectors * c.sector_size) >> 9;
466 	while (sector < total_sectors) {
467 
468 		/* Get zone info */
469 		memset(rep, 0, F2FS_REPORT_ZONES_BUFSZ);
470 		rep->sector = sector;
471 		rep->nr_zones = (F2FS_REPORT_ZONES_BUFSZ - sizeof(struct blk_zone_report))
472 			/ sizeof(struct blk_zone);
473 
474 		ret = ioctl(dev->fd, BLKREPORTZONE, rep);
475 		if (ret != 0) {
476 			ret = -errno;
477 			ERR_MSG("ioctl BLKREPORTZONES failed\n");
478 			goto out;
479 		}
480 
481 		if (!rep->nr_zones)
482 			break;
483 
484 		blkz = (struct blk_zone *)(rep + 1);
485 		for (i = 0; i < rep->nr_zones && sector < total_sectors; i++) {
486 			if (blk_zone_seq(blkz) &&
487 			    !blk_zone_empty(blkz)) {
488 				/* Non empty sequential zone: reset */
489 				range.sector = blk_zone_sector(blkz);
490 				range.nr_sectors = blk_zone_length(blkz);
491 				ret = ioctl(dev->fd, BLKRESETZONE, &range);
492 				if (ret != 0) {
493 					ret = -errno;
494 					ERR_MSG("ioctl BLKRESETZONE failed\n");
495 					goto out;
496 				}
497 			}
498 			sector = blk_zone_sector(blkz) + blk_zone_length(blkz);
499 			blkz++;
500 		}
501 	}
502 out:
503 	free(rep);
504 	if (!ret)
505 		MSG(0, "Info: Discarded %"PRIu64" MB\n", (sector << 9) >> 20);
506 	return ret;
507 }
508 
f2fs_finish_zone(int i,void * blkzone)509 int f2fs_finish_zone(int i, void *blkzone)
510 {
511 	struct blk_zone *blkz = (struct blk_zone *)blkzone;
512 	struct device_info *dev = c.devices + i;
513 	struct blk_zone_range range;
514 	int ret;
515 
516 	if (!blk_zone_seq(blkz) || !blk_zone_open(blkz))
517 		return 0;
518 
519 	/* Non empty sequential zone: finish */
520 	range.sector = blk_zone_sector(blkz);
521 	range.nr_sectors = blk_zone_length(blkz);
522 	ret = ioctl(dev->fd, BLKFINISHZONE, &range);
523 	if (ret != 0) {
524 		ret = -errno;
525 		ERR_MSG("ioctl BLKFINISHZONE failed: errno=%d, status=%s\n",
526 			errno, blk_zone_cond_str(blkz));
527 	}
528 
529 	return ret;
530 }
531 
f2fs_get_usable_segments(struct f2fs_super_block * sb)532 uint32_t f2fs_get_usable_segments(struct f2fs_super_block *sb)
533 {
534 #ifdef HAVE_BLK_ZONE_REP_V2
535 	int i, j;
536 	uint32_t usable_segs = 0, zone_segs;
537 
538 	if (c.func == RESIZE)
539 		return get_sb(segment_count_main);
540 
541 	for (i = 0; i < c.ndevs; i++) {
542 		if (c.devices[i].zoned_model != F2FS_ZONED_HM) {
543 			usable_segs += c.devices[i].total_segments;
544 			continue;
545 		}
546 		for (j = 0; j < c.devices[i].nr_zones; j++) {
547 			zone_segs = c.devices[i].zone_cap_blocks[j] >>
548 					get_sb(log_blocks_per_seg);
549 			if (c.devices[i].zone_cap_blocks[j] %
550 						DEFAULT_BLOCKS_PER_SEGMENT)
551 				usable_segs += zone_segs + 1;
552 			else
553 				usable_segs += zone_segs;
554 		}
555 	}
556 	usable_segs -= (get_sb(main_blkaddr) - get_sb(segment0_blkaddr)) >>
557 						get_sb(log_blocks_per_seg);
558 	return usable_segs;
559 #endif
560 	return get_sb(segment_count_main);
561 }
562 
563 #else
564 
f2fs_report_zone(int i,uint64_t UNUSED (sector),struct blk_zone * UNUSED (blkzone))565 int f2fs_report_zone(int i, uint64_t UNUSED(sector),
566 		     struct blk_zone *UNUSED(blkzone))
567 {
568 	ERR_MSG("%d: Unsupported zoned block device\n", i);
569 	return -1;
570 }
571 
f2fs_report_zones(int i,report_zones_cb_t * UNUSED (report_zones_cb),void * UNUSED (opaque))572 int f2fs_report_zones(int i, report_zones_cb_t *UNUSED(report_zones_cb),
573 					void *UNUSED(opaque))
574 {
575 	ERR_MSG("%d: Unsupported zoned block device\n", i);
576 	return -1;
577 }
578 
f2fs_get_zoned_model(int i)579 int f2fs_get_zoned_model(int i)
580 {
581 	struct device_info *dev = c.devices + i;
582 
583 	c.zoned_mode = 0;
584 	dev->zoned_model = F2FS_ZONED_NONE;
585 	return 0;
586 }
587 
f2fs_get_zone_blocks(int i)588 int f2fs_get_zone_blocks(int i)
589 {
590 	struct device_info *dev = c.devices + i;
591 
592 	c.zoned_mode = 0;
593 	dev->nr_zones = 0;
594 	dev->zone_blocks = 0;
595 	dev->zoned_model = F2FS_ZONED_NONE;
596 
597 	return 0;
598 }
599 
f2fs_check_zones(int i)600 int f2fs_check_zones(int i)
601 {
602 	ERR_MSG("%d: Unsupported zoned block device\n", i);
603 	return -1;
604 }
605 
f2fs_reset_zone(int i,void * UNUSED (blkzone))606 int f2fs_reset_zone(int i, void *UNUSED(blkzone))
607 {
608 	ERR_MSG("%d: Unsupported zoned block device\n", i);
609 	return -1;
610 }
611 
f2fs_reset_zones(int i)612 int f2fs_reset_zones(int i)
613 {
614 	ERR_MSG("%d: Unsupported zoned block device\n", i);
615 	return -1;
616 }
617 
f2fs_finish_zone(int i,void * UNUSED (blkzone))618 int f2fs_finish_zone(int i, void *UNUSED(blkzone))
619 {
620 	ERR_MSG("%d: Unsupported zoned block device\n", i);
621 	return -1;
622 }
623 
f2fs_get_usable_segments(struct f2fs_super_block * sb)624 uint32_t f2fs_get_usable_segments(struct f2fs_super_block *sb)
625 {
626 	return get_sb(segment_count_main);
627 }
628 #endif
629 
630