aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVaughan Cao <vaughan.cao@oracle.com>2013-08-28 22:00:36 -0400
committerJames Bottomley <JBottomley@Parallels.com>2013-09-03 10:28:09 -0400
commit15b06f9a02406e5460001db6d5af5c738cd3d4e7 (patch)
treea62209c714df581942342acb0a167889fe2e25da
parenta027b5b90eb6b5ec5c5890a5e4e198f458ae94cb (diff)
[SCSI] sg: use rwsem to solve race during exclusive open
A race condition may happen if two threads are both trying to open the same sg with O_EXCL simultaneously. It's possible that they both find fsds list is empty and get_exclude(sdp) returns 0, then they both call set_exclude() and break out from wait_event_interruptible and resume open. Now use rwsem to protect this process. Exclusive open gets write lock and others get read lock. The lock will be held until file descriptor is closed. This also leads 'exclude' only a status rather than a check mark. Signed-off-by: Vaughan Cao <vaughan.cao@oracle.com> Acked-by: Douglas Gilbert <dgilbert@interlog.com> Signed-off-by: James Bottomley <JBottomley@Parallels.com>
-rw-r--r--drivers/scsi/sg.c79
1 files changed, 41 insertions, 38 deletions
diff --git a/drivers/scsi/sg.c b/drivers/scsi/sg.c
index df5e961484e1..4efa9b5884b7 100644
--- a/drivers/scsi/sg.c
+++ b/drivers/scsi/sg.c
@@ -170,11 +170,11 @@ typedef struct sg_fd { /* holds the state of a file descriptor */
170 170
171typedef struct sg_device { /* holds the state of each scsi generic device */ 171typedef struct sg_device { /* holds the state of each scsi generic device */
172 struct scsi_device *device; 172 struct scsi_device *device;
173 wait_queue_head_t o_excl_wait; /* queue open() when O_EXCL in use */
174 int sg_tablesize; /* adapter's max scatter-gather table size */ 173 int sg_tablesize; /* adapter's max scatter-gather table size */
175 u32 index; /* device index number */ 174 u32 index; /* device index number */
176 /* sfds is protected by sg_index_lock */ 175 /* sfds is protected by sg_index_lock */
177 struct list_head sfds; 176 struct list_head sfds;
177 struct rw_semaphore o_sem; /* exclude open should hold this rwsem */
178 volatile char detached; /* 0->attached, 1->detached pending removal */ 178 volatile char detached; /* 0->attached, 1->detached pending removal */
179 /* exclude protected by sg_open_exclusive_lock */ 179 /* exclude protected by sg_open_exclusive_lock */
180 char exclude; /* opened for exclusive access */ 180 char exclude; /* opened for exclusive access */
@@ -265,7 +265,6 @@ sg_open(struct inode *inode, struct file *filp)
265 struct request_queue *q; 265 struct request_queue *q;
266 Sg_device *sdp; 266 Sg_device *sdp;
267 Sg_fd *sfp; 267 Sg_fd *sfp;
268 int res;
269 int retval; 268 int retval;
270 269
271 nonseekable_open(inode, filp); 270 nonseekable_open(inode, filp);
@@ -294,35 +293,35 @@ sg_open(struct inode *inode, struct file *filp)
294 goto error_out; 293 goto error_out;
295 } 294 }
296 295
297 if (flags & O_EXCL) { 296 if ((flags & O_EXCL) && (O_RDONLY == (flags & O_ACCMODE))) {
298 if (O_RDONLY == (flags & O_ACCMODE)) { 297 retval = -EPERM; /* Can't lock it with read only access */
299 retval = -EPERM; /* Can't lock it with read only access */ 298 goto error_out;
300 goto error_out; 299 }
301 } 300 if (flags & O_NONBLOCK) {
302 if (!sfds_list_empty(sdp) && (flags & O_NONBLOCK)) { 301 if (flags & O_EXCL) {
303 retval = -EBUSY; 302 if (!down_write_trylock(&sdp->o_sem)) {
304 goto error_out; 303 retval = -EBUSY;
305 } 304 goto error_out;
306 res = wait_event_interruptible(sdp->o_excl_wait, 305 }
307 ((!sfds_list_empty(sdp) || get_exclude(sdp)) ? 0 : set_exclude(sdp, 1))); 306 } else {
308 if (res) { 307 if (!down_read_trylock(&sdp->o_sem)) {
309 retval = res; /* -ERESTARTSYS because signal hit process */ 308 retval = -EBUSY;
310 goto error_out; 309 goto error_out;
311 } 310 }
312 } else if (get_exclude(sdp)) { /* some other fd has an exclusive lock on dev */
313 if (flags & O_NONBLOCK) {
314 retval = -EBUSY;
315 goto error_out;
316 }
317 res = wait_event_interruptible(sdp->o_excl_wait, !get_exclude(sdp));
318 if (res) {
319 retval = res; /* -ERESTARTSYS because signal hit process */
320 goto error_out;
321 } 311 }
312 } else {
313 if (flags & O_EXCL)
314 down_write(&sdp->o_sem);
315 else
316 down_read(&sdp->o_sem);
322 } 317 }
318 /* Since write lock is held, no need to check sfd_list */
319 if (flags & O_EXCL)
320 set_exclude(sdp, 1);
321
323 if (sdp->detached) { 322 if (sdp->detached) {
324 retval = -ENODEV; 323 retval = -ENODEV;
325 goto error_out; 324 goto sem_out;
326 } 325 }
327 if (sfds_list_empty(sdp)) { /* no existing opens on this device */ 326 if (sfds_list_empty(sdp)) { /* no existing opens on this device */
328 sdp->sgdebug = 0; 327 sdp->sgdebug = 0;
@@ -331,17 +330,18 @@ sg_open(struct inode *inode, struct file *filp)
331 } 330 }
332 if ((sfp = sg_add_sfp(sdp, dev))) 331 if ((sfp = sg_add_sfp(sdp, dev)))
333 filp->private_data = sfp; 332 filp->private_data = sfp;
333 /* retval is already provably zero at this point because of the
334 * check after retval = scsi_autopm_get_device(sdp->device))
335 */
334 else { 336 else {
337 retval = -ENOMEM;
338sem_out:
335 if (flags & O_EXCL) { 339 if (flags & O_EXCL) {
336 set_exclude(sdp, 0); /* undo if error */ 340 set_exclude(sdp, 0); /* undo if error */
337 wake_up_interruptible(&sdp->o_excl_wait); 341 up_write(&sdp->o_sem);
338 } 342 } else
339 retval = -ENOMEM; 343 up_read(&sdp->o_sem);
340 goto error_out;
341 }
342 retval = 0;
343error_out: 344error_out:
344 if (retval) {
345 scsi_autopm_put_device(sdp->device); 345 scsi_autopm_put_device(sdp->device);
346sdp_put: 346sdp_put:
347 scsi_device_put(sdp->device); 347 scsi_device_put(sdp->device);
@@ -358,13 +358,18 @@ sg_release(struct inode *inode, struct file *filp)
358{ 358{
359 Sg_device *sdp; 359 Sg_device *sdp;
360 Sg_fd *sfp; 360 Sg_fd *sfp;
361 int excl;
361 362
362 if ((!(sfp = (Sg_fd *) filp->private_data)) || (!(sdp = sfp->parentdp))) 363 if ((!(sfp = (Sg_fd *) filp->private_data)) || (!(sdp = sfp->parentdp)))
363 return -ENXIO; 364 return -ENXIO;
364 SCSI_LOG_TIMEOUT(3, printk("sg_release: %s\n", sdp->disk->disk_name)); 365 SCSI_LOG_TIMEOUT(3, printk("sg_release: %s\n", sdp->disk->disk_name));
365 366
367 excl = get_exclude(sdp);
366 set_exclude(sdp, 0); 368 set_exclude(sdp, 0);
367 wake_up_interruptible(&sdp->o_excl_wait); 369 if (excl)
370 up_write(&sdp->o_sem);
371 else
372 up_read(&sdp->o_sem);
368 373
369 scsi_autopm_put_device(sdp->device); 374 scsi_autopm_put_device(sdp->device);
370 kref_put(&sfp->f_ref, sg_remove_sfp); 375 kref_put(&sfp->f_ref, sg_remove_sfp);
@@ -1416,7 +1421,7 @@ static Sg_device *sg_alloc(struct gendisk *disk, struct scsi_device *scsidp)
1416 sdp->disk = disk; 1421 sdp->disk = disk;
1417 sdp->device = scsidp; 1422 sdp->device = scsidp;
1418 INIT_LIST_HEAD(&sdp->sfds); 1423 INIT_LIST_HEAD(&sdp->sfds);
1419 init_waitqueue_head(&sdp->o_excl_wait); 1424 init_rwsem(&sdp->o_sem);
1420 sdp->sg_tablesize = queue_max_segments(q); 1425 sdp->sg_tablesize = queue_max_segments(q);
1421 sdp->index = k; 1426 sdp->index = k;
1422 kref_init(&sdp->d_ref); 1427 kref_init(&sdp->d_ref);
@@ -2127,13 +2132,11 @@ static void sg_remove_sfp_usercontext(struct work_struct *work)
2127static void sg_remove_sfp(struct kref *kref) 2132static void sg_remove_sfp(struct kref *kref)
2128{ 2133{
2129 struct sg_fd *sfp = container_of(kref, struct sg_fd, f_ref); 2134 struct sg_fd *sfp = container_of(kref, struct sg_fd, f_ref);
2130 struct sg_device *sdp = sfp->parentdp;
2131 unsigned long iflags; 2135 unsigned long iflags;
2132 2136
2133 write_lock_irqsave(&sg_index_lock, iflags); 2137 write_lock_irqsave(&sg_index_lock, iflags);
2134 list_del(&sfp->sfd_siblings); 2138 list_del(&sfp->sfd_siblings);
2135 write_unlock_irqrestore(&sg_index_lock, iflags); 2139 write_unlock_irqrestore(&sg_index_lock, iflags);
2136 wake_up_interruptible(&sdp->o_excl_wait);
2137 2140
2138 INIT_WORK(&sfp->ew.work, sg_remove_sfp_usercontext); 2141 INIT_WORK(&sfp->ew.work, sg_remove_sfp_usercontext);
2139 schedule_work(&sfp->ew.work); 2142 schedule_work(&sfp->ew.work);