aboutsummaryrefslogtreecommitdiffstats
path: root/fs
diff options
context:
space:
mode:
authorJan Kara <jack@suse.cz>2007-06-16 13:16:14 -0400
committerLinus Torvalds <torvalds@woody.linux-foundation.org>2007-06-16 16:16:16 -0400
commit74584ae509befc2ed711810e7df4b075473869b2 (patch)
tree9cb1b4a06b8583895c96d9b1a959d81df3950a06 /fs
parent4b356be019d0c28f67af02809df7072c1c8f7d32 (diff)
udf: fix possible leakage of blocks
We have to take care that when we call udf_discard_prealloc() from udf_clear_inode() we have to write inode ourselves afterwards (otherwise, some changes might be lost leading to leakage of blocks, use of free blocks or improperly aligned extents). Also udf_discard_prealloc() does two different things - it removes preallocated blocks and truncates the last extent to exactly match i_size. We move the latter functionality to udf_truncate_tail_extent(), call udf_discard_prealloc() when last reference to a file is dropped and call udf_truncate_tail_extent() when inode is being removed from inode cache (udf_clear_inode() call). We cannot call udf_truncate_tail_extent() earlier as subsequent open+write would find the last block of the file mapped and happily write to the end of it, although the last extent says it's shorter. [akpm@linux-foundation.org: Make checkpatch.pl happier] Signed-off-by: Jan Kara <jack@suse.cz> Cc: Eric Sandeen <sandeen@sandeen.net> Cc: Cyrill Gorcunov <gorcunov@gmail.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'fs')
-rw-r--r--fs/udf/inode.c11
-rw-r--r--fs/udf/truncate.c79
-rw-r--r--fs/udf/udfdecl.h1
3 files changed, 74 insertions, 17 deletions
diff --git a/fs/udf/inode.c b/fs/udf/inode.c
index 1f0129405cf4..bf7de0bdbab3 100644
--- a/fs/udf/inode.c
+++ b/fs/udf/inode.c
@@ -100,14 +100,23 @@ no_delete:
100 clear_inode(inode); 100 clear_inode(inode);
101} 101}
102 102
103/*
104 * If we are going to release inode from memory, we discard preallocation and
105 * truncate last inode extent to proper length. We could use drop_inode() but
106 * it's called under inode_lock and thus we cannot mark inode dirty there. We
107 * use clear_inode() but we have to make sure to write inode as it's not written
108 * automatically.
109 */
103void udf_clear_inode(struct inode *inode) 110void udf_clear_inode(struct inode *inode)
104{ 111{
105 if (!(inode->i_sb->s_flags & MS_RDONLY)) { 112 if (!(inode->i_sb->s_flags & MS_RDONLY)) {
106 lock_kernel(); 113 lock_kernel();
114 /* Discard preallocation for directories, symlinks, etc. */
107 udf_discard_prealloc(inode); 115 udf_discard_prealloc(inode);
116 udf_truncate_tail_extent(inode);
108 unlock_kernel(); 117 unlock_kernel();
118 write_inode_now(inode, 1);
109 } 119 }
110
111 kfree(UDF_I_DATA(inode)); 120 kfree(UDF_I_DATA(inode));
112 UDF_I_DATA(inode) = NULL; 121 UDF_I_DATA(inode) = NULL;
113} 122}
diff --git a/fs/udf/truncate.c b/fs/udf/truncate.c
index 77975ae291a5..60d277644248 100644
--- a/fs/udf/truncate.c
+++ b/fs/udf/truncate.c
@@ -61,7 +61,11 @@ static void extent_trunc(struct inode * inode, struct extent_position *epos,
61 } 61 }
62} 62}
63 63
64void udf_discard_prealloc(struct inode * inode) 64/*
65 * Truncate the last extent to match i_size. This function assumes
66 * that preallocation extent is already truncated.
67 */
68void udf_truncate_tail_extent(struct inode *inode)
65{ 69{
66 struct extent_position epos = { NULL, 0, {0, 0}}; 70 struct extent_position epos = { NULL, 0, {0, 0}};
67 kernel_lb_addr eloc; 71 kernel_lb_addr eloc;
@@ -71,7 +75,10 @@ void udf_discard_prealloc(struct inode * inode)
71 int adsize; 75 int adsize;
72 76
73 if (UDF_I_ALLOCTYPE(inode) == ICBTAG_FLAG_AD_IN_ICB || 77 if (UDF_I_ALLOCTYPE(inode) == ICBTAG_FLAG_AD_IN_ICB ||
74 inode->i_size == UDF_I_LENEXTENTS(inode)) 78 inode->i_size == UDF_I_LENEXTENTS(inode))
79 return;
80 /* Are we going to delete the file anyway? */
81 if (inode->i_nlink == 0)
75 return; 82 return;
76 83
77 if (UDF_I_ALLOCTYPE(inode) == ICBTAG_FLAG_AD_SHORT) 84 if (UDF_I_ALLOCTYPE(inode) == ICBTAG_FLAG_AD_SHORT)
@@ -79,36 +86,76 @@ void udf_discard_prealloc(struct inode * inode)
79 else if (UDF_I_ALLOCTYPE(inode) == ICBTAG_FLAG_AD_LONG) 86 else if (UDF_I_ALLOCTYPE(inode) == ICBTAG_FLAG_AD_LONG)
80 adsize = sizeof(long_ad); 87 adsize = sizeof(long_ad);
81 else 88 else
82 adsize = 0; 89 BUG();
83
84 epos.block = UDF_I_LOCATION(inode);
85 90
86 /* Find the last extent in the file */ 91 /* Find the last extent in the file */
87 while ((netype = udf_next_aext(inode, &epos, &eloc, &elen, 1)) != -1) 92 while ((netype = udf_next_aext(inode, &epos, &eloc, &elen, 1)) != -1)
88 { 93 {
89 etype = netype; 94 etype = netype;
90 lbcount += elen; 95 lbcount += elen;
91 if (lbcount > inode->i_size && lbcount - elen < inode->i_size) 96 if (lbcount > inode->i_size) {
92 { 97 if (lbcount - inode->i_size >= inode->i_sb->s_blocksize)
93 WARN_ON(lbcount - inode->i_size >= inode->i_sb->s_blocksize); 98 printk(KERN_WARNING
99 "udf_truncate_tail_extent(): Too long "
100 "extent after EOF in inode %u: i_size: "
101 "%Ld lbcount: %Ld extent %u+%u\n",
102 (unsigned)inode->i_ino,
103 (long long)inode->i_size,
104 (long long)lbcount,
105 (unsigned)eloc.logicalBlockNum,
106 (unsigned)elen);
94 nelen = elen - (lbcount - inode->i_size); 107 nelen = elen - (lbcount - inode->i_size);
95 epos.offset -= adsize; 108 epos.offset -= adsize;
96 extent_trunc(inode, &epos, eloc, etype, elen, nelen); 109 extent_trunc(inode, &epos, eloc, etype, elen, nelen);
97 epos.offset += adsize; 110 epos.offset += adsize;
98 lbcount = inode->i_size; 111 if (udf_next_aext(inode, &epos, &eloc, &elen, 1) != -1)
112 printk(KERN_ERR "udf_truncate_tail_extent(): "
113 "Extent after EOF in inode %u.\n",
114 (unsigned)inode->i_ino);
115 break;
99 } 116 }
100 } 117 }
118 /* This inode entry is in-memory only and thus we don't have to mark
119 * the inode dirty */
120 UDF_I_LENEXTENTS(inode) = inode->i_size;
121 brelse(epos.bh);
122}
123
124void udf_discard_prealloc(struct inode *inode)
125{
126 struct extent_position epos = { NULL, 0, {0, 0}};
127 kernel_lb_addr eloc;
128 uint32_t elen;
129 uint64_t lbcount = 0;
130 int8_t etype = -1, netype;
131 int adsize;
132
133 if (UDF_I_ALLOCTYPE(inode) == ICBTAG_FLAG_AD_IN_ICB ||
134 inode->i_size == UDF_I_LENEXTENTS(inode))
135 return;
136
137 if (UDF_I_ALLOCTYPE(inode) == ICBTAG_FLAG_AD_SHORT)
138 adsize = sizeof(short_ad);
139 else if (UDF_I_ALLOCTYPE(inode) == ICBTAG_FLAG_AD_LONG)
140 adsize = sizeof(long_ad);
141 else
142 adsize = 0;
143
144 epos.block = UDF_I_LOCATION(inode);
145
146 /* Find the last extent in the file */
147 while ((netype = udf_next_aext(inode, &epos, &eloc, &elen, 1)) != -1) {
148 etype = netype;
149 lbcount += elen;
150 }
101 if (etype == (EXT_NOT_RECORDED_ALLOCATED >> 30)) { 151 if (etype == (EXT_NOT_RECORDED_ALLOCATED >> 30)) {
102 epos.offset -= adsize; 152 epos.offset -= adsize;
103 lbcount -= elen; 153 lbcount -= elen;
104 extent_trunc(inode, &epos, eloc, etype, elen, 0); 154 extent_trunc(inode, &epos, eloc, etype, elen, 0);
105 if (!epos.bh) 155 if (!epos.bh) {
106 {
107 UDF_I_LENALLOC(inode) = epos.offset - udf_file_entry_alloc_offset(inode); 156 UDF_I_LENALLOC(inode) = epos.offset - udf_file_entry_alloc_offset(inode);
108 mark_inode_dirty(inode); 157 mark_inode_dirty(inode);
109 } 158 } else {
110 else
111 {
112 struct allocExtDesc *aed = (struct allocExtDesc *)(epos.bh->b_data); 159 struct allocExtDesc *aed = (struct allocExtDesc *)(epos.bh->b_data);
113 aed->lengthAllocDescs = cpu_to_le32(epos.offset - sizeof(struct allocExtDesc)); 160 aed->lengthAllocDescs = cpu_to_le32(epos.offset - sizeof(struct allocExtDesc));
114 if (!UDF_QUERY_FLAG(inode->i_sb, UDF_FLAG_STRICT) || UDF_SB_UDFREV(inode->i_sb) >= 0x0201) 161 if (!UDF_QUERY_FLAG(inode->i_sb, UDF_FLAG_STRICT) || UDF_SB_UDFREV(inode->i_sb) >= 0x0201)
@@ -118,9 +165,9 @@ void udf_discard_prealloc(struct inode * inode)
118 mark_buffer_dirty_inode(epos.bh, inode); 165 mark_buffer_dirty_inode(epos.bh, inode);
119 } 166 }
120 } 167 }
168 /* This inode entry is in-memory only and thus we don't have to mark
169 * the inode dirty */
121 UDF_I_LENEXTENTS(inode) = lbcount; 170 UDF_I_LENEXTENTS(inode) = lbcount;
122
123 WARN_ON(lbcount != inode->i_size);
124 brelse(epos.bh); 171 brelse(epos.bh);
125} 172}
126 173
diff --git a/fs/udf/udfdecl.h b/fs/udf/udfdecl.h
index 67ded289497c..f581f2f69c0f 100644
--- a/fs/udf/udfdecl.h
+++ b/fs/udf/udfdecl.h
@@ -146,6 +146,7 @@ extern void udf_free_inode(struct inode *);
146extern struct inode * udf_new_inode (struct inode *, int, int *); 146extern struct inode * udf_new_inode (struct inode *, int, int *);
147 147
148/* truncate.c */ 148/* truncate.c */
149extern void udf_truncate_tail_extent(struct inode *);
149extern void udf_discard_prealloc(struct inode *); 150extern void udf_discard_prealloc(struct inode *);
150extern void udf_truncate_extents(struct inode *); 151extern void udf_truncate_extents(struct inode *);
151 152