1 // SPDX-License-Identifier: GPL-2.0 2 #include <linux/capability.h> 3 #include <linux/compat.h> 4 #include <linux/blkdev.h> 5 #include <linux/export.h> 6 #include <linux/gfp.h> 7 #include <linux/blkpg.h> 8 #include <linux/hdreg.h> 9 #include <linux/backing-dev.h> 10 #include <linux/fs.h> 11 #include <linux/blktrace_api.h> 12 #include <linux/pr.h> 13 #include <linux/uaccess.h> 14 #include "blk.h" 15 16 static int blkpg_do_ioctl(struct block_device *bdev, 17 struct blkpg_partition __user *upart, int op) 18 { 19 struct gendisk *disk = bdev->bd_disk; 20 struct blkpg_partition p; 21 sector_t start, length, capacity, end; 22 23 if (!capable(CAP_SYS_ADMIN)) 24 return -EACCES; 25 if (copy_from_user(&p, upart, sizeof(struct blkpg_partition))) 26 return -EFAULT; 27 if (bdev_is_partition(bdev)) 28 return -EINVAL; 29 30 if (p.pno <= 0) 31 return -EINVAL; 32 33 if (op == BLKPG_DEL_PARTITION) 34 return bdev_del_partition(disk, p.pno); 35 36 if (p.start < 0 || p.length <= 0 || p.start + p.length < 0) 37 return -EINVAL; 38 /* Check that the partition is aligned to the block size */ 39 if (!IS_ALIGNED(p.start | p.length, bdev_logical_block_size(bdev))) 40 return -EINVAL; 41 42 start = p.start >> SECTOR_SHIFT; 43 length = p.length >> SECTOR_SHIFT; 44 capacity = get_capacity(disk); 45 46 if (check_add_overflow(start, length, &end)) 47 return -EINVAL; 48 49 if (start >= capacity || end > capacity) 50 return -EINVAL; 51 52 switch (op) { 53 case BLKPG_ADD_PARTITION: 54 return bdev_add_partition(disk, p.pno, start, length); 55 case BLKPG_RESIZE_PARTITION: 56 return bdev_resize_partition(disk, p.pno, start, length); 57 default: 58 return -EINVAL; 59 } 60 } 61 62 static int blkpg_ioctl(struct block_device *bdev, 63 struct blkpg_ioctl_arg __user *arg) 64 { 65 struct blkpg_partition __user *udata; 66 int op; 67 68 if (get_user(op, &arg->op) || get_user(udata, &arg->data)) 69 return -EFAULT; 70 71 return blkpg_do_ioctl(bdev, udata, op); 72 } 73 74 #ifdef CONFIG_COMPAT 75 struct compat_blkpg_ioctl_arg { 76 compat_int_t op; 77 compat_int_t flags; 78 compat_int_t datalen; 79 compat_caddr_t data; 80 }; 81 82 static int compat_blkpg_ioctl(struct block_device *bdev, 83 struct compat_blkpg_ioctl_arg __user *arg) 84 { 85 compat_caddr_t udata; 86 int op; 87 88 if (get_user(op, &arg->op) || get_user(udata, &arg->data)) 89 return -EFAULT; 90 91 return blkpg_do_ioctl(bdev, compat_ptr(udata), op); 92 } 93 #endif 94 95 static int blk_ioctl_discard(struct block_device *bdev, blk_mode_t mode, 96 unsigned long arg) 97 { 98 uint64_t range[2]; 99 uint64_t start, len; 100 struct inode *inode = bdev->bd_inode; 101 int err; 102 103 if (!(mode & BLK_OPEN_WRITE)) 104 return -EBADF; 105 106 if (!bdev_max_discard_sectors(bdev)) 107 return -EOPNOTSUPP; 108 109 if (copy_from_user(range, (void __user *)arg, sizeof(range))) 110 return -EFAULT; 111 112 start = range[0]; 113 len = range[1]; 114 115 if (start & 511) 116 return -EINVAL; 117 if (len & 511) 118 return -EINVAL; 119 120 if (start + len > bdev_nr_bytes(bdev)) 121 return -EINVAL; 122 123 filemap_invalidate_lock(inode->i_mapping); 124 err = truncate_bdev_range(bdev, mode, start, start + len - 1); 125 if (err) 126 goto fail; 127 err = blkdev_issue_discard(bdev, start >> 9, len >> 9, GFP_KERNEL); 128 fail: 129 filemap_invalidate_unlock(inode->i_mapping); 130 return err; 131 } 132 133 static int blk_ioctl_secure_erase(struct block_device *bdev, blk_mode_t mode, 134 void __user *argp) 135 { 136 uint64_t start, len; 137 uint64_t range[2]; 138 int err; 139 140 if (!(mode & BLK_OPEN_WRITE)) 141 return -EBADF; 142 if (!bdev_max_secure_erase_sectors(bdev)) 143 return -EOPNOTSUPP; 144 if (copy_from_user(range, argp, sizeof(range))) 145 return -EFAULT; 146 147 start = range[0]; 148 len = range[1]; 149 if ((start & 511) || (len & 511)) 150 return -EINVAL; 151 if (start + len > bdev_nr_bytes(bdev)) 152 return -EINVAL; 153 154 filemap_invalidate_lock(bdev->bd_inode->i_mapping); 155 err = truncate_bdev_range(bdev, mode, start, start + len - 1); 156 if (!err) 157 err = blkdev_issue_secure_erase(bdev, start >> 9, len >> 9, 158 GFP_KERNEL); 159 filemap_invalidate_unlock(bdev->bd_inode->i_mapping); 160 return err; 161 } 162 163 164 static int blk_ioctl_zeroout(struct block_device *bdev, blk_mode_t mode, 165 unsigned long arg) 166 { 167 uint64_t range[2]; 168 uint64_t start, end, len; 169 struct inode *inode = bdev->bd_inode; 170 int err; 171 172 if (!(mode & BLK_OPEN_WRITE)) 173 return -EBADF; 174 175 if (copy_from_user(range, (void __user *)arg, sizeof(range))) 176 return -EFAULT; 177 178 start = range[0]; 179 len = range[1]; 180 end = start + len - 1; 181 182 if (start & 511) 183 return -EINVAL; 184 if (len & 511) 185 return -EINVAL; 186 if (end >= (uint64_t)bdev_nr_bytes(bdev)) 187 return -EINVAL; 188 if (end < start) 189 return -EINVAL; 190 191 /* Invalidate the page cache, including dirty pages */ 192 filemap_invalidate_lock(inode->i_mapping); 193 err = truncate_bdev_range(bdev, mode, start, end); 194 if (err) 195 goto fail; 196 197 err = blkdev_issue_zeroout(bdev, start >> 9, len >> 9, GFP_KERNEL, 198 BLKDEV_ZERO_NOUNMAP); 199 200 fail: 201 filemap_invalidate_unlock(inode->i_mapping); 202 return err; 203 } 204 205 static int put_ushort(unsigned short __user *argp, unsigned short val) 206 { 207 return put_user(val, argp); 208 } 209 210 static int put_int(int __user *argp, int val) 211 { 212 return put_user(val, argp); 213 } 214 215 static int put_uint(unsigned int __user *argp, unsigned int val) 216 { 217 return put_user(val, argp); 218 } 219 220 static int put_long(long __user *argp, long val) 221 { 222 return put_user(val, argp); 223 } 224 225 static int put_ulong(unsigned long __user *argp, unsigned long val) 226 { 227 return put_user(val, argp); 228 } 229 230 static int put_u64(u64 __user *argp, u64 val) 231 { 232 return put_user(val, argp); 233 } 234 235 #ifdef CONFIG_COMPAT 236 static int compat_put_long(compat_long_t __user *argp, long val) 237 { 238 return put_user(val, argp); 239 } 240 241 static int compat_put_ulong(compat_ulong_t __user *argp, compat_ulong_t val) 242 { 243 return put_user(val, argp); 244 } 245 #endif 246 247 #ifdef CONFIG_COMPAT 248 /* 249 * This is the equivalent of compat_ptr_ioctl(), to be used by block 250 * drivers that implement only commands that are completely compatible 251 * between 32-bit and 64-bit user space 252 */ 253 int blkdev_compat_ptr_ioctl(struct block_device *bdev, blk_mode_t mode, 254 unsigned cmd, unsigned long arg) 255 { 256 struct gendisk *disk = bdev->bd_disk; 257 258 if (disk->fops->ioctl) 259 return disk->fops->ioctl(bdev, mode, cmd, 260 (unsigned long)compat_ptr(arg)); 261 262 return -ENOIOCTLCMD; 263 } 264 EXPORT_SYMBOL(blkdev_compat_ptr_ioctl); 265 #endif 266 267 static bool blkdev_pr_allowed(struct block_device *bdev, blk_mode_t mode) 268 { 269 /* no sense to make reservations for partitions */ 270 if (bdev_is_partition(bdev)) 271 return false; 272 273 if (capable(CAP_SYS_ADMIN)) 274 return true; 275 /* 276 * Only allow unprivileged reservations if the file descriptor is open 277 * for writing. 278 */ 279 return mode & BLK_OPEN_WRITE; 280 } 281 282 static int blkdev_pr_register(struct block_device *bdev, blk_mode_t mode, 283 struct pr_registration __user *arg) 284 { 285 const struct pr_ops *ops = bdev->bd_disk->fops->pr_ops; 286 struct pr_registration reg; 287 288 if (!blkdev_pr_allowed(bdev, mode)) 289 return -EPERM; 290 if (!ops || !ops->pr_register) 291 return -EOPNOTSUPP; 292 if (copy_from_user(®, arg, sizeof(reg))) 293 return -EFAULT; 294 295 if (reg.flags & ~PR_FL_IGNORE_KEY) 296 return -EOPNOTSUPP; 297 return ops->pr_register(bdev, reg.old_key, reg.new_key, reg.flags); 298 } 299 300 static int blkdev_pr_reserve(struct block_device *bdev, blk_mode_t mode, 301 struct pr_reservation __user *arg) 302 { 303 const struct pr_ops *ops = bdev->bd_disk->fops->pr_ops; 304 struct pr_reservation rsv; 305 306 if (!blkdev_pr_allowed(bdev, mode)) 307 return -EPERM; 308 if (!ops || !ops->pr_reserve) 309 return -EOPNOTSUPP; 310 if (copy_from_user(&rsv, arg, sizeof(rsv))) 311 return -EFAULT; 312 313 if (rsv.flags & ~PR_FL_IGNORE_KEY) 314 return -EOPNOTSUPP; 315 return ops->pr_reserve(bdev, rsv.key, rsv.type, rsv.flags); 316 } 317 318 static int blkdev_pr_release(struct block_device *bdev, blk_mode_t mode, 319 struct pr_reservation __user *arg) 320 { 321 const struct pr_ops *ops = bdev->bd_disk->fops->pr_ops; 322 struct pr_reservation rsv; 323 324 if (!blkdev_pr_allowed(bdev, mode)) 325 return -EPERM; 326 if (!ops || !ops->pr_release) 327 return -EOPNOTSUPP; 328 if (copy_from_user(&rsv, arg, sizeof(rsv))) 329 return -EFAULT; 330 331 if (rsv.flags) 332 return -EOPNOTSUPP; 333 return ops->pr_release(bdev, rsv.key, rsv.type); 334 } 335 336 static int blkdev_pr_preempt(struct block_device *bdev, blk_mode_t mode, 337 struct pr_preempt __user *arg, bool abort) 338 { 339 const struct pr_ops *ops = bdev->bd_disk->fops->pr_ops; 340 struct pr_preempt p; 341 342 if (!blkdev_pr_allowed(bdev, mode)) 343 return -EPERM; 344 if (!ops || !ops->pr_preempt) 345 return -EOPNOTSUPP; 346 if (copy_from_user(&p, arg, sizeof(p))) 347 return -EFAULT; 348 349 if (p.flags) 350 return -EOPNOTSUPP; 351 return ops->pr_preempt(bdev, p.old_key, p.new_key, p.type, abort); 352 } 353 354 static int blkdev_pr_clear(struct block_device *bdev, blk_mode_t mode, 355 struct pr_clear __user *arg) 356 { 357 const struct pr_ops *ops = bdev->bd_disk->fops->pr_ops; 358 struct pr_clear c; 359 360 if (!blkdev_pr_allowed(bdev, mode)) 361 return -EPERM; 362 if (!ops || !ops->pr_clear) 363 return -EOPNOTSUPP; 364 if (copy_from_user(&c, arg, sizeof(c))) 365 return -EFAULT; 366 367 if (c.flags) 368 return -EOPNOTSUPP; 369 return ops->pr_clear(bdev, c.key); 370 } 371 372 static int blkdev_flushbuf(struct block_device *bdev, unsigned cmd, 373 unsigned long arg) 374 { 375 if (!capable(CAP_SYS_ADMIN)) 376 return -EACCES; 377 378 mutex_lock(&bdev->bd_holder_lock); 379 if (bdev->bd_holder_ops && bdev->bd_holder_ops->sync) 380 bdev->bd_holder_ops->sync(bdev); 381 else { 382 mutex_unlock(&bdev->bd_holder_lock); 383 sync_blockdev(bdev); 384 } 385 386 invalidate_bdev(bdev); 387 return 0; 388 } 389 390 static int blkdev_roset(struct block_device *bdev, unsigned cmd, 391 unsigned long arg) 392 { 393 int ret, n; 394 395 if (!capable(CAP_SYS_ADMIN)) 396 return -EACCES; 397 398 if (get_user(n, (int __user *)arg)) 399 return -EFAULT; 400 if (bdev->bd_disk->fops->set_read_only) { 401 ret = bdev->bd_disk->fops->set_read_only(bdev, n); 402 if (ret) 403 return ret; 404 } 405 bdev->bd_read_only = n; 406 return 0; 407 } 408 409 static int blkdev_getgeo(struct block_device *bdev, 410 struct hd_geometry __user *argp) 411 { 412 struct gendisk *disk = bdev->bd_disk; 413 struct hd_geometry geo; 414 int ret; 415 416 if (!argp) 417 return -EINVAL; 418 if (!disk->fops->getgeo) 419 return -ENOTTY; 420 421 /* 422 * We need to set the startsect first, the driver may 423 * want to override it. 424 */ 425 memset(&geo, 0, sizeof(geo)); 426 geo.start = get_start_sect(bdev); 427 ret = disk->fops->getgeo(bdev, &geo); 428 if (ret) 429 return ret; 430 if (copy_to_user(argp, &geo, sizeof(geo))) 431 return -EFAULT; 432 return 0; 433 } 434 435 #ifdef CONFIG_COMPAT 436 struct compat_hd_geometry { 437 unsigned char heads; 438 unsigned char sectors; 439 unsigned short cylinders; 440 u32 start; 441 }; 442 443 static int compat_hdio_getgeo(struct block_device *bdev, 444 struct compat_hd_geometry __user *ugeo) 445 { 446 struct gendisk *disk = bdev->bd_disk; 447 struct hd_geometry geo; 448 int ret; 449 450 if (!ugeo) 451 return -EINVAL; 452 if (!disk->fops->getgeo) 453 return -ENOTTY; 454 455 memset(&geo, 0, sizeof(geo)); 456 /* 457 * We need to set the startsect first, the driver may 458 * want to override it. 459 */ 460 geo.start = get_start_sect(bdev); 461 ret = disk->fops->getgeo(bdev, &geo); 462 if (ret) 463 return ret; 464 465 ret = copy_to_user(ugeo, &geo, 4); 466 ret |= put_user(geo.start, &ugeo->start); 467 if (ret) 468 ret = -EFAULT; 469 470 return ret; 471 } 472 #endif 473 474 /* set the logical block size */ 475 static int blkdev_bszset(struct block_device *bdev, blk_mode_t mode, 476 int __user *argp) 477 { 478 int ret, n; 479 struct file *file; 480 481 if (!capable(CAP_SYS_ADMIN)) 482 return -EACCES; 483 if (!argp) 484 return -EINVAL; 485 if (get_user(n, argp)) 486 return -EFAULT; 487 488 if (mode & BLK_OPEN_EXCL) 489 return set_blocksize(bdev, n); 490 491 file = bdev_file_open_by_dev(bdev->bd_dev, mode, &bdev, NULL); 492 if (IS_ERR(file)) 493 return -EBUSY; 494 ret = set_blocksize(bdev, n); 495 fput(file); 496 return ret; 497 } 498 499 /* 500 * Common commands that are handled the same way on native and compat 501 * user space. Note the separate arg/argp parameters that are needed 502 * to deal with the compat_ptr() conversion. 503 */ 504 static int blkdev_common_ioctl(struct block_device *bdev, blk_mode_t mode, 505 unsigned int cmd, unsigned long arg, 506 void __user *argp) 507 { 508 unsigned int max_sectors; 509 510 switch (cmd) { 511 case BLKFLSBUF: 512 return blkdev_flushbuf(bdev, cmd, arg); 513 case BLKROSET: 514 return blkdev_roset(bdev, cmd, arg); 515 case BLKDISCARD: 516 return blk_ioctl_discard(bdev, mode, arg); 517 case BLKSECDISCARD: 518 return blk_ioctl_secure_erase(bdev, mode, argp); 519 case BLKZEROOUT: 520 return blk_ioctl_zeroout(bdev, mode, arg); 521 case BLKGETDISKSEQ: 522 return put_u64(argp, bdev->bd_disk->diskseq); 523 case BLKREPORTZONE: 524 return blkdev_report_zones_ioctl(bdev, cmd, arg); 525 case BLKRESETZONE: 526 case BLKOPENZONE: 527 case BLKCLOSEZONE: 528 case BLKFINISHZONE: 529 return blkdev_zone_mgmt_ioctl(bdev, mode, cmd, arg); 530 case BLKGETZONESZ: 531 return put_uint(argp, bdev_zone_sectors(bdev)); 532 case BLKGETNRZONES: 533 return put_uint(argp, bdev_nr_zones(bdev)); 534 case BLKROGET: 535 return put_int(argp, bdev_read_only(bdev) != 0); 536 case BLKSSZGET: /* get block device logical block size */ 537 return put_int(argp, bdev_logical_block_size(bdev)); 538 case BLKPBSZGET: /* get block device physical block size */ 539 return put_uint(argp, bdev_physical_block_size(bdev)); 540 case BLKIOMIN: 541 return put_uint(argp, bdev_io_min(bdev)); 542 case BLKIOOPT: 543 return put_uint(argp, bdev_io_opt(bdev)); 544 case BLKALIGNOFF: 545 return put_int(argp, bdev_alignment_offset(bdev)); 546 case BLKDISCARDZEROES: 547 return put_uint(argp, 0); 548 case BLKSECTGET: 549 max_sectors = min_t(unsigned int, USHRT_MAX, 550 queue_max_sectors(bdev_get_queue(bdev))); 551 return put_ushort(argp, max_sectors); 552 case BLKROTATIONAL: 553 return put_ushort(argp, !bdev_nonrot(bdev)); 554 case BLKRASET: 555 case BLKFRASET: 556 if(!capable(CAP_SYS_ADMIN)) 557 return -EACCES; 558 bdev->bd_disk->bdi->ra_pages = (arg * 512) / PAGE_SIZE; 559 return 0; 560 case BLKRRPART: 561 if (!capable(CAP_SYS_ADMIN)) 562 return -EACCES; 563 if (bdev_is_partition(bdev)) 564 return -EINVAL; 565 return disk_scan_partitions(bdev->bd_disk, mode); 566 case BLKTRACESTART: 567 case BLKTRACESTOP: 568 case BLKTRACETEARDOWN: 569 return blk_trace_ioctl(bdev, cmd, argp); 570 case IOC_PR_REGISTER: 571 return blkdev_pr_register(bdev, mode, argp); 572 case IOC_PR_RESERVE: 573 return blkdev_pr_reserve(bdev, mode, argp); 574 case IOC_PR_RELEASE: 575 return blkdev_pr_release(bdev, mode, argp); 576 case IOC_PR_PREEMPT: 577 return blkdev_pr_preempt(bdev, mode, argp, false); 578 case IOC_PR_PREEMPT_ABORT: 579 return blkdev_pr_preempt(bdev, mode, argp, true); 580 case IOC_PR_CLEAR: 581 return blkdev_pr_clear(bdev, mode, argp); 582 default: 583 return -ENOIOCTLCMD; 584 } 585 } 586 587 /* 588 * Always keep this in sync with compat_blkdev_ioctl() 589 * to handle all incompatible commands in both functions. 590 * 591 * New commands must be compatible and go into blkdev_common_ioctl 592 */ 593 long blkdev_ioctl(struct file *file, unsigned cmd, unsigned long arg) 594 { 595 struct block_device *bdev = I_BDEV(file->f_mapping->host); 596 void __user *argp = (void __user *)arg; 597 blk_mode_t mode = file_to_blk_mode(file); 598 int ret; 599 600 switch (cmd) { 601 /* These need separate implementations for the data structure */ 602 case HDIO_GETGEO: 603 return blkdev_getgeo(bdev, argp); 604 case BLKPG: 605 return blkpg_ioctl(bdev, argp); 606 607 /* Compat mode returns 32-bit data instead of 'long' */ 608 case BLKRAGET: 609 case BLKFRAGET: 610 if (!argp) 611 return -EINVAL; 612 return put_long(argp, 613 (bdev->bd_disk->bdi->ra_pages * PAGE_SIZE) / 512); 614 case BLKGETSIZE: 615 if (bdev_nr_sectors(bdev) > ~0UL) 616 return -EFBIG; 617 return put_ulong(argp, bdev_nr_sectors(bdev)); 618 619 /* The data is compatible, but the command number is different */ 620 case BLKBSZGET: /* get block device soft block size (cf. BLKSSZGET) */ 621 return put_int(argp, block_size(bdev)); 622 case BLKBSZSET: 623 return blkdev_bszset(bdev, mode, argp); 624 case BLKGETSIZE64: 625 return put_u64(argp, bdev_nr_bytes(bdev)); 626 627 /* Incompatible alignment on i386 */ 628 case BLKTRACESETUP: 629 return blk_trace_ioctl(bdev, cmd, argp); 630 default: 631 break; 632 } 633 634 ret = blkdev_common_ioctl(bdev, mode, cmd, arg, argp); 635 if (ret != -ENOIOCTLCMD) 636 return ret; 637 638 if (!bdev->bd_disk->fops->ioctl) 639 return -ENOTTY; 640 return bdev->bd_disk->fops->ioctl(bdev, mode, cmd, arg); 641 } 642 643 #ifdef CONFIG_COMPAT 644 645 #define BLKBSZGET_32 _IOR(0x12, 112, int) 646 #define BLKBSZSET_32 _IOW(0x12, 113, int) 647 #define BLKGETSIZE64_32 _IOR(0x12, 114, int) 648 649 /* Most of the generic ioctls are handled in the normal fallback path. 650 This assumes the blkdev's low level compat_ioctl always returns 651 ENOIOCTLCMD for unknown ioctls. */ 652 long compat_blkdev_ioctl(struct file *file, unsigned cmd, unsigned long arg) 653 { 654 int ret; 655 void __user *argp = compat_ptr(arg); 656 struct block_device *bdev = I_BDEV(file->f_mapping->host); 657 struct gendisk *disk = bdev->bd_disk; 658 blk_mode_t mode = file_to_blk_mode(file); 659 660 switch (cmd) { 661 /* These need separate implementations for the data structure */ 662 case HDIO_GETGEO: 663 return compat_hdio_getgeo(bdev, argp); 664 case BLKPG: 665 return compat_blkpg_ioctl(bdev, argp); 666 667 /* Compat mode returns 32-bit data instead of 'long' */ 668 case BLKRAGET: 669 case BLKFRAGET: 670 if (!argp) 671 return -EINVAL; 672 return compat_put_long(argp, 673 (bdev->bd_disk->bdi->ra_pages * PAGE_SIZE) / 512); 674 case BLKGETSIZE: 675 if (bdev_nr_sectors(bdev) > ~(compat_ulong_t)0) 676 return -EFBIG; 677 return compat_put_ulong(argp, bdev_nr_sectors(bdev)); 678 679 /* The data is compatible, but the command number is different */ 680 case BLKBSZGET_32: /* get the logical block size (cf. BLKSSZGET) */ 681 return put_int(argp, bdev_logical_block_size(bdev)); 682 case BLKBSZSET_32: 683 return blkdev_bszset(bdev, mode, argp); 684 case BLKGETSIZE64_32: 685 return put_u64(argp, bdev_nr_bytes(bdev)); 686 687 /* Incompatible alignment on i386 */ 688 case BLKTRACESETUP32: 689 return blk_trace_ioctl(bdev, cmd, argp); 690 default: 691 break; 692 } 693 694 ret = blkdev_common_ioctl(bdev, mode, cmd, arg, argp); 695 if (ret == -ENOIOCTLCMD && disk->fops->compat_ioctl) 696 ret = disk->fops->compat_ioctl(bdev, mode, cmd, arg); 697 698 return ret; 699 } 700 #endif 701