diff options
author | Thomas Gleixner <tglx@tglx.tec.linutronix.de> | 2005-07-15 02:14:44 -0400 |
---|---|---|
committer | Thomas Gleixner <tglx@mtd.linutronix.de> | 2005-07-15 02:14:44 -0400 |
commit | 5d157885f383ccc0660c011fa488ae4edb77ab16 (patch) | |
tree | 0caba31219184fcf47fb7f91ef330217812cc149 /fs | |
parent | ba460e48064edeb57e3398eb8972c58de33f11ea (diff) |
[JFFS2] Fix node allocation leak
In the rare case of failing to write the cleanmarker
the allocated node was not freed.
Pointed out by Forrest Zhao
Initial cleanup by Joern Engel
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Diffstat (limited to 'fs')
-rw-r--r-- | fs/jffs2/erase.c | 174 |
1 files changed, 92 insertions, 82 deletions
diff --git a/fs/jffs2/erase.c b/fs/jffs2/erase.c index 6a4c0a3685da..787d84ac2bcd 100644 --- a/fs/jffs2/erase.c +++ b/fs/jffs2/erase.c | |||
@@ -7,7 +7,7 @@ | |||
7 | * | 7 | * |
8 | * For licensing information, see the file 'LICENCE' in this directory. | 8 | * For licensing information, see the file 'LICENCE' in this directory. |
9 | * | 9 | * |
10 | * $Id: erase.c,v 1.76 2005/05/03 15:11:40 dedekind Exp $ | 10 | * $Id: erase.c,v 1.80 2005/07/14 19:46:24 joern Exp $ |
11 | * | 11 | * |
12 | */ | 12 | */ |
13 | 13 | ||
@@ -300,100 +300,86 @@ static void jffs2_free_all_node_refs(struct jffs2_sb_info *c, struct jffs2_erase | |||
300 | jeb->last_node = NULL; | 300 | jeb->last_node = NULL; |
301 | } | 301 | } |
302 | 302 | ||
303 | static void jffs2_mark_erased_block(struct jffs2_sb_info *c, struct jffs2_eraseblock *jeb) | 303 | static int jffs2_block_check_erase(struct jffs2_sb_info *c, struct jffs2_eraseblock *jeb, uint32_t *bad_offset) |
304 | { | 304 | { |
305 | struct jffs2_raw_node_ref *marker_ref = NULL; | 305 | void *ebuf; |
306 | unsigned char *ebuf; | 306 | uint32_t ofs; |
307 | size_t retlen; | 307 | size_t retlen; |
308 | int ret; | 308 | int ret = -EIO; |
309 | uint32_t bad_offset; | 309 | |
310 | |||
311 | if ((!jffs2_cleanmarker_oob(c)) && (c->cleanmarker_size > 0)) { | ||
312 | marker_ref = jffs2_alloc_raw_node_ref(); | ||
313 | if (!marker_ref) { | ||
314 | printk(KERN_WARNING "Failed to allocate raw node ref for clean marker\n"); | ||
315 | /* Stick it back on the list from whence it came and come back later */ | ||
316 | jffs2_erase_pending_trigger(c); | ||
317 | spin_lock(&c->erase_completion_lock); | ||
318 | list_add(&jeb->list, &c->erase_complete_list); | ||
319 | spin_unlock(&c->erase_completion_lock); | ||
320 | return; | ||
321 | } | ||
322 | } | ||
323 | ebuf = kmalloc(PAGE_SIZE, GFP_KERNEL); | 310 | ebuf = kmalloc(PAGE_SIZE, GFP_KERNEL); |
324 | if (!ebuf) { | 311 | if (!ebuf) { |
325 | printk(KERN_WARNING "Failed to allocate page buffer for verifying erase at 0x%08x. Assuming it worked\n", jeb->offset); | 312 | printk(KERN_WARNING "Failed to allocate page buffer for verifying erase at 0x%08x. Refiling\n", jeb->offset); |
326 | } else { | 313 | return -EAGAIN; |
327 | uint32_t ofs = jeb->offset; | 314 | } |
328 | 315 | ||
329 | D1(printk(KERN_DEBUG "Verifying erase at 0x%08x\n", jeb->offset)); | 316 | D1(printk(KERN_DEBUG "Verifying erase at 0x%08x\n", jeb->offset)); |
330 | while(ofs < jeb->offset + c->sector_size) { | ||
331 | uint32_t readlen = min((uint32_t)PAGE_SIZE, jeb->offset + c->sector_size - ofs); | ||
332 | int i; | ||
333 | 317 | ||
334 | bad_offset = ofs; | 318 | for (ofs = jeb->offset; ofs < jeb->offset + c->sector_size; ) { |
319 | uint32_t readlen = min((uint32_t)PAGE_SIZE, jeb->offset + c->sector_size - ofs); | ||
320 | int i; | ||
335 | 321 | ||
336 | ret = c->mtd->read(c->mtd, ofs, readlen, &retlen, ebuf); | 322 | *bad_offset = ofs; |
337 | 323 | ||
338 | if (ret) { | 324 | ret = jffs2_flash_read(c, ofs, readlen, &retlen, ebuf); |
339 | printk(KERN_WARNING "Read of newly-erased block at 0x%08x failed: %d. Putting on bad_list\n", ofs, ret); | 325 | if (ret) { |
340 | goto bad; | 326 | printk(KERN_WARNING "Read of newly-erased block at 0x%08x failed: %d. Putting on bad_list\n", ofs, ret); |
341 | } | 327 | goto fail; |
342 | if (retlen != readlen) { | 328 | } |
343 | printk(KERN_WARNING "Short read from newly-erased block at 0x%08x. Wanted %d, got %zd\n", ofs, readlen, retlen); | 329 | if (retlen != readlen) { |
344 | goto bad; | 330 | printk(KERN_WARNING "Short read from newly-erased block at 0x%08x. Wanted %d, got %zd\n", ofs, readlen, retlen); |
345 | } | 331 | goto fail; |
346 | for (i=0; i<readlen; i += sizeof(unsigned long)) { | 332 | } |
347 | /* It's OK. We know it's properly aligned */ | 333 | for (i=0; i<readlen; i += sizeof(unsigned long)) { |
348 | unsigned long datum = *(unsigned long *)(&ebuf[i]); | 334 | /* It's OK. We know it's properly aligned */ |
349 | if (datum + 1) { | 335 | unsigned long *datum = ebuf + i; |
350 | bad_offset += i; | 336 | if (*datum + 1) { |
351 | printk(KERN_WARNING "Newly-erased block contained word 0x%lx at offset 0x%08x\n", datum, bad_offset); | 337 | *bad_offset += i; |
352 | bad: | 338 | printk(KERN_WARNING "Newly-erased block contained word 0x%lx at offset 0x%08x\n", *datum, *bad_offset); |
353 | if ((!jffs2_cleanmarker_oob(c)) && (c->cleanmarker_size > 0)) | 339 | goto fail; |
354 | jffs2_free_raw_node_ref(marker_ref); | ||
355 | kfree(ebuf); | ||
356 | bad2: | ||
357 | spin_lock(&c->erase_completion_lock); | ||
358 | /* Stick it on a list (any list) so | ||
359 | erase_failed can take it right off | ||
360 | again. Silly, but shouldn't happen | ||
361 | often. */ | ||
362 | list_add(&jeb->list, &c->erasing_list); | ||
363 | spin_unlock(&c->erase_completion_lock); | ||
364 | jffs2_erase_failed(c, jeb, bad_offset); | ||
365 | return; | ||
366 | } | ||
367 | } | 340 | } |
368 | ofs += readlen; | ||
369 | cond_resched(); | ||
370 | } | 341 | } |
371 | kfree(ebuf); | 342 | ofs += readlen; |
343 | cond_resched(); | ||
372 | } | 344 | } |
345 | ret = 0; | ||
346 | fail: | ||
347 | kfree(ebuf); | ||
348 | return ret; | ||
349 | } | ||
373 | 350 | ||
374 | bad_offset = jeb->offset; | 351 | static void jffs2_mark_erased_block(struct jffs2_sb_info *c, struct jffs2_eraseblock *jeb) |
352 | { | ||
353 | struct jffs2_raw_node_ref *marker_ref = NULL; | ||
354 | size_t retlen; | ||
355 | int ret; | ||
356 | uint32_t bad_offset; | ||
357 | |||
358 | switch (jffs2_block_check_erase(c, jeb, &bad_offset)) { | ||
359 | case -EAGAIN: goto refile; | ||
360 | case -EIO: goto filebad; | ||
361 | } | ||
375 | 362 | ||
376 | /* Write the erase complete marker */ | 363 | /* Write the erase complete marker */ |
377 | D1(printk(KERN_DEBUG "Writing erased marker to block at 0x%08x\n", jeb->offset)); | 364 | D1(printk(KERN_DEBUG "Writing erased marker to block at 0x%08x\n", jeb->offset)); |
378 | if (jffs2_cleanmarker_oob(c)) { | 365 | bad_offset = jeb->offset; |
379 | 366 | ||
380 | if (jffs2_write_nand_cleanmarker(c, jeb)) | 367 | /* Cleanmarker in oob area or no cleanmarker at all ? */ |
381 | goto bad2; | 368 | if (jffs2_cleanmarker_oob(c) || c->cleanmarker_size == 0) { |
382 | |||
383 | jeb->first_node = jeb->last_node = NULL; | ||
384 | 369 | ||
385 | jeb->free_size = c->sector_size; | 370 | if (jffs2_cleanmarker_oob(c)) { |
386 | jeb->used_size = 0; | 371 | if (jffs2_write_nand_cleanmarker(c, jeb)) |
387 | jeb->dirty_size = 0; | 372 | goto filebad; |
388 | jeb->wasted_size = 0; | 373 | } |
389 | } else if (c->cleanmarker_size == 0) { | ||
390 | jeb->first_node = jeb->last_node = NULL; | ||
391 | 374 | ||
375 | jeb->first_node = jeb->last_node = NULL; | ||
392 | jeb->free_size = c->sector_size; | 376 | jeb->free_size = c->sector_size; |
393 | jeb->used_size = 0; | 377 | jeb->used_size = 0; |
394 | jeb->dirty_size = 0; | 378 | jeb->dirty_size = 0; |
395 | jeb->wasted_size = 0; | 379 | jeb->wasted_size = 0; |
380 | |||
396 | } else { | 381 | } else { |
382 | |||
397 | struct kvec vecs[1]; | 383 | struct kvec vecs[1]; |
398 | struct jffs2_unknown_node marker = { | 384 | struct jffs2_unknown_node marker = { |
399 | .magic = cpu_to_je16(JFFS2_MAGIC_BITMASK), | 385 | .magic = cpu_to_je16(JFFS2_MAGIC_BITMASK), |
@@ -401,21 +387,28 @@ static void jffs2_mark_erased_block(struct jffs2_sb_info *c, struct jffs2_eraseb | |||
401 | .totlen = cpu_to_je32(c->cleanmarker_size) | 387 | .totlen = cpu_to_je32(c->cleanmarker_size) |
402 | }; | 388 | }; |
403 | 389 | ||
390 | marker_ref = jffs2_alloc_raw_node_ref(); | ||
391 | if (!marker_ref) { | ||
392 | printk(KERN_WARNING "Failed to allocate raw node ref for clean marker. Refiling\n"); | ||
393 | goto refile; | ||
394 | } | ||
395 | |||
404 | marker.hdr_crc = cpu_to_je32(crc32(0, &marker, sizeof(struct jffs2_unknown_node)-4)); | 396 | marker.hdr_crc = cpu_to_je32(crc32(0, &marker, sizeof(struct jffs2_unknown_node)-4)); |
405 | 397 | ||
406 | vecs[0].iov_base = (unsigned char *) ▮ | 398 | vecs[0].iov_base = (unsigned char *) ▮ |
407 | vecs[0].iov_len = sizeof(marker); | 399 | vecs[0].iov_len = sizeof(marker); |
408 | ret = jffs2_flash_direct_writev(c, vecs, 1, jeb->offset, &retlen); | 400 | ret = jffs2_flash_direct_writev(c, vecs, 1, jeb->offset, &retlen); |
409 | 401 | ||
410 | if (ret) { | 402 | if (ret || retlen != sizeof(marker)) { |
411 | printk(KERN_WARNING "Write clean marker to block at 0x%08x failed: %d\n", | 403 | if (ret) |
412 | jeb->offset, ret); | 404 | printk(KERN_WARNING "Write clean marker to block at 0x%08x failed: %d\n", |
413 | goto bad2; | 405 | jeb->offset, ret); |
414 | } | 406 | else |
415 | if (retlen != sizeof(marker)) { | 407 | printk(KERN_WARNING "Short write to newly-erased block at 0x%08x: Wanted %zd, got %zd\n", |
416 | printk(KERN_WARNING "Short write to newly-erased block at 0x%08x: Wanted %zd, got %zd\n", | 408 | jeb->offset, sizeof(marker), retlen); |
417 | jeb->offset, sizeof(marker), retlen); | 409 | |
418 | goto bad2; | 410 | jffs2_free_raw_node_ref(marker_ref); |
411 | goto filebad; | ||
419 | } | 412 | } |
420 | 413 | ||
421 | marker_ref->next_in_ino = NULL; | 414 | marker_ref->next_in_ino = NULL; |
@@ -444,5 +437,22 @@ static void jffs2_mark_erased_block(struct jffs2_sb_info *c, struct jffs2_eraseb | |||
444 | c->nr_free_blocks++; | 437 | c->nr_free_blocks++; |
445 | spin_unlock(&c->erase_completion_lock); | 438 | spin_unlock(&c->erase_completion_lock); |
446 | wake_up(&c->erase_wait); | 439 | wake_up(&c->erase_wait); |
447 | } | 440 | return; |
441 | |||
442 | filebad: | ||
443 | spin_lock(&c->erase_completion_lock); | ||
444 | /* Stick it on a list (any list) so erase_failed can take it | ||
445 | right off again. Silly, but shouldn't happen often. */ | ||
446 | list_add(&jeb->list, &c->erasing_list); | ||
447 | spin_unlock(&c->erase_completion_lock); | ||
448 | jffs2_erase_failed(c, jeb, bad_offset); | ||
449 | return; | ||
448 | 450 | ||
451 | refile: | ||
452 | /* Stick it back on the list from whence it came and come back later */ | ||
453 | jffs2_erase_pending_trigger(c); | ||
454 | spin_lock(&c->erase_completion_lock); | ||
455 | list_add(&jeb->list, &c->erase_complete_list); | ||
456 | spin_unlock(&c->erase_completion_lock); | ||
457 | return; | ||
458 | } | ||