Blame view

fs/exfat/file.c 9.5 KB
98d917047   Namjae Jeon   exfat: add file o...
1
2
3
4
5
6
7
8
  // SPDX-License-Identifier: GPL-2.0-or-later
  /*
   * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd.
   */
  
  #include <linux/slab.h>
  #include <linux/cred.h>
  #include <linux/buffer_head.h>
5267456e9   Sungjong Seo   exfat: flush dirt...
9
  #include <linux/blkdev.h>
98d917047   Namjae Jeon   exfat: add file o...
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
  
  #include "exfat_raw.h"
  #include "exfat_fs.h"
  
  static int exfat_cont_expand(struct inode *inode, loff_t size)
  {
  	struct address_space *mapping = inode->i_mapping;
  	loff_t start = i_size_read(inode), count = size - i_size_read(inode);
  	int err, err2;
  
  	err = generic_cont_expand_simple(inode, size);
  	if (err)
  		return err;
  
  	inode->i_ctime = inode->i_mtime = current_time(inode);
  	mark_inode_dirty(inode);
  
  	if (!IS_SYNC(inode))
  		return 0;
  
  	err = filemap_fdatawrite_range(mapping, start, start + count - 1);
  	err2 = sync_mapping_buffers(mapping);
  	if (!err)
  		err = err2;
  	err2 = write_inode_now(inode, 1);
  	if (!err)
  		err = err2;
  	if (err)
  		return err;
  
  	return filemap_fdatawait_range(mapping, start, start + count - 1);
  }
  
  static bool exfat_allow_set_time(struct exfat_sb_info *sbi, struct inode *inode)
  {
  	mode_t allow_utime = sbi->options.allow_utime;
  
  	if (!uid_eq(current_fsuid(), inode->i_uid)) {
  		if (in_group_p(inode->i_gid))
  			allow_utime >>= 3;
  		if (allow_utime & MAY_WRITE)
  			return true;
  	}
  
  	/* use a default check */
  	return false;
  }
  
  static int exfat_sanitize_mode(const struct exfat_sb_info *sbi,
  		struct inode *inode, umode_t *mode_ptr)
  {
  	mode_t i_mode, mask, perm;
  
  	i_mode = inode->i_mode;
  
  	mask = (S_ISREG(i_mode) || S_ISLNK(i_mode)) ?
  		sbi->options.fs_fmask : sbi->options.fs_dmask;
  	perm = *mode_ptr & ~(S_IFMT | mask);
  
  	/* Of the r and x bits, all (subject to umask) must be present.*/
  	if ((perm & 0555) != (i_mode & 0555))
  		return -EPERM;
  
  	if (exfat_mode_can_hold_ro(inode)) {
  		/*
  		 * Of the w bits, either all (subject to umask) or none must
  		 * be present.
  		 */
  		if ((perm & 0222) && ((perm & 0222) != (0222 & ~mask)))
  			return -EPERM;
  	} else {
  		/*
  		 * If exfat_mode_can_hold_ro(inode) is false, can't change
  		 * w bits.
  		 */
  		if ((perm & 0222) != (0222 & ~mask))
  			return -EPERM;
  	}
  
  	*mode_ptr &= S_IFMT | perm;
  
  	return 0;
  }
  
  /* resize the file length */
  int __exfat_truncate(struct inode *inode, loff_t new_size)
  {
  	unsigned int num_clusters_new, num_clusters_phys;
  	unsigned int last_clu = EXFAT_FREE_CLUSTER;
  	struct exfat_chain clu;
98d917047   Namjae Jeon   exfat: add file o...
100
101
102
  	struct super_block *sb = inode->i_sb;
  	struct exfat_sb_info *sbi = EXFAT_SB(sb);
  	struct exfat_inode_info *ei = EXFAT_I(inode);
98d917047   Namjae Jeon   exfat: add file o...
103
104
105
106
107
  	int evict = (ei->dir.dir == DIR_DELETED) ? 1 : 0;
  
  	/* check if the given file ID is opened */
  	if (ei->type != TYPE_FILE && ei->type != TYPE_DIR)
  		return -EPERM;
7018ec68f   Tetsuhiro Kohada   exfat: retain 'Vo...
108
  	exfat_set_volume_dirty(sb);
98d917047   Namjae Jeon   exfat: add file o...
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
  
  	num_clusters_new = EXFAT_B_TO_CLU_ROUND_UP(i_size_read(inode), sbi);
  	num_clusters_phys =
  		EXFAT_B_TO_CLU_ROUND_UP(EXFAT_I(inode)->i_size_ondisk, sbi);
  
  	exfat_chain_set(&clu, ei->start_clu, num_clusters_phys, ei->flags);
  
  	if (new_size > 0) {
  		/*
  		 * Truncate FAT chain num_clusters after the first cluster
  		 * num_clusters = min(new, phys);
  		 */
  		unsigned int num_clusters =
  			min(num_clusters_new, num_clusters_phys);
  
  		/*
  		 * Follow FAT chain
  		 * (defensive coding - works fine even with corrupted FAT table
  		 */
  		if (clu.flags == ALLOC_NO_FAT_CHAIN) {
  			clu.dir += num_clusters;
  			clu.size -= num_clusters;
  		} else {
  			while (num_clusters > 0) {
  				last_clu = clu.dir;
  				if (exfat_get_next_cluster(sb, &(clu.dir)))
  					return -EIO;
  
  				num_clusters--;
  				clu.size--;
  			}
  		}
  	} else {
  		ei->flags = ALLOC_NO_FAT_CHAIN;
  		ei->start_clu = EXFAT_EOF_CLUSTER;
  	}
  
  	i_size_write(inode, new_size);
  
  	if (ei->type == TYPE_FILE)
  		ei->attr |= ATTR_ARCHIVE;
  
  	/* update the directory entry */
  	if (!evict) {
  		struct timespec64 ts;
943af1fda   Tetsuhiro Kohada   exfat: optimize d...
154
155
  		struct exfat_dentry *ep, *ep2;
  		struct exfat_entry_set_cache *es;
8b0c47177   Tetsuhiro Kohada   exfat: add error ...
156
  		int err;
98d917047   Namjae Jeon   exfat: add file o...
157
158
  
  		es = exfat_get_dentry_set(sb, &(ei->dir), ei->entry,
943af1fda   Tetsuhiro Kohada   exfat: optimize d...
159
  				ES_ALL_ENTRIES);
98d917047   Namjae Jeon   exfat: add file o...
160
161
  		if (!es)
  			return -EIO;
943af1fda   Tetsuhiro Kohada   exfat: optimize d...
162
163
  		ep = exfat_get_dentry_cached(es, 0);
  		ep2 = exfat_get_dentry_cached(es, 1);
98d917047   Namjae Jeon   exfat: add file o...
164
165
166
167
168
169
  
  		ts = current_time(inode);
  		exfat_set_entry_time(sbi, &ts,
  				&ep->dentry.file.modify_tz,
  				&ep->dentry.file.modify_time,
  				&ep->dentry.file.modify_date,
ed0f84d30   Tetsuhiro Kohada   exfat: replace 't...
170
  				&ep->dentry.file.modify_time_cs);
98d917047   Namjae Jeon   exfat: add file o...
171
172
173
174
  		ep->dentry.file.attr = cpu_to_le16(ei->attr);
  
  		/* File size should be zero if there is no cluster allocated */
  		if (ei->start_clu == EXFAT_EOF_CLUSTER) {
29bbb14bf   Namjae Jeon   exfat: fix incorr...
175
176
  			ep2->dentry.stream.valid_size = 0;
  			ep2->dentry.stream.size = 0;
98d917047   Namjae Jeon   exfat: add file o...
177
  		} else {
29bbb14bf   Namjae Jeon   exfat: fix incorr...
178
  			ep2->dentry.stream.valid_size = cpu_to_le64(new_size);
41e3928f8   Hyeongseok Kim   exfat: fix wrong ...
179
  			ep2->dentry.stream.size = ep2->dentry.stream.valid_size;
98d917047   Namjae Jeon   exfat: add file o...
180
181
182
183
184
185
186
187
188
  		}
  
  		if (new_size == 0) {
  			/* Any directory can not be truncated to zero */
  			WARN_ON(ei->type != TYPE_FILE);
  
  			ep2->dentry.stream.flags = ALLOC_FAT_CHAIN;
  			ep2->dentry.stream.start_clu = EXFAT_FREE_CLUSTER;
  		}
943af1fda   Tetsuhiro Kohada   exfat: optimize d...
189
  		exfat_update_dir_chksum_with_entry_set(es);
8b0c47177   Tetsuhiro Kohada   exfat: add error ...
190
191
192
  		err = exfat_free_dentry_set(es, inode_needs_sync(inode));
  		if (err)
  			return err;
98d917047   Namjae Jeon   exfat: add file o...
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
  	}
  
  	/* cut off from the FAT chain */
  	if (ei->flags == ALLOC_FAT_CHAIN && last_clu != EXFAT_FREE_CLUSTER &&
  			last_clu != EXFAT_EOF_CLUSTER) {
  		if (exfat_ent_set(sb, last_clu, EXFAT_EOF_CLUSTER))
  			return -EIO;
  	}
  
  	/* invalidate cache and free the clusters */
  	/* clear exfat cache */
  	exfat_cache_inval_inode(inode);
  
  	/* hint information */
  	ei->hint_bmap.off = EXFAT_EOF_CLUSTER;
  	ei->hint_bmap.clu = EXFAT_EOF_CLUSTER;
98d917047   Namjae Jeon   exfat: add file o...
209
210
211
212
213
214
215
216
217
  
  	/* hint_stat will be used if this is directory. */
  	ei->hint_stat.eidx = 0;
  	ei->hint_stat.clu = ei->start_clu;
  	ei->hint_femp.eidx = EXFAT_HINT_NONE;
  
  	/* free the clusters */
  	if (exfat_free_cluster(inode, &clu))
  		return -EIO;
7018ec68f   Tetsuhiro Kohada   exfat: retain 'Vo...
218
  	exfat_clear_volume_dirty(sb);
98d917047   Namjae Jeon   exfat: add file o...
219
220
221
222
223
224
225
226
  
  	return 0;
  }
  
  void exfat_truncate(struct inode *inode, loff_t size)
  {
  	struct super_block *sb = inode->i_sb;
  	struct exfat_sb_info *sbi = EXFAT_SB(sb);
45882a6a0   Xianting Tian   exfat: use i_bloc...
227
  	unsigned int blocksize = i_blocksize(inode);
98d917047   Namjae Jeon   exfat: add file o...
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
  	loff_t aligned_size;
  	int err;
  
  	mutex_lock(&sbi->s_lock);
  	if (EXFAT_I(inode)->start_clu == 0) {
  		/*
  		 * Empty start_clu != ~0 (not allocated)
  		 */
  		exfat_fs_error(sb, "tried to truncate zeroed cluster.");
  		goto write_size;
  	}
  
  	err = __exfat_truncate(inode, i_size_read(inode));
  	if (err)
  		goto write_size;
  
  	inode->i_ctime = inode->i_mtime = current_time(inode);
  	if (IS_DIRSYNC(inode))
  		exfat_sync_inode(inode);
  	else
  		mark_inode_dirty(inode);
  
  	inode->i_blocks = ((i_size_read(inode) + (sbi->cluster_size - 1)) &
  			~(sbi->cluster_size - 1)) >> inode->i_blkbits;
  write_size:
  	aligned_size = i_size_read(inode);
  	if (aligned_size & (blocksize - 1)) {
  		aligned_size |= (blocksize - 1);
  		aligned_size++;
  	}
  
  	if (EXFAT_I(inode)->i_size_ondisk > i_size_read(inode))
  		EXFAT_I(inode)->i_size_ondisk = aligned_size;
  
  	if (EXFAT_I(inode)->i_size_aligned > i_size_read(inode))
  		EXFAT_I(inode)->i_size_aligned = aligned_size;
  	mutex_unlock(&sbi->s_lock);
  }
  
  int exfat_getattr(const struct path *path, struct kstat *stat,
  		unsigned int request_mask, unsigned int query_flags)
  {
  	struct inode *inode = d_backing_inode(path->dentry);
  	struct exfat_inode_info *ei = EXFAT_I(inode);
  
  	generic_fillattr(inode, stat);
81df1ad40   Eric Sandeen   exfat: truncate a...
274
  	exfat_truncate_atime(&stat->atime);
98d917047   Namjae Jeon   exfat: add file o...
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
  	stat->result_mask |= STATX_BTIME;
  	stat->btime.tv_sec = ei->i_crtime.tv_sec;
  	stat->btime.tv_nsec = ei->i_crtime.tv_nsec;
  	stat->blksize = EXFAT_SB(inode->i_sb)->cluster_size;
  	return 0;
  }
  
  int exfat_setattr(struct dentry *dentry, struct iattr *attr)
  {
  	struct exfat_sb_info *sbi = EXFAT_SB(dentry->d_sb);
  	struct inode *inode = dentry->d_inode;
  	unsigned int ia_valid;
  	int error;
  
  	if ((attr->ia_valid & ATTR_SIZE) &&
  	    attr->ia_size > i_size_read(inode)) {
  		error = exfat_cont_expand(inode, attr->ia_size);
  		if (error || attr->ia_valid == ATTR_SIZE)
  			return error;
  		attr->ia_valid &= ~ATTR_SIZE;
  	}
  
  	/* Check for setting the inode time. */
  	ia_valid = attr->ia_valid;
  	if ((ia_valid & (ATTR_MTIME_SET | ATTR_ATIME_SET | ATTR_TIMES_SET)) &&
  	    exfat_allow_set_time(sbi, inode)) {
  		attr->ia_valid &= ~(ATTR_MTIME_SET | ATTR_ATIME_SET |
  				ATTR_TIMES_SET);
  	}
  
  	error = setattr_prepare(dentry, attr);
  	attr->ia_valid = ia_valid;
  	if (error)
  		goto out;
  
  	if (((attr->ia_valid & ATTR_UID) &&
  	     !uid_eq(attr->ia_uid, sbi->options.fs_uid)) ||
  	    ((attr->ia_valid & ATTR_GID) &&
  	     !gid_eq(attr->ia_gid, sbi->options.fs_gid)) ||
  	    ((attr->ia_valid & ATTR_MODE) &&
  	     (attr->ia_mode & ~(S_IFREG | S_IFLNK | S_IFDIR | 0777)))) {
  		error = -EPERM;
  		goto out;
  	}
  
  	/*
  	 * We don't return -EPERM here. Yes, strange, but this is too
  	 * old behavior.
  	 */
  	if (attr->ia_valid & ATTR_MODE) {
  		if (exfat_sanitize_mode(sbi, inode, &attr->ia_mode) < 0)
  			attr->ia_valid &= ~ATTR_MODE;
  	}
  
  	if (attr->ia_valid & ATTR_SIZE) {
  		error = exfat_block_truncate_page(inode, attr->ia_size);
  		if (error)
  			goto out;
  
  		down_write(&EXFAT_I(inode)->truncate_lock);
  		truncate_setsize(inode, attr->ia_size);
  		exfat_truncate(inode, attr->ia_size);
  		up_write(&EXFAT_I(inode)->truncate_lock);
  	}
  
  	setattr_copy(inode, attr);
81df1ad40   Eric Sandeen   exfat: truncate a...
341
  	exfat_truncate_atime(&inode->i_atime);
98d917047   Namjae Jeon   exfat: add file o...
342
343
344
345
346
  	mark_inode_dirty(inode);
  
  out:
  	return error;
  }
