| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1 | /* | 
 | 2 |  * File...........: linux/drivers/s390/block/dasd_ioctl.c | 
 | 3 |  * Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com> | 
 | 4 |  *		    Horst Hummel <Horst.Hummel@de.ibm.com> | 
 | 5 |  *		    Carsten Otte <Cotte@de.ibm.com> | 
 | 6 |  *		    Martin Schwidefsky <schwidefsky@de.ibm.com> | 
 | 7 |  * Bugreports.to..: <Linux390@de.ibm.com> | 
 | 8 |  * (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999-2001 | 
 | 9 |  * | 
 | 10 |  * i/o controls for the dasd driver. | 
 | 11 |  */ | 
| Stefan Haberland | fc19f38 | 2009-03-26 15:23:49 +0100 | [diff] [blame] | 12 |  | 
 | 13 | #define KMSG_COMPONENT "dasd" | 
 | 14 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 15 | #include <linux/interrupt.h> | 
 | 16 | #include <linux/major.h> | 
 | 17 | #include <linux/fs.h> | 
 | 18 | #include <linux/blkpg.h> | 
| Tejun Heo | 5a0e3ad | 2010-03-24 17:04:11 +0900 | [diff] [blame] | 19 | #include <linux/slab.h> | 
| Heiko Carstens | 8803486 | 2010-01-13 20:44:29 +0100 | [diff] [blame] | 20 | #include <asm/compat.h> | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 21 | #include <asm/ccwdev.h> | 
| Christoph Hellwig | 8b2eb66 | 2006-03-24 03:15:21 -0800 | [diff] [blame] | 22 | #include <asm/cmb.h> | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 23 | #include <asm/uaccess.h> | 
 | 24 |  | 
 | 25 | /* This is ugly... */ | 
 | 26 | #define PRINTK_HEADER "dasd_ioctl:" | 
 | 27 |  | 
 | 28 | #include "dasd_int.h" | 
 | 29 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 30 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 31 | static int | 
| Christoph Hellwig | 13c6204 | 2006-03-24 03:15:19 -0800 | [diff] [blame] | 32 | dasd_ioctl_api_version(void __user *argp) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 33 | { | 
 | 34 | 	int ver = DASD_API_VERSION; | 
| Bastian Blank | b502962 | 2006-03-24 03:15:32 -0800 | [diff] [blame] | 35 | 	return put_user(ver, (int __user *)argp); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 36 | } | 
 | 37 |  | 
 | 38 | /* | 
 | 39 |  * Enable device. | 
 | 40 |  * used by dasdfmt after BIODASDDISABLE to retrigger blocksize detection | 
 | 41 |  */ | 
 | 42 | static int | 
| Christoph Hellwig | 13c6204 | 2006-03-24 03:15:19 -0800 | [diff] [blame] | 43 | dasd_ioctl_enable(struct block_device *bdev) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 44 | { | 
| Stefan Weinhuber | 65f8da4 | 2011-04-20 10:15:30 +0200 | [diff] [blame] | 45 | 	struct dasd_device *base; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 46 |  | 
 | 47 | 	if (!capable(CAP_SYS_ADMIN)) | 
 | 48 | 		return -EACCES; | 
| Christoph Hellwig | 13c6204 | 2006-03-24 03:15:19 -0800 | [diff] [blame] | 49 |  | 
| Stefan Weinhuber | 65f8da4 | 2011-04-20 10:15:30 +0200 | [diff] [blame] | 50 | 	base = dasd_device_from_gendisk(bdev->bd_disk); | 
 | 51 | 	if (!base) | 
 | 52 | 		return -ENODEV; | 
 | 53 |  | 
 | 54 | 	dasd_enable_device(base); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 55 | 	/* Formatting the dasd device can change the capacity. */ | 
| Arjan van de Ven | c039e31 | 2006-03-23 03:00:28 -0800 | [diff] [blame] | 56 | 	mutex_lock(&bdev->bd_mutex); | 
| Stefan Weinhuber | 65f8da4 | 2011-04-20 10:15:30 +0200 | [diff] [blame] | 57 | 	i_size_write(bdev->bd_inode, | 
 | 58 | 		     (loff_t)get_capacity(base->block->gdp) << 9); | 
| Arjan van de Ven | c039e31 | 2006-03-23 03:00:28 -0800 | [diff] [blame] | 59 | 	mutex_unlock(&bdev->bd_mutex); | 
| Stefan Weinhuber | 65f8da4 | 2011-04-20 10:15:30 +0200 | [diff] [blame] | 60 | 	dasd_put_device(base); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 61 | 	return 0; | 
 | 62 | } | 
 | 63 |  | 
 | 64 | /* | 
 | 65 |  * Disable device. | 
 | 66 |  * Used by dasdfmt. Disable I/O operations but allow ioctls. | 
 | 67 |  */ | 
 | 68 | static int | 