5267456e9   Sungjong Seo   exfat: flush dirt...
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
  int exfat_file_fsync(struct file *filp, loff_t start, loff_t end, int datasync)
  {
  	struct inode *inode = filp->f_mapping->host;
  	int err;
  
  	err = __generic_file_fsync(filp, start, end, datasync);
  	if (err)
  		return err;
  
  	err = sync_blockdev(inode->i_sb->s_bdev);
  	if (err)
  		return err;
  
  	return blkdev_issue_flush(inode->i_sb->s_bdev, GFP_KERNEL);
  }
98d917047   Namjae Jeon   exfat: add file o...
362
  const struct file_operations exfat_file_operations = {
035779483   Eric Sandeen   exfat: use iter_f...
363
364
365
366
  	.llseek		= generic_file_llseek,
  	.read_iter	= generic_file_read_iter,
  	.write_iter	= generic_file_write_iter,
  	.mmap		= generic_file_mmap,
5267456e9   Sungjong Seo   exfat: flush dirt...
367
  	.fsync		= exfat_file_fsync,
035779483   Eric Sandeen   exfat: use iter_f...
368
369
  	.splice_read	= generic_file_splice_read,
  	.splice_write	= iter_file_splice_write,
98d917047   Namjae Jeon   exfat: add file o...
370
371
372
373
374
375
  };
  
  const struct inode_operations exfat_file_inode_operations = {
  	.setattr     = exfat_setattr,
  	.getattr     = exfat_getattr,
  };