| Christoph Hellwig | 13c6204 | 2006-03-24 03:15:19 -0800 | [diff] [blame] | 69 | dasd_ioctl_disable(struct block_device *bdev) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 70 | { | 
| Stefan Weinhuber | 65f8da4 | 2011-04-20 10:15:30 +0200 | [diff] [blame] | 71 | 	struct dasd_device *base; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 72 |  | 
 | 73 | 	if (!capable(CAP_SYS_ADMIN)) | 
 | 74 | 		return -EACCES; | 
| Christoph Hellwig | 13c6204 | 2006-03-24 03:15:19 -0800 | [diff] [blame] | 75 |  | 
| Stefan Weinhuber | 65f8da4 | 2011-04-20 10:15:30 +0200 | [diff] [blame] | 76 | 	base = dasd_device_from_gendisk(bdev->bd_disk); | 
 | 77 | 	if (!base) | 
 | 78 | 		return -ENODEV; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 79 | 	/* | 
 | 80 | 	 * Man this is sick. We don't do a real disable but only downgrade | 
 | 81 | 	 * the device to DASD_STATE_BASIC. The reason is that dasdfmt uses | 
 | 82 | 	 * BIODASDDISABLE to disable accesses to the device via the block | 
 | 83 | 	 * device layer but it still wants to do i/o on the device by | 
 | 84 | 	 * using the BIODASDFMT ioctl. Therefore the correct state for the | 
 | 85 | 	 * device is DASD_STATE_BASIC that allows to do basic i/o. | 
 | 86 | 	 */ | 
| Stefan Weinhuber | 65f8da4 | 2011-04-20 10:15:30 +0200 | [diff] [blame] | 87 | 	dasd_set_target_state(base, DASD_STATE_BASIC); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 88 | 	/* | 
 | 89 | 	 * Set i_size to zero, since read, write, etc. check against this | 
 | 90 | 	 * value. | 
 | 91 | 	 */ | 
| Arjan van de Ven | c039e31 | 2006-03-23 03:00:28 -0800 | [diff] [blame] | 92 | 	mutex_lock(&bdev->bd_mutex); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 93 | 	i_size_write(bdev->bd_inode, 0); | 
| Arjan van de Ven | c039e31 | 2006-03-23 03:00:28 -0800 | [diff] [blame] | 94 | 	mutex_unlock(&bdev->bd_mutex); | 
| Stefan Weinhuber | 65f8da4 | 2011-04-20 10:15:30 +0200 | [diff] [blame] | 95 | 	dasd_put_device(base); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 96 | 	return 0; | 
 | 97 | } | 
 | 98 |  | 
 | 99 | /* | 
 | 100 |  * Quiesce device. | 
 | 101 |  */ | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 102 | static int dasd_ioctl_quiesce(struct dasd_block *block) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 103 | { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 104 | 	unsigned long flags; | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 105 | 	struct dasd_device *base; | 
| Horst Hummel | 138c014 | 2006-06-29 14:58:12 +0200 | [diff] [blame] | 106 |  | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 107 | 	base = block->base; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 108 | 	if (!capable (CAP_SYS_ADMIN)) | 
 | 109 | 		return -EACCES; | 
| Horst Hummel | 138c014 | 2006-06-29 14:58:12 +0200 | [diff] [blame] | 110 |  | 
| Stefan Haberland | ca99dab | 2009-09-11 10:28:30 +0200 | [diff] [blame] | 111 | 	pr_info("%s: The DASD has been put in the quiesce " | 
 | 112 | 		"state\n", dev_name(&base->cdev->dev)); | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 113 | 	spin_lock_irqsave(get_ccwdev_lock(base->cdev), flags); | 
| Stefan Weinhuber | eb6e199 | 2009-12-07 12:51:51 +0100 | [diff] [blame] | 114 | 	dasd_device_set_stop_bits(base, DASD_STOPPED_QUIESCE); | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 115 | 	spin_unlock_irqrestore(get_ccwdev_lock(base->cdev), flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 116 | 	return 0; | 
 | 117 | } | 
 | 118 |  | 
 | 119 |  | 
 | 120 | /* | 
| Stefan Haberland | fc19f38 | 2009-03-26 15:23:49 +0100 | [diff] [blame] | 121 |  * Resume device. | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 122 |  */ | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 123 | static int dasd_ioctl_resume(struct dasd_block *block) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 124 | { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 125 | 	unsigned long flags; | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 126 | 	struct dasd_device *base; | 
| Horst Hummel | 138c014 | 2006-06-29 14:58:12 +0200 | [diff] [blame] | 127 |  | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 128 | 	base = block->base; | 
| Horst Hummel | 138c014 | 2006-06-29 14:58:12 +0200 | [diff] [blame] | 129 | 	if (!capable (CAP_SYS_ADMIN)) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 130 | 		return -EACCES; | 
 | 131 |  | 
| Stefan Haberland | ca99dab | 2009-09-11 10:28:30 +0200 | [diff] [blame] | 132 | 	pr_info("%s: I/O operations have been resumed " | 
 | 133 | 		"on the DASD\n", dev_name(&base->cdev->dev)); | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 134 | 	spin_lock_irqsave(get_ccwdev_lock(base->cdev), flags); | 
| Stefan Weinhuber | eb6e199 | 2009-12-07 12:51:51 +0100 | [diff] [blame] | 135 | 	dasd_device_remove_stop_bits(base, DASD_STOPPED_QUIESCE); | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 136 | 	spin_unlock_irqrestore(get_ccwdev_lock(base->cdev), flags); | 
| Horst Hummel | 138c014 | 2006-06-29 14:58:12 +0200 | [diff] [blame] | 137 |  | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 138 | 	dasd_schedule_block_bh(block); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 139 | 	return 0; | 
 | 140 | } | 
 | 141 |  | 
 | 142 | /* | 
 | 143 |  * performs formatting of _device_ according to _fdata_ | 
 | 144 |  * Note: The discipline's format_function is assumed to deliver formatting | 
 | 145 |  * commands to format a single unit of the device. In terms of the ECKD | 
 | 146 |  * devices this means CCWs are generated to format a single track. | 
 | 147 |  */ | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 148 | static int dasd_format(struct dasd_block *block, struct format_data_t *fdata) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 149 | { | 
 | 150 | 	struct dasd_ccw_req *cqr; | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 151 | 	struct dasd_device *base; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 152 | 	int rc; | 
 | 153 |  | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 154 | 	base = block->base; | 
 | 155 | 	if (base->discipline->format_device == NULL) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 156 | 		return -EPERM; | 
 | 157 |  | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 158 | 	if (base->state != DASD_STATE_BASIC) { | 
| Stefan Haberland | ca99dab | 2009-09-11 10:28:30 +0200 | [diff] [blame] | 159 | 		pr_warning("%s: The DASD cannot be formatted while it is " | 
 | 160 | 			   "enabled\n",  dev_name(&base->cdev->dev)); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 161 | 		return -EBUSY; | 
 | 162 | 	} | 
 | 163 |  | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 164 | 	DBF_DEV_EVENT(DBF_NOTICE, base, | 
| Stefan Weinhuber | b44b0ab3 | 2009-03-26 15:23:47 +0100 | [diff] [blame] | 165 | 		      "formatting units %u to %u (%u B blocks) flags %u", | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 166 | 		      fdata->start_unit, | 
 | 167 | 		      fdata->stop_unit, fdata->blksize, fdata->intensity); | 
 | 168 |  | 
 | 169 | 	/* Since dasdfmt keeps the device open after it was disabled, | 
 | 170 | 	 * there still exists an inode for this device. | 
 | 171 | 	 * We must update i_blkbits, otherwise we might get errors when | 
 | 172 | 	 * enabling the device later. | 
 | 173 | 	 */ | 
 | 174 | 	if (fdata->start_unit == 0) { | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 175 | 		struct block_device *bdev = bdget_disk(block->gdp, 0); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 176 | 		bdev->bd_inode->i_blkbits = blksize_bits(fdata->blksize); | 
 | 177 | 		bdput(bdev); | 
 | 178 | 	} | 
 | 179 |  | 
 | 180 | 	while (fdata->start_unit <= fdata->stop_unit) { | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 181 | 		cqr = base->discipline->format_device(base, fdata); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 182 | 		if (IS_ERR(cqr)) | 
 | 183 | 			return PTR_ERR(cqr); | 
 | 184 | 		rc = dasd_sleep_on_interruptible(cqr); | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 185 | 		dasd_sfree_request(cqr, cqr->memdev); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 186 | 		if (rc) { | 
 | 187 | 			if (rc != -ERESTARTSYS) | 
| Stefan Haberland | ca99dab | 2009-09-11 10:28:30 +0200 | [diff] [blame] | 188 | 				pr_err("%s: Formatting unit %d failed with " | 
 | 189 | 				       "rc=%d\n", dev_name(&base->cdev->dev), | 
 | 190 | 				       fdata->start_unit, rc); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 191 | 			return rc; | 
 | 192 | 		} | 
 | 193 | 		fdata->start_unit++; | 
 | 194 | 	} | 
 | 195 | 	return 0; | 
 | 196 | } | 
 | 197 |  | 
 | 198 | /* | 
 | 199 |  * Format device. | 
 | 200 |  */ | 
 | 201 | static int | 
| Christoph Hellwig | 13c6204 | 2006-03-24 03:15:19 -0800 | [diff] [blame] | 202 | dasd_ioctl_format(struct block_device *bdev, void __user *argp) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 203 | { | 
| Stefan Weinhuber | 65f8da4 | 2011-04-20 10:15:30 +0200 | [diff] [blame] | 204 | 	struct dasd_device *base; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 205 | 	struct format_data_t fdata; | 
| Stefan Weinhuber | 65f8da4 | 2011-04-20 10:15:30 +0200 | [diff] [blame] | 206 | 	int rc; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 207 |  | 
 | 208 | 	if (!capable(CAP_SYS_ADMIN)) | 
 | 209 | 		return -EACCES; | 
| Christoph Hellwig | 13c6204 | 2006-03-24 03:15:19 -0800 | [diff] [blame] | 210 | 	if (!argp) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 211 | 		return -EINVAL; | 
| Stefan Weinhuber | 65f8da4 | 2011-04-20 10:15:30 +0200 | [diff] [blame] | 212 | 	base = dasd_device_from_gendisk(bdev->bd_disk); | 
 | 213 | 	if (!base) | 
 | 214 | 		return -ENODEV; | 
 | 215 | 	if (base->features & DASD_FEATURE_READONLY || | 
 | 216 | 	    test_bit(DASD_FLAG_DEVICE_RO, &base->flags)) { | 
 | 217 | 		dasd_put_device(base); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 218 | 		return -EROFS; | 
| Stefan Weinhuber | 65f8da4 | 2011-04-20 10:15:30 +0200 | [diff] [blame] | 219 | 	} | 
 | 220 | 	if (copy_from_user(&fdata, argp, sizeof(struct format_data_t))) { | 
 | 221 | 		dasd_put_device(base); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 222 | 		return -EFAULT; | 
| Stefan Weinhuber | 65f8da4 | 2011-04-20 10:15:30 +0200 | [diff] [blame] | 223 | 	} | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 224 | 	if (bdev != bdev->bd_contains) { | 
| Stefan Haberland | ca99dab | 2009-09-11 10:28:30 +0200 | [diff] [blame] | 225 | 		pr_warning("%s: The specified DASD is a partition and cannot " | 
 | 226 | 			   "be formatted\n", | 
| Stefan Weinhuber | 65f8da4 | 2011-04-20 10:15:30 +0200 | [diff] [blame] | 227 | 			   dev_name(&base->cdev->dev)); | 
 | 228 | 		dasd_put_device(base); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 229 | 		return -EINVAL; | 
 | 230 | 	} | 
| Stefan Weinhuber | 65f8da4 | 2011-04-20 10:15:30 +0200 | [diff] [blame] | 231 | 	rc = dasd_format(base->block, &fdata); | 
 | 232 | 	dasd_put_device(base); | 
 | 233 | 	return rc; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 234 | } | 
 | 235 |  | 
 | 236 | #ifdef CONFIG_DASD_PROFILE | 
 | 237 | /* | 
 | 238 |  * Reset device profile information | 
 | 239 |  */ | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 240 | static int dasd_ioctl_reset_profile(struct dasd_block *block) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 241 | { | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 242 | 	memset(&block->profile, 0, sizeof(struct dasd_profile_info_t)); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 243 | 	return 0; | 
 | 244 | } | 
 | 245 |  | 
 | 246 | /* | 
 | 247 |  * Return device profile information | 
 | 248 |  */ | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 249 | static int dasd_ioctl_read_profile(struct dasd_block *block, void __user *argp) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 250 | { | 
| Horst Hummel | 9a7af28 | 2006-01-06 00:19:14 -0800 | [diff] [blame] | 251 | 	if (dasd_profile_level == DASD_PROFILE_OFF) | 
 | 252 | 		return -EIO; | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 253 | 	if (copy_to_user(argp, &block->profile, | 
 | 254 | 			 sizeof(struct dasd_profile_info_t))) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 255 | 		return -EFAULT; | 
 | 256 | 	return 0; | 
 | 257 | } | 
 | 258 | #else | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 259 | static int dasd_ioctl_reset_profile(struct dasd_block *block) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 260 | { | 
 | 261 | 	return -ENOSYS; | 
 | 262 | } | 
 | 263 |  | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 264 | static int dasd_ioctl_read_profile(struct dasd_block *block, void __user *argp) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 265 | { | 
 | 266 | 	return -ENOSYS; | 
 | 267 | } | 
 | 268 | #endif | 
 | 269 |  | 
 | 270 | /* | 
 | 271 |  * Return dasd information. Used for BIODASDINFO and BIODASDINFO2. | 
 | 272 |  */ | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 273 | static int dasd_ioctl_information(struct dasd_block *block, | 
 | 274 | 				  unsigned int cmd, void __user *argp) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 275 | { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 276 | 	struct dasd_information2_t *dasd_info; | 
 | 277 | 	unsigned long flags; | 
| Horst Hummel | c6eb7b7 | 2005-09-03 15:57:58 -0700 | [diff] [blame] | 278 | 	int rc; | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 279 | 	struct dasd_device *base; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 280 | 	struct ccw_device *cdev; | 
| Cornelia Huck | 9a92fe4 | 2007-05-10 15:45:42 +0200 | [diff] [blame] | 281 | 	struct ccw_dev_id dev_id; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 282 |  | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 283 | 	base = block->base; | 
| Stefan Haberland | 294001a | 2010-01-27 10:12:35 +0100 | [diff] [blame] | 284 | 	if (!base->discipline || !base->discipline->fill_info) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 285 | 		return -EINVAL; | 
 | 286 |  | 
| Horst Hummel | 554a826 | 2006-03-24 03:15:24 -0800 | [diff] [blame] | 287 | 	dasd_info = kzalloc(sizeof(struct dasd_information2_t), GFP_KERNEL); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 288 | 	if (dasd_info == NULL) | 
 | 289 | 		return -ENOMEM; | 
 | 290 |  | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 291 | 	rc = base->discipline->fill_info(base, dasd_info); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 292 | 	if (rc) { | 
 | 293 | 		kfree(dasd_info); | 
 | 294 | 		return rc; | 
 | 295 | 	} | 
 | 296 |  | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 297 | 	cdev = base->cdev; | 
| Cornelia Huck | 9a92fe4 | 2007-05-10 15:45:42 +0200 | [diff] [blame] | 298 | 	ccw_device_get_id(cdev, &dev_id); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 299 |  | 
| Cornelia Huck | 9a92fe4 | 2007-05-10 15:45:42 +0200 | [diff] [blame] | 300 | 	dasd_info->devno = dev_id.devno; | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 301 | 	dasd_info->schid = _ccw_device_get_subchannel_number(base->cdev); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 302 | 	dasd_info->cu_type = cdev->id.cu_type; | 
 | 303 | 	dasd_info->cu_model = cdev->id.cu_model; | 
 | 304 | 	dasd_info->dev_type = cdev->id.dev_type; | 
 | 305 | 	dasd_info->dev_model = cdev->id.dev_model; | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 306 | 	dasd_info->status = base->state; | 
| Horst Hummel | 5746719 | 2006-02-01 03:06:36 -0800 | [diff] [blame] | 307 | 	/* | 
 | 308 | 	 * The open_count is increased for every opener, that includes | 
 | 309 | 	 * the blkdev_get in dasd_scan_partitions. | 
 | 310 | 	 * This must be hidden from user-space. | 
 | 311 | 	 */ | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 312 | 	dasd_info->open_count = atomic_read(&block->open_count); | 
 | 313 | 	if (!block->bdev) | 
| Horst Hummel | 5746719 | 2006-02-01 03:06:36 -0800 | [diff] [blame] | 314 | 		dasd_info->open_count++; | 
| Horst Hummel | 138c014 | 2006-06-29 14:58:12 +0200 | [diff] [blame] | 315 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 316 | 	/* | 
 | 317 | 	 * check if device is really formatted | 
 | 318 | 	 * LDL / CDL was returned by 'fill_info' | 
 | 319 | 	 */ | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 320 | 	if ((base->state < DASD_STATE_READY) || | 
 | 321 | 	    (dasd_check_blocksize(block->bp_block))) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 322 | 		dasd_info->format = DASD_FORMAT_NONE; | 
| Horst Hummel | f24acd4 | 2005-05-01 08:58:59 -0700 | [diff] [blame] | 323 |  | 
| Horst Hummel | c6eb7b7 | 2005-09-03 15:57:58 -0700 | [diff] [blame] | 324 | 	dasd_info->features |= | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 325 | 		((base->features & DASD_FEATURE_READONLY) != 0); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 326 |  | 
| Stefan Haberland | 294001a | 2010-01-27 10:12:35 +0100 | [diff] [blame] | 327 | 	memcpy(dasd_info->type, base->discipline->name, 4); | 
| Horst Hummel | 554a826 | 2006-03-24 03:15:24 -0800 | [diff] [blame] | 328 |  | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 329 | 	if (block->request_queue->request_fn) { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 330 | 		struct list_head *l; | 
 | 331 | #ifdef DASD_EXTENDED_PROFILING | 
 | 332 | 		{ | 
 | 333 | 			struct list_head *l; | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 334 | 			spin_lock_irqsave(&block->lock, flags); | 
 | 335 | 			list_for_each(l, &block->request_queue->queue_head) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 336 | 				dasd_info->req_queue_len++; | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 337 | 			spin_unlock_irqrestore(&block->lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 338 | 		} | 
 | 339 | #endif				/* DASD_EXTENDED_PROFILING */ | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 340 | 		spin_lock_irqsave(get_ccwdev_lock(base->cdev), flags); | 
 | 341 | 		list_for_each(l, &base->ccw_queue) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 342 | 			dasd_info->chanq_len++; | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 343 | 		spin_unlock_irqrestore(get_ccwdev_lock(base->cdev), | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 344 | 				       flags); | 
 | 345 | 	} | 
 | 346 |  | 
 | 347 | 	rc = 0; | 
| Christoph Hellwig | 13c6204 | 2006-03-24 03:15:19 -0800 | [diff] [blame] | 348 | 	if (copy_to_user(argp, dasd_info, | 
 | 349 | 			 ((cmd == (unsigned int) BIODASDINFO2) ? | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 350 | 			  sizeof(struct dasd_information2_t) : | 
 | 351 | 			  sizeof(struct dasd_information_t)))) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 352 | 		rc = -EFAULT; | 
 | 353 | 	kfree(dasd_info); | 
 | 354 | 	return rc; | 
 | 355 | } | 
 | 356 |  | 
 | 357 | /* | 
 | 358 |  * Set read only | 
 | 359 |  */ | 
 | 360 | static int | 
| Christoph Hellwig | 13c6204 | 2006-03-24 03:15:19 -0800 | [diff] [blame] | 361 | dasd_ioctl_set_ro(struct block_device *bdev, void __user *argp) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 362 | { | 
| Stefan Weinhuber | 65f8da4 | 2011-04-20 10:15:30 +0200 | [diff] [blame] | 363 | 	struct dasd_device *base; | 
 | 364 | 	int intval, rc; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 365 |  | 
 | 366 | 	if (!capable(CAP_SYS_ADMIN)) | 
 | 367 | 		return -EACCES; | 
 | 368 | 	if (bdev != bdev->bd_contains) | 
 | 369 | 		// ro setting is not allowed for partitions | 
 | 370 | 		return -EINVAL; | 
| Heiko Carstens | d2c993d | 2006-07-12 16:41:55 +0200 | [diff] [blame] | 371 | 	if (get_user(intval, (int __user *)argp)) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 372 | 		return -EFAULT; | 
| Stefan Weinhuber | 65f8da4 | 2011-04-20 10:15:30 +0200 | [diff] [blame] | 373 | 	base = dasd_device_from_gendisk(bdev->bd_disk); | 
 | 374 | 	if (!base) | 
 | 375 | 		return -ENODEV; | 
 | 376 | 	if (!intval && test_bit(DASD_FLAG_DEVICE_RO, &base->flags)) { | 
 | 377 | 		dasd_put_device(base); | 
| Stefan Weinhuber | 33b62a3 | 2010-03-08 12:26:24 +0100 | [diff] [blame] | 378 | 		return -EROFS; | 
| Stefan Weinhuber | 65f8da4 | 2011-04-20 10:15:30 +0200 | [diff] [blame] | 379 | 	} | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 380 | 	set_disk_ro(bdev->bd_disk, intval); | 
| Stefan Weinhuber | 65f8da4 | 2011-04-20 10:15:30 +0200 | [diff] [blame] | 381 | 	rc = dasd_set_feature(base->cdev, DASD_FEATURE_READONLY, intval); | 
 | 382 | 	dasd_put_device(base); | 
 | 383 | 	return rc; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 384 | } | 
 | 385 |  | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 386 | static int dasd_ioctl_readall_cmb(struct dasd_block *block, unsigned int cmd, | 
| Heiko Carstens | 8803486 | 2010-01-13 20:44:29 +0100 | [diff] [blame] | 387 | 				  struct cmbdata __user *argp) | 
| Christoph Hellwig | 8b2eb66 | 2006-03-24 03:15:21 -0800 | [diff] [blame] | 388 | { | 
| Christoph Hellwig | 8b2eb66 | 2006-03-24 03:15:21 -0800 | [diff] [blame] | 389 | 	size_t size = _IOC_SIZE(cmd); | 
 | 390 | 	struct cmbdata data; | 
 | 391 | 	int ret; | 
 | 392 |  | 
| Stefan Weinhuber | 8e09f21 | 2008-01-26 14:11:23 +0100 | [diff] [blame] | 393 | 	ret = cmf_readall(block->base->cdev, &data); | 
| Christoph Hellwig | 8b2eb66 | 2006-03-24 03:15:21 -0800 | [diff] [blame] | 394 | 	if (!ret && copy_to_user(argp, &data, min(size, sizeof(*argp)))) | 
 | 395 | 		return -EFAULT; | 
 | 396 | 	return ret; | 
 | 397 | } | 
 | 398 |  | 
| Arnd Bergmann | cfdb00a | 2010-05-31 22:38:40 +0200 | [diff] [blame] | 399 | int dasd_ioctl(struct block_device *bdev, fmode_t mode, | 
 | 400 | 	       unsigned int cmd, unsigned long arg) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 401 | { | 
| Stefan Weinhuber | 65f8da4 | 2011-04-20 10:15:30 +0200 | [diff] [blame] | 402 | 	struct dasd_block *block; | 
 | 403 | 	struct dasd_device *base; | 
| Heiko Carstens | 8803486 | 2010-01-13 20:44:29 +0100 | [diff] [blame] | 404 | 	void __user *argp; | 
| Stefan Weinhuber | 65f8da4 | 2011-04-20 10:15:30 +0200 | [diff] [blame] | 405 | 	int rc; | 
| Heiko Carstens | 8803486 | 2010-01-13 20:44:29 +0100 | [diff] [blame] | 406 |  | 
 | 407 | 	if (is_compat_task()) | 
 | 408 | 		argp = compat_ptr(arg); | 
 | 409 | 	else | 
 | 410 | 		argp = (void __user *)arg; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 411 |  | 
| Christoph Hellwig | 13c6204 | 2006-03-24 03:15:19 -0800 | [diff] [blame] | 412 | 	if ((_IOC_DIR(cmd) != _IOC_NONE) && !arg) { | 
 | 413 | 		PRINT_DEBUG("empty data ptr"); | 
 | 414 | 		return -EINVAL; | 
 | 415 | 	} | 
 | 416 |  | 
| Stefan Weinhuber | 65f8da4 | 2011-04-20 10:15:30 +0200 | [diff] [blame] | 417 | 	base = dasd_device_from_gendisk(bdev->bd_disk); | 
 | 418 | 	if (!base) | 
 | 419 | 		return -ENODEV; | 
 | 420 | 	block = base->block; | 
 | 421 | 	rc = 0; | 
| Christoph Hellwig | 13c6204 | 2006-03-24 03:15:19 -0800 | [diff] [blame] | 422 | 	switch (cmd) { | 
 | 423 | 	case BIODASDDISABLE: | 
| Stefan Weinhuber | 65f8da4 | 2011-04-20 10:15:30 +0200 | [diff] [blame] | 424 | 		rc = dasd_ioctl_disable(bdev); | 
 | 425 | 		break; | 
| Christoph Hellwig | 13c6204 | 2006-03-24 03:15:19 -0800 | [diff] [blame] | 426 | 	case BIODASDENABLE: | 
| Stefan Weinhuber | 65f8da4 | 2011-04-20 10:15:30 +0200 | [diff] [blame] | 427 | 		rc = dasd_ioctl_enable(bdev); | 
 | 428 | 		break; | 
| Christoph Hellwig | 13c6204 | 2006-03-24 03:15:19 -0800 | [diff] [blame] | 429 | 	case BIODASDQUIESCE: | 
| Stefan Weinhuber | 65f8da4 | 2011-04-20 10:15:30 +0200 | [diff] [blame] | 430 | 		rc = dasd_ioctl_quiesce(block); | 
 | 431 | 		break; | 
| Christoph Hellwig | 13c6204 | 2006-03-24 03:15:19 -0800 | [diff] [blame] | 432 | 	case BIODASDRESUME: | 
| Stefan Weinhuber | 65f8da4 | 2011-04-20 10:15:30 +0200 | [diff] [blame] | 433 | 		rc = dasd_ioctl_resume(block); | 
 | 434 | 		break; | 
| Christoph Hellwig | 13c6204 | 2006-03-24 03:15:19 -0800 | [diff] [blame] | 435 | 	case BIODASDFMT: | 
| Stefan Weinhuber | 65f8da4 | 2011-04-20 10:15:30 +0200 | [diff] [blame] | 436 | 		rc = dasd_ioctl_format(bdev, argp); | 
 | 437 | 		break; | 
| Christoph Hellwig | 13c6204 | 2006-03-24 03:15:19 -0800 | [diff] [blame] | 438 | 	case BIODASDINFO: | 
| Stefan Weinhuber | 65f8da4 | 2011-04-20 10:15:30 +0200 | [diff] [blame] | 439 | 		rc = dasd_ioctl_information(block, cmd, argp); | 
 | 440 | 		break; | 
| Christoph Hellwig | 13c6204 | 2006-03-24 03:15:19 -0800 | [diff] [blame] | 441 | 	case BIODASDINFO2: | 
| Stefan Weinhuber | 65f8da4 | 2011-04-20 10:15:30 +0200 | [diff] [blame] | 442 | 		rc = dasd_ioctl_information(block, cmd, argp); | 
 | 443 | 		break; | 
| Christoph Hellwig | 13c6204 | 2006-03-24 03:15:19 -0800 | [diff] [blame] | 444 | 	case BIODASDPRRD: | 
| Stefan Weinhuber | 65f8da4 | 2011-04-20 10:15:30 +0200 | [diff] [blame] | 445 | 		rc = dasd_ioctl_read_profile(block, argp); | 
 | 446 | 		break; | 
| Christoph Hellwig | 13c6204 | 2006-03-24 03:15:19 -0800 | [diff] [blame] | 447 | 	case BIODASDPRRST: | 
| Stefan Weinhuber | 65f8da4 | 2011-04-20 10:15:30 +0200 | [diff] [blame] | 448 | 		rc = dasd_ioctl_reset_profile(block); | 
 | 449 | 		break; | 
| Christoph Hellwig | 13c6204 | 2006-03-24 03:15:19 -0800 | [diff] [blame] | 450 | 	case BLKROSET: | 
| Stefan Weinhuber | 65f8da4 | 2011-04-20 10:15:30 +0200 | [diff] [blame] | 451 | 		rc = dasd_ioctl_set_ro(bdev, argp); | 
 | 452 | 		break; | 
| Christoph Hellwig | 13c6204 | 2006-03-24 03:15:19 -0800 | [diff] [blame] | 453 | 	case DASDAPIVER: | 
| Stefan Weinhuber | 65f8da4 | 2011-04-20 10:15:30 +0200 | [diff] [blame] | 454 | 		rc = dasd_ioctl_api_version(argp); | 
 | 455 | 		break; | 
| Christoph Hellwig | 8b2eb66 | 2006-03-24 03:15:21 -0800 | [diff] [blame] | 456 | 	case BIODASDCMFENABLE: | 
| Stefan Weinhuber | 65f8da4 | 2011-04-20 10:15:30 +0200 | [diff] [blame] | 457 | 		rc = enable_cmf(base->cdev); | 
 | 458 | 		break; | 
| Christoph Hellwig | 8b2eb66 | 2006-03-24 03:15:21 -0800 | [diff] [blame] | 459 | 	case BIODASDCMFDISABLE: | 
| Stefan Weinhuber | 65f8da4 | 2011-04-20 10:15:30 +0200 | [diff] [blame] | 460 | 		rc = disable_cmf(base->cdev); | 
 | 461 | 		break; | 
| Christoph Hellwig | 8b2eb66 | 2006-03-24 03:15:21 -0800 | [diff] [blame] | 462 | 	case BIODASDREADALLCMB: | 
| Stefan Weinhuber | 65f8da4 | 2011-04-20 10:15:30 +0200 | [diff] [blame] | 463 | 		rc = dasd_ioctl_readall_cmb(block, cmd, argp); | 
 | 464 | 		break; | 
| Christoph Hellwig | 13c6204 | 2006-03-24 03:15:19 -0800 | [diff] [blame] | 465 | 	default: | 
| Christoph Hellwig | 1107ccf | 2006-03-24 03:15:20 -0800 | [diff] [blame] | 466 | 		/* if the discipline has an ioctl method try it. */ | 
| Stefan Weinhuber | 65f8da4 | 2011-04-20 10:15:30 +0200 | [diff] [blame] | 467 | 		if (base->discipline->ioctl) { | 
 | 468 | 			rc = base->discipline->ioctl(block, cmd, argp); | 
 | 469 | 			if (rc == -ENOIOCTLCMD) | 
 | 470 | 				rc = -EINVAL; | 
 | 471 | 		} else | 
 | 472 | 			rc = -EINVAL; | 
| Christoph Hellwig | 13c6204 | 2006-03-24 03:15:19 -0800 | [diff] [blame] | 473 | 	} | 
| Stefan Weinhuber | 65f8da4 | 2011-04-20 10:15:30 +0200 | [diff] [blame] | 474 | 	dasd_put_device(base); | 
 | 475 | 	return rc; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 476 | } |