diff --git a/Documentation/filesystems/ntfs3.rst b/Documentation/filesystems/ntfs3.rst index 2b86a9b3a6de..e1d86dcc3316 100644 --- a/Documentation/filesystems/ntfs3.rst +++ b/Documentation/filesystems/ntfs3.rst @@ -109,6 +109,29 @@ this table marked with no it means default is without **no**. Kernel. Not to be confused with NTFS ACLs. The option specified as acl enables support for POSIX ACLs. + * - ads + - Enable access to alternate data streams, i.e. named $DATA + attributes. A stream is addressed by appending a colon and the + stream name to the file name. Reading the pseudo-stream + ``query_streams`` returns the names of the streams attached to a + file, one per line:: + + cat file:query_streams # list stream names + cat file:ads1 # read a stream + touch file:ads2 # create a stream on an existing file + rm file:ads1 # remove a stream + + Creating a file together with a stream in a single call, and + renaming a stream, are not supported. Enabled by default. + + * - nocase + - Perform file name lookups case-insensitively. + + * - delalloc + - Delay block allocation for buffered writes until writeback, + rather than allocating as each write is issued. This lets the + driver allocate larger contiguous runs and reduces fragmentation. + Todo list ========= - Full journaling support over JBD. Currently journal replaying is supported diff --git a/MAINTAINERS b/MAINTAINERS index 09713dc4c6a7..7a162e9bcab1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19924,6 +19924,7 @@ W: http://www.paragon-software.com/ T: git https://github.com/Paragon-Software-Group/linux-ntfs3.git F: Documentation/filesystems/ntfs3.rst F: fs/ntfs3/ +F: include/trace/events/ntfs3.h NTSYNC SYNCHRONIZATION PRIMITIVE DRIVER M: Elizabeth Figura diff --git a/fs/ntfs3/attrib.c b/fs/ntfs3/attrib.c index b1c315206ffa..805acc3ed869 100644 --- a/fs/ntfs3/attrib.c +++ b/fs/ntfs3/attrib.c @@ -13,6 +13,7 @@ #include "debug.h" #include "ntfs.h" #include "ntfs_fs.h" +#include /* * You can set external NTFS_MIN_LOG2_OF_CLUMP/NTFS_MAX_LOG2_OF_CLUMP to manage @@ -61,11 +62,16 @@ static int attr_load_runs(struct ATTRIB *attr, struct ntfs_inode *ni, struct runs_tree *run, const CLST *vcn) { int err; - CLST svcn = le64_to_cpu(attr->nres.svcn); - CLST evcn = le64_to_cpu(attr->nres.evcn); + CLST svcn, evcn; u32 asize; u16 run_off; + if (!attr->non_res) + return -EIO; + + svcn = le64_to_cpu(attr->nres.svcn); + evcn = le64_to_cpu(attr->nres.evcn); + if (svcn >= evcn + 1 || run_is_mapped_full(run, svcn, evcn)) return 0; @@ -167,6 +173,8 @@ int attr_allocate_clusters(struct ntfs_sb_info *sbi, struct runs_tree *run, CLST flen, vcn0 = vcn, pre = pre_alloc ? *pre_alloc : 0; size_t cnt = run->count; + trace_ntfs3_attr_allocate_clusters(sbi->sb, vcn, lcn, len, opt); + for (;;) { err = ntfs_look_for_free_space(sbi, lcn, len + pre, &lcn, &flen, opt); @@ -253,8 +261,8 @@ int attr_allocate_clusters(struct ntfs_sb_info *sbi, struct runs_tree *run, */ int attr_make_nonresident(struct ntfs_inode *ni, struct ATTRIB *attr, struct ATTR_LIST_ENTRY *le, struct mft_inode *mi, - u64 new_size, struct runs_tree *run, - struct ATTRIB **ins_attr, struct page *page) + struct runs_tree *run, struct ATTRIB **ins_attr, + struct page *page) { struct ntfs_sb_info *sbi; struct ATTRIB *attr_s; @@ -394,13 +402,15 @@ static int attr_set_size_res(struct ntfs_inode *ni, struct ATTRIB *attr, char *next = Add2Ptr(attr, asize); s64 dsize = ALIGN(new_size, 8) - ALIGN(rsize, 8); + if (new_size > sbi->record_size || + (dsize > 0 && used + dsize > sbi->max_bytes_per_attr)) { + return attr_make_nonresident(ni, attr, le, mi, run, ins_attr, + NULL); + } + if (dsize < 0) { memmove(next + dsize, next, tail); } else if (dsize > 0) { - if (used + dsize > sbi->max_bytes_per_attr) - return attr_make_nonresident(ni, attr, le, mi, new_size, - run, ins_attr, NULL); - memmove(next + dsize, next, tail); memset(next, 0, dsize); } @@ -463,6 +473,12 @@ int attr_set_size_ex(struct ntfs_inode *ni, enum ATTR_TYPE type, goto bad_inode; } + trace_ntfs3_attr_set_size_ex(&ni->vfs_inode, le32_to_cpu(type), + attr_b->non_res ? + le64_to_cpu(attr_b->nres.data_size) : + le32_to_cpu(attr_b->res.data_size), + new_size, keep_prealloc, no_da); + if (!attr_b->non_res) { err = attr_set_size_res(ni, attr_b, le_b, mi_b, new_size, run, &attr_b); @@ -556,6 +572,10 @@ int attr_set_size_ex(struct ntfs_inode *ni, enum ATTR_TYPE type, } next_le_1: + if (!attr->non_res) { + err = -EIO; + goto out; + } svcn = le64_to_cpu(attr->nres.svcn); evcn = le64_to_cpu(attr->nres.evcn); } @@ -960,6 +980,9 @@ int attr_data_get_block(struct ntfs_inode *ni, CLST vcn, CLST clen, CLST *lcn, if (res) *res = NULL; + trace_ntfs3_attr_data_get_block(&ni->vfs_inode, vcn, clen, new, zero, + no_da); + /* Try to find in cache. */ down_read(&ni->file.run_lock); if (run_lookup_entry_da(&ni->file.run, !no_da ? &ni->file.run_da : NULL, @@ -1008,11 +1031,8 @@ int attr_data_get_block_locked(struct ntfs_inode *ni, CLST vcn, CLST clen, int step; again: - if (run_lookup_entry_da(run, da ? &ni->file.run_da : NULL, vcn, lcn, - len)) { - } else { + if (!run_lookup_entry_da(run, da ? run_da : NULL, vcn, lcn, len)) *len = 0; - } if (*len) { if (*lcn != SPARSE_LCN || !new) @@ -1034,6 +1054,21 @@ int attr_data_get_block_locked(struct ntfs_inode *ni, CLST vcn, CLST clen, if (!attr_b->non_res) { u32 data_size = le32_to_cpu(attr_b->res.data_size); + + /* + * A resident attribute is copied into a single page below + * (alloc_page() + memcpy()) and mapped as a one-page + * IOMAP_INLINE extent by ntfs_iomap_begin(). mi_enum_attr() + * only bounds the resident value length against the MFT record + * size, so a corrupted volume whose records are larger than a + * page can report data_size > PAGE_SIZE; copying that many bytes + * would overflow the single destination page. Reject it. + */ + if (data_size > PAGE_SIZE) { + err = -EINVAL; + goto out; + } + *lcn = RESIDENT_LCN; *len = data_size; if (res) { @@ -1138,7 +1173,7 @@ int attr_data_get_block_locked(struct ntfs_inode *ni, CLST vcn, CLST clen, to_alloc = ((vcn0 + clen + clst_per_frame - 1) & cmask) - vcn; if (fr < clst_per_frame) fr = clst_per_frame; - if (vcn != vcn0) + if (vcn != vcn0 || clen < to_alloc) zero = true; /* Check if 'vcn' and 'vcn0' in different attribute segments. */ @@ -1234,6 +1269,10 @@ int attr_data_get_block_locked(struct ntfs_inode *ni, CLST vcn, CLST clen, total_size = total_size0 + ((u64)*len << cluster_bits); if (vcn != vcn0) { + if (vcn0 >= end) { + err = -ENOSPC; + goto undo1; + } if (!run_lookup_entry(run, vcn0, lcn, len, NULL)) { err = -EINVAL; goto out; @@ -1456,6 +1495,9 @@ int attr_load_runs_vcn(struct ntfs_inode *ni, enum ATTR_TYPE type, return -ENOENT; } + if (!attr->non_res) + return -EIO; + svcn = le64_to_cpu(attr->nres.svcn); evcn = le64_to_cpu(attr->nres.evcn); @@ -1783,7 +1825,7 @@ int attr_allocate_frame(struct ntfs_inode *ni, CLST frame, size_t compr_size, struct ATTR_LIST_ENTRY *le, *le_b; struct mft_inode *mi, *mi_b; CLST svcn, evcn1, next_svcn, len; - CLST vcn, end, clst_data; + CLST vcn, end, clst_data, alloc, evcn; u64 total_size, valid_size, data_size; le_b = NULL; @@ -1801,6 +1843,7 @@ int attr_allocate_frame(struct ntfs_inode *ni, CLST frame, size_t compr_size, svcn = le64_to_cpu(attr_b->nres.svcn); evcn1 = le64_to_cpu(attr_b->nres.evcn) + 1; data_size = le64_to_cpu(attr_b->nres.data_size); + valid_size = le64_to_cpu(attr_b->nres.valid_size); if (svcn <= vcn && vcn < evcn1) { attr = attr_b; @@ -1920,63 +1963,63 @@ int attr_allocate_frame(struct ntfs_inode *ni, CLST frame, size_t compr_size, attr = ni_find_attr(ni, attr, &le, ATTR_DATA, ni->file.ads.name, ni->file.ads.len, &svcn, &mi); - if (attr) { - CLST alloc = bytes_to_cluster( - sbi, le64_to_cpu(attr_b->nres.alloc_size)); - CLST evcn = le64_to_cpu(attr->nres.evcn); + if (!attr) + goto ins_ext; - if (end < next_svcn) - end = next_svcn; - while (end > evcn) { - /* Remove segment [svcn : evcn). */ - mi_remove_attr(NULL, mi, attr); + alloc = bytes_to_cluster(sbi, le64_to_cpu(attr_b->nres.alloc_size)); + evcn = le64_to_cpu(attr->nres.evcn); - if (!al_remove_le(ni, le)) { - err = -EINVAL; - goto out; - } + if (end < next_svcn) + end = next_svcn; + while (end > evcn) { + /* Remove segment [svcn : evcn). */ + mi_remove_attr(NULL, mi, attr); - if (evcn + 1 >= alloc) { - /* Last attribute segment. */ - evcn1 = evcn + 1; - goto ins_ext; - } - - if (ni_load_mi(ni, le, &mi)) { - attr = NULL; - goto out; - } - - attr = mi_find_attr(ni, mi, NULL, ATTR_DATA, - ni->file.ads.name, ni->file.ads.len, - &le->id); - if (!attr) { - err = -EINVAL; - goto out; - } - svcn = le64_to_cpu(attr->nres.svcn); - evcn = le64_to_cpu(attr->nres.evcn); + if (!al_remove_le(ni, le)) { + err = -EINVAL; + goto out; } - if (end < svcn) - end = svcn; + if (evcn + 1 >= alloc) { + /* Last attribute segment. */ + evcn1 = evcn + 1; + goto ins_ext; + } - err = attr_load_runs(attr, ni, run, &end); - if (err) + if (ni_load_mi(ni, le, &mi)) { + attr = NULL; goto out; + } - evcn1 = evcn + 1; - attr->nres.svcn = cpu_to_le64(next_svcn); - err = mi_pack_runs(mi, attr, run, evcn1 - next_svcn); - if (err) + attr = mi_find_attr(ni, mi, NULL, ATTR_DATA, ni->file.ads.name, + ni->file.ads.len, &le->id); + if (!attr) { + err = -EINVAL; goto out; - - le->vcn = cpu_to_le64(next_svcn); - ni->attr_list.dirty = true; - mi->dirty = true; - - next_svcn = le64_to_cpu(attr->nres.evcn) + 1; + } + svcn = le64_to_cpu(attr->nres.svcn); + evcn = le64_to_cpu(attr->nres.evcn); } + + if (end < svcn) + end = svcn; + + err = attr_load_runs(attr, ni, run, &end); + if (err) + goto out; + + evcn1 = evcn + 1; + attr->nres.svcn = cpu_to_le64(next_svcn); + err = mi_pack_runs(mi, attr, run, evcn1 - next_svcn); + if (err) + goto out; + + le->vcn = cpu_to_le64(next_svcn); + ni->attr_list.dirty = true; + mi->dirty = true; + + next_svcn = le64_to_cpu(attr->nres.evcn) + 1; + ins_ext: if (evcn1 > next_svcn) { err = ni_insert_nonresident(ni, ATTR_DATA, ni->file.ads.name, @@ -1985,6 +2028,14 @@ int attr_allocate_frame(struct ntfs_inode *ni, CLST frame, size_t compr_size, &attr, &mi, NULL); if (err) goto out; + /* Layout of records is changed. */ + attr_b = ni_find_attr(ni, NULL, NULL, ATTR_DATA, + ni->file.ads.name, ni->file.ads.len, NULL, + &mi_b); + if (!attr_b) { + err = -EINVAL; + goto out; + } } ok: run_truncate_around(run, vcn); @@ -1993,9 +2044,8 @@ int attr_allocate_frame(struct ntfs_inode *ni, CLST frame, size_t compr_size, if (new_valid > data_size) new_valid = data_size; - valid_size = le64_to_cpu(attr_b->nres.valid_size); if (new_valid != valid_size) { - attr_b->nres.valid_size = cpu_to_le64(valid_size); + attr_b->nres.valid_size = cpu_to_le64(new_valid); mi_b->dirty = true; } } @@ -2357,6 +2407,12 @@ int attr_punch_hole(struct ntfs_inode *ni, u64 vbo, u64 bytes, u32 *frame_size) if ((vbo | bytes) & mask) { /* We have to zero a range(s). */ + if (is_attr_compressed(attr_b)) { + /* Compressed file punch must be frame aligned. */ + return -EINVAL; + } + + /* Sparse files can be punched. */ if (!frame_size) { /* Caller insists range is aligned. */ return -EINVAL; @@ -2610,8 +2666,8 @@ int attr_insert_range(struct ntfs_inode *ni, u64 vbo, u64 bytes) char *data = Add2Ptr(attr_b, le16_to_cpu(attr_b->res.data_off)); - memmove(data + bytes, data, bytes); - memset(data, 0, bytes); + memmove(data + vbo + bytes, data + vbo, data_size - vbo); + memset(data + vbo, 0, bytes); goto done; } @@ -2803,9 +2859,8 @@ int attr_force_nonresident(struct ntfs_inode *ni) } down_write(&ni->file.run_lock); - err = attr_make_nonresident(ni, attr, le, mi, - le32_to_cpu(attr->res.data_size), - &ni->file.run, &attr, NULL); + err = attr_make_nonresident(ni, attr, le, mi, &ni->file.run, &attr, + NULL); up_write(&ni->file.run_lock); return err; diff --git a/fs/ntfs3/dir.c b/fs/ntfs3/dir.c index 14ca54e54578..7ee1a5d4189e 100644 --- a/fs/ntfs3/dir.c +++ b/fs/ntfs3/dir.c @@ -14,6 +14,7 @@ #include "debug.h" #include "ntfs.h" #include "ntfs_fs.h" +#include /* Convert little endian UTF-16 to NLS string. */ int ntfs_utf16_to_nls(struct ntfs_sb_info *sbi, const __le16 *name, u32 len, @@ -34,6 +35,9 @@ int ntfs_utf16_to_nls(struct ntfs_sb_info *sbi, const __le16 *name, u32 len, /* UTF-16 -> UTF-8 */ ret = utf16s_to_utf8s((wchar_t *)name, len, UTF16_LITTLE_ENDIAN, buf, buf_len); + if (ret >= buf_len) { + ret = buf_len-1; + } buf[ret] = '\0'; return ret; } @@ -290,6 +294,8 @@ struct inode *dir_search_flags(struct inode *dir, const struct cpu_str *uni, struct inode *inode = NULL; struct ntfs_fnd *fnd_a = NULL; + trace_ntfs3_dir_search_u(dir, uni ? uni->len : 0); + if (!fnd) { fnd_a = fnd_get(); if (!fnd_a) { @@ -525,23 +531,6 @@ static int ntfs_readdir(struct file *file, struct dir_context *ctx) if (!name) return -ENOMEM; - if (!ni->mi_loaded && ni->attr_list.size) { - /* - * Directory inode is locked for read. - * Load all subrecords to avoid 'write' access to 'ni' during - * directory reading. - */ - ni_lock(ni); - if (!ni->mi_loaded && ni->attr_list.size) { - err = ni_load_all_mi(ni); - if (!err) - ni->mi_loaded = true; - } - ni_unlock(ni); - if (err) - goto out; - } - /* * Keep directory metadata stable for the whole walk. Loading subrecords * once is not enough if concurrent writeback can still compact ATTR_LIST @@ -549,21 +538,43 @@ static int ntfs_readdir(struct file *file, struct dir_context *ctx) */ ni_lock(ni); + if (!ni->mi_loaded && ni->attr_list.size) { + /* + * Directory inode is locked for read. + * Load all subrecords to avoid 'write' access to 'ni' during + * directory reading. + */ + err = ni_load_all_mi(ni); + if (err) + goto out; + ni->mi_loaded = true; + } + root = indx_get_root(&ni->dir, ni, NULL, NULL); if (!root) { err = -EINVAL; - goto out_unlock; + goto out; } if (pos >= sbi->record_size) { bit = (pos - sbi->record_size) >> index_bits; } else { + /* + * Release ni_lock before dir_emit (which may fault into + * user pages). root->ihdr lives in the main MFT record + * whose lifetime is bound to the inode, and i_rwsem shared + * prevents concurrent directory modifications. + */ + ni_unlock(ni); + /* * Add each name from root in 'ctx'. */ err = ntfs_read_hdr(sbi, ni, &root->ihdr, 0, pos, name, ctx); if (err) - goto out_unlock; + goto out; + + ni_lock(ni); bit = 0; } @@ -574,7 +585,7 @@ static int ntfs_readdir(struct file *file, struct dir_context *ctx) /* Get the next used index. */ err = indx_used_bit(&ni->dir, ni, &bit); if (err) - goto out_unlock; + goto out; if (bit == MINUS_ONE_T) { /* no more used indexes. end of dir. */ @@ -584,13 +595,19 @@ static int ntfs_readdir(struct file *file, struct dir_context *ctx) if (bit >= max_bit) { /* Corrupted directory. */ err = -EINVAL; - goto out_unlock; + goto out; } err = indx_read_ra(&ni->dir, ni, bit << ni->dir.idx2vbn_bits, &node, &file->f_ra); if (err) - goto out_unlock; + goto out; + + /* + * node->index is a kmalloc'd buffer, safe after ni_unlock. + * Release lock before dir_emit to avoid lock-order issues. + */ + ni_unlock(ni); /* * Add each name from index in 'ctx'. @@ -599,13 +616,14 @@ static int ntfs_readdir(struct file *file, struct dir_context *ctx) ((u64)bit << index_bits) + sbi->record_size, pos, name, ctx); if (err) - goto out_unlock; + goto out; + + ni_lock(ni); } -out_unlock: +out: ni_unlock(ni); -out: kfree(name); put_indx_node(node); diff --git a/fs/ntfs3/file.c b/fs/ntfs3/file.c index 52e1cd969e06..1624f9b014e0 100644 --- a/fs/ntfs3/file.c +++ b/fs/ntfs3/file.c @@ -20,6 +20,7 @@ #include "debug.h" #include "ntfs.h" #include "ntfs_fs.h" +#include /* * cifx, btrfs, exfat, ext4, f2fs use this constant. @@ -183,6 +184,9 @@ static int ntfs_ioctl_set_volume_label(struct ntfs_sb_info *sbi, u8 __user *buf) if (!capable(CAP_SYS_ADMIN)) return -EPERM; + if (sb_rdonly(sbi->sb)) + return -EROFS; + if (copy_from_user(user, buf, FSLABEL_MAX)) return -EFAULT; @@ -824,6 +828,8 @@ static ssize_t ntfs_file_read_iter(struct kiocb *iocb, struct iov_iter *iter) unsigned int dio_flags; ssize_t ret; + trace_ntfs3_file_read_iter(iocb, iter); + ret = check_read_restriction(inode); if (ret) return ret; @@ -1244,6 +1250,8 @@ static ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from) loff_t vbo, endbyte; ssize_t ret, err; + trace_ntfs3_file_write_iter(iocb, from); + if (!inode_trylock(inode)) { if (iocb->ki_flags & IOCB_NOWAIT) return -EAGAIN; diff --git a/fs/ntfs3/frecord.c b/fs/ntfs3/frecord.c index bead01a953f3..16ac847de48d 100644 --- a/fs/ntfs3/frecord.c +++ b/fs/ntfs3/frecord.c @@ -2190,10 +2190,13 @@ int ni_decompress_file(struct ntfs_inode *ni) /* Clear cached flag. */ ni->ni_flags &= ~NI_FLAG_COMPRESSED_MASK; + /* offs_folio is accessed under run_lock in attr_wof_frame_info(). */ + down_write(&ni->file.run_lock); if (ni->file.offs_folio) { folio_put(ni->file.offs_folio); ni->file.offs_folio = NULL; } + up_write(&ni->file.run_lock); mapping->a_ops = &ntfs_aops; out: @@ -2287,7 +2290,6 @@ int ni_read_frame(struct ntfs_inode *ni, u64 frame_vbo, struct page **pages, u64 vbo_disk; size_t unc_size = 0; u32 frame_size, i, ondisk_size; - struct page *pg; struct ATTRIB *attr; CLST frame, clst_data; @@ -2519,9 +2521,10 @@ int ni_read_frame(struct ntfs_inode *ni, u64 frame_vbo, struct page **pages, #endif vunmap(frame_mem); out: - for (i = 0; i < pages_per_frame; i++) { - pg = pages[i]; - SetPageUptodate(pg); + if (!err) { + for (i = 0; i < pages_per_frame; i++) { + SetPageUptodate(pages[i]); + } } return err; @@ -2569,9 +2572,8 @@ int ni_write_frame(struct ntfs_inode *ni, struct page **pages, if (!attr->non_res) { down_write(&ni->file.run_lock); - err = attr_make_nonresident(ni, attr, le, mi, - le32_to_cpu(attr->res.data_size), - &ni->file.run, &attr, pages[0]); + err = attr_make_nonresident(ni, attr, le, mi, &ni->file.run, + &attr, pages[0]); up_write(&ni->file.run_lock); if (err) goto out; diff --git a/fs/ntfs3/fslog.c b/fs/ntfs3/fslog.c index ed50c1d0c23e..bb8d40bb5869 100644 --- a/fs/ntfs3/fslog.c +++ b/fs/ntfs3/fslog.c @@ -7,12 +7,14 @@ #include #include +#include #include #include #include "debug.h" #include "ntfs.h" #include "ntfs_fs.h" +#include /* * LOG FILE structs @@ -2457,7 +2459,8 @@ static int find_log_rec(struct ntfs_log *log, u64 lsn, struct lcb *lcb) * Check that the length field isn't greater than the total * available space the log file. */ - rec_len = len + log->record_header_len; + if (check_add_overflow(len, log->record_header_len, &rec_len)) + return -EINVAL; if (rec_len >= log->total_avail) return -EINVAL; @@ -2783,9 +2786,6 @@ static inline bool check_attr(const struct MFT_REC *rec, return false; } - if (run_off > asize) - return false; - if (run_unpack(NULL, sbi, 0, svcn, evcn, svcn, Add2Ptr(attr, run_off), asize - run_off) < 0) { return false; @@ -3318,10 +3318,15 @@ static int do_action(struct ntfs_log *log, struct OPEN_ATTR_ENRTY *oe, asize = le32_to_cpu(attr2->size); used = le32_to_cpu(rec->used); + /* + * attr2->size is taken from the log record and is bounded only + * by the log record length, not by the MFT record. Refuse an + * attribute that does not fit, as mi_insert_attr() does. + */ if (!check_if_attr(rec, lrh) || dlen < SIZEOF_RESIDENT || !IS_ALIGNED(asize, 8) || Add2Ptr(attr2, asize) > Add2Ptr(lrh, rec_len) || - dlen > record_size - used) { + dlen > record_size - used || asize > record_size - used) { goto dirty_vol; } @@ -3344,10 +3349,21 @@ static int do_action(struct ntfs_log *log, struct OPEN_ATTR_ENRTY *oe, break; case DeleteAttribute: - asize = le32_to_cpu(attr->size); used = le32_to_cpu(rec->used); - if (!check_if_attr(rec, lrh)) + /* + * check_if_attr() accepts a record_off that points at the + * ATTR_END marker, which is how CreateAttribute appends. The + * bytes there are not an attribute and check_attr() never + * validated them, so refuse an offset that cannot hold a + * resident header before reading attr->size, and then bound it + * as mi_remove_attr() does. + */ + if (!check_if_attr(rec, lrh) || roff + SIZEOF_RESIDENT > used) + goto dirty_vol; + + asize = le32_to_cpu(attr->size); + if (asize > used - roff) goto dirty_vol; rec->used = cpu_to_le32(used - asize); @@ -3421,7 +3437,8 @@ static int do_action(struct ntfs_log *log, struct OPEN_ATTR_ENRTY *oe, if (!check_if_attr(rec, lrh) || !attr->non_res || aoff < le16_to_cpu(attr->nres.run_off) || aoff > asize || - (nsize > asize && nsize - asize > record_size - used)) { + (nsize > asize && + ALIGN(nsize, 8) - asize > record_size - used)) { goto dirty_vol; } @@ -5402,6 +5419,8 @@ int log_replay(struct ntfs_inode *ni, bool *initialized) else if (log->set_dirty) ntfs_set_state(sbi, NTFS_DIRTY_ERROR); + trace_ntfs3_log_replay(&ni->vfs_inode, *initialized, err); + kfree(log); return err; diff --git a/fs/ntfs3/fsntfs.c b/fs/ntfs3/fsntfs.c index 97c04ab2763a..b55311ccd229 100644 --- a/fs/ntfs3/fsntfs.c +++ b/fs/ntfs3/fsntfs.c @@ -2153,6 +2153,9 @@ int ntfs_insert_security(struct ntfs_sb_info *sbi, goto out; while (e) { + if (le16_to_cpu(e->de.size) < SIZEOF_SDH_DIRENTRY) + break; + if (le32_to_cpu(e->sec_hdr.size) == new_sec_size) { err = ntfs_read_run_nb(sbi, &ni->file.run, le64_to_cpu(e->sec_hdr.off), diff --git a/fs/ntfs3/index.c b/fs/ntfs3/index.c index 689712d3463d..c06b36760448 100644 --- a/fs/ntfs3/index.c +++ b/fs/ntfs3/index.c @@ -13,6 +13,7 @@ #include "debug.h" #include "ntfs.h" #include "ntfs_fs.h" +#include static const struct INDEX_NAMES { const __le16 *name; @@ -594,6 +595,8 @@ static const struct NTFS_DE *hdr_insert_head(struct INDEX_HDR *hdr, if (!e) return NULL; + if (size_add(used, ins_bytes) > le32_to_cpu(hdr->total)) + return NULL; /* Now we just make room for the inserted entries and jam it in. */ to_move = used - le32_to_cpu(hdr->de_off); @@ -1179,6 +1182,8 @@ int indx_find(struct ntfs_index *indx, struct ntfs_inode *ni, if (!root) root = indx_get_root(&ni->dir, ni, NULL, NULL); + trace_ntfs3_indx_find(&ni->vfs_inode, indx->type, key_len); + if (!root) { /* Should not happen. */ return -EINVAL; @@ -1801,7 +1806,10 @@ static int indx_insert_into_root(struct ntfs_index *indx, struct ntfs_inode *ni, } /* Copy root entries into new buffer. */ - hdr_insert_head(hdr, re, to_move); + if (!hdr_insert_head(hdr, re, to_move)) { + err = -EINVAL; + goto out_put_n; + } /* Update bitmap attribute. */ indx_mark_used(indx, ni, new_vbn >> indx->idx2vbn_bits); @@ -1955,7 +1963,11 @@ static int indx_insert_into_buffer(struct ntfs_index *indx, /* Copy all the entries <= sp into the new buffer. */ de_t = hdr_first_de(hdr1); to_copy = PtrOffset(de_t, sp); - hdr_insert_head(hdr2, de_t, to_copy); + if (!hdr_insert_head(hdr2, de_t, to_copy)) { + err = -EINVAL; + put_indx_node(n2); + goto out; + } /* Remove all entries (sp including) from hdr1. */ used = used1 - to_copy - sp_size; @@ -2049,9 +2061,13 @@ int indx_insert_entry(struct ntfs_index *indx, struct ntfs_inode *ni, cmp = get_cmp_func(root); if (unlikely(!cmp)) { WARN_ON_ONCE(1); - return -EINVAL; + err = -EINVAL; + goto out; } + trace_ntfs3_indx_insert_entry(&ni->vfs_inode, indx->type, + le16_to_cpu(new_de->key_size), undo); + if (fnd_is_empty(fnd)) { /* * Find the spot the tree where we want to @@ -2406,9 +2422,12 @@ int indx_delete_entry(struct ntfs_index *indx, struct ntfs_inode *ni, cmp = get_cmp_func(root); if (unlikely(!cmp)) { WARN_ON_ONCE(1); - return -EINVAL; + err = -EINVAL; + goto out; } + trace_ntfs3_indx_delete_entry(&ni->vfs_inode, indx->type, key_len); + /* Locate the entry to remove. */ err = indx_find(indx, ni, root, key, key_len, ctx, &diff, &e, fnd); if (err) @@ -2702,18 +2721,28 @@ int indx_delete_entry(struct ntfs_index *indx, struct ntfs_inode *ni, err = attr_set_size(ni, ATTR_ALLOC, in->name, in->name_len, &indx->alloc_run, 0, NULL, false); + if (err) + goto out; + if (in->name == I30_NAME) i_size_write(&ni->vfs_inode, 0); err = ni_remove_attr(ni, ATTR_ALLOC, in->name, in->name_len, false, NULL); run_close(&indx->alloc_run); + if (err) + goto out; err = attr_set_size(ni, ATTR_BITMAP, in->name, in->name_len, &indx->bitmap_run, 0, NULL, false); + if (err) + goto out; + err = ni_remove_attr(ni, ATTR_BITMAP, in->name, in->name_len, false, NULL); run_close(&indx->bitmap_run); + if (err) + goto out; root = indx_get_root(indx, ni, &attr, &mi); if (!root) { @@ -2787,7 +2816,8 @@ int indx_update_dup(struct ntfs_inode *ni, struct ntfs_sb_info *sbi, cmp = get_cmp_func(root); if (unlikely(!cmp)) { WARN_ON_ONCE(1); - return -EINVAL; + err = -EINVAL; + goto out; } /* Find entry in directory. */ diff --git a/fs/ntfs3/inode.c b/fs/ntfs3/inode.c index 5a8d362155ac..43d5e83403aa 100644 --- a/fs/ntfs3/inode.c +++ b/fs/ntfs3/inode.c @@ -17,6 +17,7 @@ #include "debug.h" #include "ntfs.h" #include "ntfs_fs.h" +#include struct IGET5_PARAM { const struct MFT_REF *ref; @@ -213,6 +214,11 @@ static int ntfs_read_mft(struct inode *inode, const struct cpu_str *name, names += 1; fname = Add2Ptr(attr, roff); + + /* Make sure the full name fits in the resident data. */ + if (rsize < fname_full_size(fname)) + goto out; + if (fname->type == FILE_NAME_DOS) goto next_attr; @@ -280,7 +286,8 @@ static int ntfs_read_mft(struct inode *inode, const struct cpu_str *name, break; case ATTR_ROOT: - if (attr->non_res) + if (attr->non_res || asize < sizeof(struct INDEX_ROOT) + roff || + rsize < sizeof(struct INDEX_ROOT)) goto out; root = Add2Ptr(attr, roff); @@ -947,6 +954,8 @@ static int ntfs_iomap_begin(struct inode *inode, loff_t offset, loff_t length, bool new_clst = false; bool no_da; bool zero = false; + + trace_ntfs3_iomap_begin(inode, offset, length, flags); if (unlikely(ntfs3_forced_shutdown(sbi->sb))) return -EIO; @@ -962,9 +971,7 @@ static int ntfs_iomap_begin(struct inode *inode, loff_t offset, loff_t length, } } - if (IOMAP_ZERO == flags && (endbyte & sbi->cluster_mask)) { - rw = true; - } else if (rw) { + if (rw) { clen_max = bytes_to_cluster(sbi, endbyte) - vcn; } @@ -972,7 +979,7 @@ static int ntfs_iomap_begin(struct inode *inode, loff_t offset, loff_t length, * Force to allocate clusters if directIO(write) or writeback_range. * NOTE: attr_data_get_block allocates clusters only for sparse file. * Normal file allocates clusters in attr_set_size. - */ + */ no_da = flags == (IOMAP_DIRECT | IOMAP_WRITE) || srcmap == WB_NO_DA; err = attr_data_get_block(ni, vcn, clen_max, &lcn, &clen, @@ -1017,7 +1024,7 @@ static int ntfs_iomap_begin(struct inode *inode, loff_t offset, loff_t length, } if (!clen) { - /* broken file? */ + /* Looks like file is broken.*/ return -EINVAL; } @@ -1086,6 +1093,8 @@ static int ntfs_iomap_end(struct inode *inode, loff_t pos, loff_t length, struct ntfs_inode *ni = ntfs_i(inode); loff_t endbyte = pos + written; + trace_ntfs3_iomap_end(inode, pos, length, written, flags); + if ((flags & IOMAP_WRITE) || (flags & IOMAP_ZERO)) { if (iomap->type == IOMAP_INLINE) { u32 data_size; @@ -1228,6 +1237,15 @@ static int ntfs_writepages(struct address_space *mapping, folio_unlock(folio); if (err2) { + /* + * attr_data_write_resident() can return the + * positive internal code E_NTFS_NONRESIDENT; + * both mapping_set_error() and writeback_iter() + * need a negative errno and warn on a positive + * value. + */ + if (err2 > 0) + err2 = -EIO; mapping_set_error(mapping, err2); if (!err) err = err2; @@ -1412,6 +1430,8 @@ int ntfs_create_inode(const struct mnt_idmap *idmap, struct inode *dir, /* New file will be resident or non resident. */ const bool new_file_resident = 1; + trace_ntfs3_create_inode(dir, dentry); + if (!fnd) ni_lock_dir(dir_ni); @@ -1831,7 +1851,7 @@ int ntfs_create_inode(const struct mnt_idmap *idmap, struct inode *dir, inode->i_flags |= S_NOSEC; } - if (!S_ISLNK(mode)) { + if (!S_ISLNK(mode) && sbi->options->ugm) { /* * ntfs_init_acl and ntfs_save_wsl_perm update extended attribute. * The packed size of extended attribute is stored in direntry too. @@ -2244,6 +2264,9 @@ static noinline int ntfs_readlink_hlp(const struct dentry *link_de, if (err < 0) goto out; + if (err >= buflen) + err = buflen - 1; + /* Translate Windows '\' into Linux '/'. */ for (i = 0; i < err; i++) { if (buffer[i] == '\\') diff --git a/fs/ntfs3/namei.c b/fs/ntfs3/namei.c index ae88cb66fb7e..f4286c642752 100644 --- a/fs/ntfs3/namei.c +++ b/fs/ntfs3/namei.c @@ -13,6 +13,7 @@ #include "debug.h" #include "ntfs.h" #include "ntfs_fs.h" +#include /* * fill_name_de - Format NTFS_DE in @buf. @@ -56,7 +57,7 @@ int fill_name_de(struct ntfs_sb_info *sbi, void *buf, const struct qstr *name, real_size = data_size + sizeof(struct NTFS_DE); aligned_size = ALIGN(data_size, 8) + sizeof(struct NTFS_DE); if (aligned_size > real_size) - memset((char *)buf + real_size, 0, aligned_size - real_size); + memset(Add2Ptr(buf, real_size), 0, aligned_size - real_size); e->size = cpu_to_le16(aligned_size); e->key_size = cpu_to_le16(data_size); @@ -77,6 +78,8 @@ static struct dentry *ntfs_lookup(struct inode *dir, struct dentry *dentry, struct inode *inode; int err; + trace_ntfs3_lookup(dir, dentry); + if (!uni) return ERR_PTR(-ENOMEM); @@ -231,8 +234,8 @@ static int ntfs_symlink(const struct mnt_idmap *idmap, struct inode *dir, static struct dentry *ntfs_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { - return ERR_PTR(ntfs_create_inode(idmap, dir, dentry, NULL, - mode, 0, NULL, 0, NULL)); + return ERR_PTR(ntfs_create_inode(idmap, dir, dentry, NULL, mode, 0, + NULL, 0, NULL)); } /* @@ -299,6 +302,8 @@ static int ntfs_rename(const struct mnt_idmap *idmap, struct inode *dir, if (unlikely(ntfs3_forced_shutdown(sb))) return -EIO; + trace_ntfs3_rename(dir, dentry, new_dir, new_dentry); + if (flags & ~RENAME_NOREPLACE) return -EINVAL; diff --git a/fs/ntfs3/ntfs_fs.h b/fs/ntfs3/ntfs_fs.h index 689151fcf993..f400cf94d05b 100644 --- a/fs/ntfs3/ntfs_fs.h +++ b/fs/ntfs3/ntfs_fs.h @@ -113,6 +113,7 @@ struct ntfs_mount_options { unsigned nocase : 1; /* case insensitive. */ unsigned delalloc : 1; /* delay allocation. */ unsigned ads : 1; /* ads support. */ + unsigned ugm : 1; /* store user/group/mode in xattr. */ }; /* Special value to unpack and deallocate. */ @@ -467,8 +468,8 @@ int attr_allocate_clusters(struct ntfs_sb_info *sbi, struct runs_tree *run, CLST *new_len); int attr_make_nonresident(struct ntfs_inode *ni, struct ATTRIB *attr, struct ATTR_LIST_ENTRY *le, struct mft_inode *mi, - u64 new_size, struct runs_tree *run, - struct ATTRIB **ins_attr, struct page *page); + struct runs_tree *run, struct ATTRIB **ins_attr, + struct page *page); int attr_set_size_ex(struct ntfs_inode *ni, enum ATTR_TYPE type, const __le16 *name, u8 name_len, struct runs_tree *run, u64 new_size, const u64 *new_valid, bool keep_prealloc, diff --git a/fs/ntfs3/record.c b/fs/ntfs3/record.c index 4f12ce15b03b..d7785f9e8e09 100644 --- a/fs/ntfs3/record.c +++ b/fs/ntfs3/record.c @@ -665,18 +665,32 @@ int mi_pack_runs(struct mft_inode *mi, struct ATTRIB *attr, u32 run_size = asize - run_off; u32 tail = used - aoff - asize; u32 dsize = sbi->record_size - used; + u32 avail; /* Make a maximum gap in current record. */ memmove(next + dsize, next, tail); + /* Leave room for the 8-byte ALIGN() to avoid OOB write */ + avail = (run_size + dsize) & ~7u; + + if (!avail) { + memmove(next, next + dsize, tail); + return -ENOSPC; + } + /* Pack as much as possible. */ - err = run_pack(run, svcn, len, Add2Ptr(attr, run_off), run_size + dsize, + err = run_pack(run, svcn, len, Add2Ptr(attr, run_off), avail, &plen); if (err < 0) { memmove(next, next + dsize, tail); return err; } + if (!plen) { + memmove(next, next + dsize, tail); + return -ENOSPC; + } + new_run_size = ALIGN(err, 8); memmove(next + new_run_size - run_size, next + dsize, tail); diff --git a/fs/ntfs3/super.c b/fs/ntfs3/super.c index 58ef6be3286b..29aabdc8a801 100644 --- a/fs/ntfs3/super.c +++ b/fs/ntfs3/super.c @@ -78,6 +78,9 @@ #include "lib/lib.h" #endif +#define CREATE_TRACE_POINTS +#include + #ifdef CONFIG_PRINTK /* * ntfs_printk - Trace warnings/notices/errors. @@ -280,6 +283,8 @@ enum Opt { Opt_delalloc_bool, Opt_ads, Opt_ads_bool, + Opt_ugm, + Opt_ugm_bool, Opt_err, }; @@ -308,6 +313,8 @@ static const struct fs_parameter_spec ntfs_fs_parameters[] = { fsparam_bool("delalloc", Opt_delalloc_bool), fsparam_flag("ads", Opt_ads), fsparam_bool("ads", Opt_ads_bool), + fsparam_flag("ugm", Opt_ugm), + fsparam_bool("ugm", Opt_ugm_bool), {} }; // clang-format on @@ -344,6 +351,17 @@ static int ntfs_fs_parse_param(struct fs_context *fc, int opt; opt = fs_parse(fc, ntfs_fs_parameters, param, &result); + + if (opt == -ENOPARAM && strcmp(param->key, "source")) { + /* Show mini help. */ + pr_err("ntfs3: unknown option \"%s\". Use uid,gid,umask," + "dmask,fmask,sys_immutable,discard,force,sparse," + "nohidden,hide_dot_files,windows_names,showmeta,acl," + "iocharset,prealloc,nocase,delalloc,ads,ugm", + param->key); + return -ENOPARAM; + } + if (opt < 0) return opt; @@ -437,6 +455,12 @@ static int ntfs_fs_parse_param(struct fs_context *fc, case Opt_ads_bool: opts->ads = result.boolean; break; + case Opt_ugm: + opts->ugm = 1; + break; + case Opt_ugm_bool: + opts->ugm = result.boolean; + break; default: /* Should not be here unless we forget add case. */ return -EINVAL; @@ -991,7 +1015,7 @@ static int ntfs_init_from_boot(struct super_block *sb, u32 sector_size, { struct ntfs_sb_info *sbi = sb->s_fs_info; int err; - u32 mb, gb, boot_sector_size, sct_per_clst, record_size; + u32 mb, gb, boot_sector_size = 0, sct_per_clst, record_size; u64 sectors, clusters, mlcn, mlcn2, mft_pos, mft2_pos, dev_size0; struct NTFS_BOOT *boot; struct buffer_head *bh; @@ -1231,12 +1255,17 @@ static int ntfs_init_from_boot(struct super_block *sb, u32 sector_size, #ifdef CONFIG_NTFS3_64BIT_CLUSTER if (clusters >= (1ull << (64 - cluster_bits))) - sbi->maxbytes = -1; + sbi->maxbytes = MAX_LFS_FILESIZE; sbi->maxbytes_sparse = MAX_LFS_FILESIZE; sb->s_maxbytes = MAX_LFS_FILESIZE; #else - /* Maximum size for sparse file. */ - sbi->maxbytes_sparse = (1ull << (cluster_bits + 32)) - 1; + /* + * Maximum size for sparse file: + * 16 aligned up clusters should fit in 32 bits. + * xfstest 308 on sparse file. + */ + sbi->maxbytes_sparse = (1ull << (cluster_bits + 32)) - 1 - + (sbi->cluster_size << NTFS_LZNT_CUNIT); sb->s_maxbytes = 0xFFFFFFFFull << cluster_bits; #endif @@ -1259,6 +1288,8 @@ static int ntfs_init_from_boot(struct super_block *sb, u32 sector_size, } out: + trace_ntfs3_init_from_boot(sb, sector_size, boot_sector_size, + !!boot_block, err); brelse(bh); if (err == -EINVAL && !boot_block && dev_size0 > PAGE_SHIFT) { @@ -1777,12 +1808,16 @@ static int ntfs_fill_super(struct super_block *sb, struct fs_context *fc) } ntfs_create_procdir(sb); + trace_ntfs3_fill_super(sb, ro, sbi->cluster_size, sbi->record_size, + sbi->index_size, 0); return 0; put_inode_out: iput(inode); out: + trace_ntfs3_fill_super(sb, ro, sbi->cluster_size, sbi->record_size, + sbi->index_size, err); /* sbi->options == options */ if (options) { put_mount_options(sbi->options); @@ -1915,6 +1950,7 @@ static int ntfs_init_fs_context(struct fs_context *fc) opts->fs_fmask_inv = opts->fs_dmask_inv = ~current_umask(); opts->prealloc = 1; opts->ads = 1; + opts->ugm = 1; #ifdef CONFIG_NTFS3_FS_POSIX_ACL /* Set the default value 'acl' */ diff --git a/fs/ntfs3/xattr.c b/fs/ntfs3/xattr.c index 941d516ab8dd..7a138e7d0be7 100644 --- a/fs/ntfs3/xattr.c +++ b/fs/ntfs3/xattr.c @@ -146,29 +146,32 @@ static int ntfs_read_ea(struct ntfs_inode *ni, struct EA_FULL **ea, for (off = 0; off < size; off += ea_size) { const struct EA_FULL *ef = Add2Ptr(ea_p, off); u32 bytes = size - off; - size_t need; - - /* Check if we can use field ea->size. */ - if (bytes < sizeof(ef->size)) - goto out1; + u32 packed_size; /* Check if we can use fields ef->name_len and ef->elength. */ if (bytes < offsetof(struct EA_FULL, name)) goto out1; - /* Size needed to hold this record's name and value. */ - need = struct_size(ef, name, - 1 + ef->name_len + le16_to_cpu(ef->elength)); + /* + * The name and the value have to fit in the entry whether or + * not ef->size is set, because ntfs_get_ea() copies elength + * bytes out of this buffer and checks them only against the + * caller supplied buffer size. + */ + packed_size = + ALIGN(struct_size(ef, name, + 1 + ef->name_len + + le16_to_cpu(ef->elength)), + 4); if (ef->size) { ea_size = le32_to_cpu(ef->size); - /* ef->size must fit the list and cover the record. */ - if (ea_size > bytes || ea_size < need) + if (ea_size > bytes || packed_size > ea_size) goto out1; continue; } - ea_size = ALIGN(need, 4); + ea_size = packed_size; if (ea_size > bytes) goto out1; } @@ -783,7 +786,7 @@ static int ntfs_getxattr(const struct xattr_handler *handler, struct dentry *de, if (!buffer) { err = sizeof(u8); } else if (size < sizeof(u8)) { - err = -ENODATA; + err = -ERANGE; } else { err = sizeof(u8); *(u8 *)buffer = le32_to_cpu(ni->std_fa); @@ -797,7 +800,7 @@ static int ntfs_getxattr(const struct xattr_handler *handler, struct dentry *de, if (!buffer) { err = sizeof(u32); } else if (size < sizeof(u32)) { - err = -ENODATA; + err = -ERANGE; } else { err = sizeof(u32); *(u32 *)buffer = le32_to_cpu(ni->std_fa); @@ -837,7 +840,7 @@ static int ntfs_getxattr(const struct xattr_handler *handler, struct dentry *de, if (!buffer) { err = sd_size; } else if (size < sd_size) { - err = -ENODATA; + err = -ERANGE; } else { err = sd_size; memcpy(buffer, sd, sd_size); @@ -976,8 +979,10 @@ static noinline int ntfs_setxattr(const struct xattr_handler *handler, NULL); out: - inode_set_ctime_current(inode); - mark_inode_dirty(inode); + if (!err) { + inode_set_ctime_current(inode); + mark_inode_dirty(inode); + } return err; } @@ -993,6 +998,13 @@ int ntfs_save_wsl_perm(struct inode *inode, __le32 *ea_size) __le32 value; struct ntfs_inode *ni = ntfs_i(inode); + if (!ni->mi.sbi->options->ugm) { + /* Do not save uid/gid/mode/dev in xattr. */ + if (ea_size) + *ea_size = 0; + return 0; + } + ni_lock(ni); value = cpu_to_le32(i_uid_read(inode)); err = ntfs_set_ea(inode, "$LXUID", sizeof("$LXUID") - 1, &value, diff --git a/include/trace/events/ntfs3.h b/include/trace/events/ntfs3.h new file mode 100644 index 000000000000..750300068984 --- /dev/null +++ b/include/trace/events/ntfs3.h @@ -0,0 +1,389 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#undef TRACE_SYSTEM +#define TRACE_SYSTEM ntfs3 + +#if !defined(_TRACE_NTFS3_H) || defined(TRACE_HEADER_MULTI_READ) +#define _TRACE_NTFS3_H + +#include +#include + +TRACE_EVENT(ntfs3_fill_super, + TP_PROTO(struct super_block *sb, bool ro, u32 cluster_size, + u32 record_size, u32 index_size, int err), + TP_ARGS(sb, ro, cluster_size, record_size, index_size, err), + TP_STRUCT__entry( + __field(dev_t, dev) + __field(u32, cluster_size) + __field(u32, record_size) + __field(u32, index_size) + __field(int, err) + __field(bool, ro) + ), + TP_fast_assign( + __entry->dev = sb->s_bdev->bd_dev; + __entry->cluster_size = cluster_size; + __entry->record_size = record_size; + __entry->index_size = index_size; + __entry->err = err; + __entry->ro = ro; + ), + TP_printk("dev=(%d,%d) ro=%d cluster=%u record=%u index=%u err=%d", + MAJOR(__entry->dev), MINOR(__entry->dev), __entry->ro, + __entry->cluster_size, __entry->record_size, + __entry->index_size, __entry->err) +); + +TRACE_EVENT(ntfs3_init_from_boot, + TP_PROTO(struct super_block *sb, u32 media_sector_size, + u32 boot_sector_size, bool used_alt_boot, int err), + TP_ARGS(sb, media_sector_size, boot_sector_size, used_alt_boot, err), + TP_STRUCT__entry( + __field(dev_t, dev) + __field(u32, media_sector_size) + __field(u32, boot_sector_size) + __field(int, err) + __field(bool, used_alt_boot) + ), + TP_fast_assign( + __entry->dev = sb->s_bdev->bd_dev; + __entry->media_sector_size = media_sector_size; + __entry->boot_sector_size = boot_sector_size; + __entry->err = err; + __entry->used_alt_boot = used_alt_boot; + ), + TP_printk("dev=(%d,%d) media_sector=%u boot_sector=%u alt_boot=%d err=%d", + MAJOR(__entry->dev), MINOR(__entry->dev), + __entry->media_sector_size, __entry->boot_sector_size, + __entry->used_alt_boot, __entry->err) +); + +TRACE_EVENT(ntfs3_log_replay, + TP_PROTO(struct inode *inode, bool initialized, int err), + TP_ARGS(inode, initialized, err), + TP_STRUCT__entry( + __field(unsigned long, ino) + __field(loff_t, size) + __field(dev_t, dev) + __field(int, err) + __field(bool, initialized) + ), + TP_fast_assign( + __entry->ino = inode->i_ino; + __entry->size = i_size_read(inode); + __entry->dev = inode->i_sb->s_dev; + __entry->err = err; + __entry->initialized = initialized; + ), + TP_printk("dev=(%d,%d) ino=%lu size=%lld initialized=%d err=%d", + MAJOR(__entry->dev), MINOR(__entry->dev), __entry->ino, + __entry->size, __entry->initialized, __entry->err) +); + +TRACE_EVENT(ntfs3_lookup, + TP_PROTO(struct inode *dir, struct dentry *dentry), + TP_ARGS(dir, dentry), + TP_STRUCT__entry( + __field(unsigned long, parent_ino) + __field(dev_t, dev) + __string(name, dentry->d_name.name) + ), + TP_fast_assign( + __entry->parent_ino = dir->i_ino; + __entry->dev = dir->i_sb->s_dev; + __assign_str(name); + ), + TP_printk("dev=(%d,%d) parent=%lu name=%s", + MAJOR(__entry->dev), MINOR(__entry->dev), + __entry->parent_ino, __get_str(name)) +); + +TRACE_EVENT(ntfs3_rename, + TP_PROTO(struct inode *dir, struct dentry *dentry, + struct inode *new_dir, struct dentry *new_dentry), + TP_ARGS(dir, dentry, new_dir, new_dentry), + TP_STRUCT__entry( + __field(unsigned long, dir_ino) + __field(unsigned long, new_dir_ino) + __field(unsigned long, ino) + __field(dev_t, dev) + __string(old_name, dentry->d_name.name) + __string(new_name, new_dentry->d_name.name) + ), + TP_fast_assign( + __entry->dir_ino = dir->i_ino; + __entry->new_dir_ino = new_dir->i_ino; + __entry->ino = d_inode(dentry)->i_ino; + __entry->dev = dir->i_sb->s_dev; + __assign_str(old_name); + __assign_str(new_name); + ), + TP_printk("dev=(%d,%d) dir=%lu new_dir=%lu ino=%lu old=%s new=%s", + MAJOR(__entry->dev), MINOR(__entry->dev), + __entry->dir_ino, __entry->new_dir_ino, __entry->ino, + __get_str(old_name), __get_str(new_name)) +); + +TRACE_EVENT(ntfs3_create_inode, + TP_PROTO(struct inode *dir, struct dentry *dentry), + TP_ARGS(dir, dentry), + TP_STRUCT__entry( + __field(unsigned long, parent_ino) + __field(dev_t, dev) + __string(name, dentry->d_name.name) + ), + TP_fast_assign( + __entry->parent_ino = dir->i_ino; + __entry->dev = dir->i_sb->s_dev; + __assign_str(name); + ), + TP_printk("dev=(%d,%d) parent=%lu name=%s", + MAJOR(__entry->dev), MINOR(__entry->dev), + __entry->parent_ino, __get_str(name)) +); + +TRACE_EVENT(ntfs3_dir_search_u, + TP_PROTO(struct inode *dir, unsigned int name_len), + TP_ARGS(dir, name_len), + TP_STRUCT__entry( + __field(unsigned long, dir_ino) + __field(dev_t, dev) + __field(unsigned int, name_len) + ), + TP_fast_assign( + __entry->dir_ino = dir->i_ino; + __entry->dev = dir->i_sb->s_dev; + __entry->name_len = name_len; + ), + TP_printk("dev=(%d,%d) dir=%lu name_len=%u", + MAJOR(__entry->dev), MINOR(__entry->dev), + __entry->dir_ino, __entry->name_len) +); + +TRACE_EVENT(ntfs3_indx_find, + TP_PROTO(struct inode *inode, u8 type, size_t key_len), + TP_ARGS(inode, type, key_len), + TP_STRUCT__entry( + __field(unsigned long, ino) + __field(size_t, key_len) + __field(dev_t, dev) + __field(u8, type) + ), + TP_fast_assign( + __entry->ino = inode->i_ino; + __entry->key_len = key_len; + __entry->dev = inode->i_sb->s_dev; + __entry->type = type; + ), + TP_printk("dev=(%d,%d) ino=%lu type=%u key_len=%zu", + MAJOR(__entry->dev), MINOR(__entry->dev), + __entry->ino, __entry->type, __entry->key_len) +); + +TRACE_EVENT(ntfs3_indx_insert_entry, + TP_PROTO(struct inode *inode, u8 type, u16 key_len, bool undo), + TP_ARGS(inode, type, key_len, undo), + TP_STRUCT__entry( + __field(unsigned long, ino) + __field(dev_t, dev) + __field(u16, key_len) + __field(u8, type) + __field(bool, undo) + ), + TP_fast_assign( + __entry->ino = inode->i_ino; + __entry->dev = inode->i_sb->s_dev; + __entry->key_len = key_len; + __entry->type = type; + __entry->undo = undo; + ), + TP_printk("dev=(%d,%d) ino=%lu type=%u key_len=%u undo=%d", + MAJOR(__entry->dev), MINOR(__entry->dev), + __entry->ino, __entry->type, __entry->key_len, + __entry->undo) +); + +TRACE_EVENT(ntfs3_indx_delete_entry, + TP_PROTO(struct inode *inode, u8 type, u32 key_len), + TP_ARGS(inode, type, key_len), + TP_STRUCT__entry( + __field(unsigned long, ino) + __field(u32, key_len) + __field(dev_t, dev) + __field(u8, type) + ), + TP_fast_assign( + __entry->ino = inode->i_ino; + __entry->key_len = key_len; + __entry->dev = inode->i_sb->s_dev; + __entry->type = type; + ), + TP_printk("dev=(%d,%d) ino=%lu type=%u key_len=%u", + MAJOR(__entry->dev), MINOR(__entry->dev), + __entry->ino, __entry->type, __entry->key_len) +); + +TRACE_EVENT(ntfs3_attr_allocate_clusters, + TP_PROTO(struct super_block *sb, u64 vcn, u64 lcn, u64 len, u32 opt), + TP_ARGS(sb, vcn, lcn, len, opt), + TP_STRUCT__entry( + __field(u64, vcn) + __field(u64, lcn) + __field(u64, len) + __field(dev_t, dev) + __field(u32, opt) + ), + TP_fast_assign( + __entry->vcn = vcn; + __entry->lcn = lcn; + __entry->len = len; + __entry->dev = sb->s_dev; + __entry->opt = opt; + ), + TP_printk("dev=(%d,%d) vcn=%llu lcn=%llu len=%llu opt=0x%x", + MAJOR(__entry->dev), MINOR(__entry->dev), + __entry->vcn, __entry->lcn, __entry->len, __entry->opt) +); + +TRACE_EVENT(ntfs3_attr_set_size_ex, + TP_PROTO(struct inode *inode, u32 type, u64 old_size, u64 new_size, + bool keep_prealloc, bool no_da), + TP_ARGS(inode, type, old_size, new_size, keep_prealloc, no_da), + TP_STRUCT__entry( + __field(u64, old_size) + __field(u64, new_size) + __field(unsigned long, ino) + __field(dev_t, dev) + __field(u32, type) + __field(bool, keep_prealloc) + __field(bool, no_da) + ), + TP_fast_assign( + __entry->old_size = old_size; + __entry->new_size = new_size; + __entry->ino = inode->i_ino; + __entry->dev = inode->i_sb->s_dev; + __entry->type = type; + __entry->keep_prealloc = keep_prealloc; + __entry->no_da = no_da; + ), + TP_printk("dev=(%d,%d) ino=%lu type=0x%x old_size=%llu new_size=%llu keep_prealloc=%d no_da=%d", + MAJOR(__entry->dev), MINOR(__entry->dev), __entry->ino, + __entry->type, __entry->old_size, __entry->new_size, + __entry->keep_prealloc, __entry->no_da) +); + +TRACE_EVENT(ntfs3_attr_data_get_block, + TP_PROTO(struct inode *inode, u64 vcn, u64 clen, bool *new, + bool zero, bool no_da), + TP_ARGS(inode, vcn, clen, new, zero, no_da), + TP_STRUCT__entry( + __field(u64, vcn) + __field(u64, clen) + __field(unsigned long, ino) + __field(dev_t, dev) + __field(bool, create) + __field(bool, zero) + __field(bool, no_da) + ), + TP_fast_assign( + __entry->vcn = vcn; + __entry->clen = clen; + __entry->ino = inode->i_ino; + __entry->dev = inode->i_sb->s_dev; + __entry->create = !!new; + __entry->zero = zero; + __entry->no_da = no_da; + ), + TP_printk("dev=(%d,%d) ino=%lu vcn=%llu clen=%llu create=%d zero=%d no_da=%d", + MAJOR(__entry->dev), MINOR(__entry->dev), __entry->ino, + __entry->vcn, __entry->clen, __entry->create, + __entry->zero, __entry->no_da) +); + +TRACE_EVENT(ntfs3_iomap_begin, + TP_PROTO(struct inode *inode, loff_t offset, loff_t length, + unsigned int flags), + TP_ARGS(inode, offset, length, flags), + TP_STRUCT__entry( + __field(unsigned long, ino) + __field(loff_t, offset) + __field(loff_t, length) + __field(dev_t, dev) + __field(unsigned int, flags) + ), + TP_fast_assign( + __entry->ino = inode->i_ino; + __entry->offset = offset; + __entry->length = length; + __entry->dev = inode->i_sb->s_dev; + __entry->flags = flags; + ), + TP_printk("dev=(%d,%d) ino=%lu offset=%lld length=%lld flags=0x%x", + MAJOR(__entry->dev), MINOR(__entry->dev), + __entry->ino, __entry->offset, __entry->length, + __entry->flags) +); + +TRACE_EVENT(ntfs3_iomap_end, + TP_PROTO(struct inode *inode, loff_t pos, loff_t length, + ssize_t written, unsigned int flags), + TP_ARGS(inode, pos, length, written, flags), + TP_STRUCT__entry( + __field(unsigned long, ino) + __field(loff_t, pos) + __field(loff_t, length) + __field(ssize_t, written) + __field(dev_t, dev) + __field(unsigned int, flags) + ), + TP_fast_assign( + __entry->ino = inode->i_ino; + __entry->pos = pos; + __entry->length = length; + __entry->written = written; + __entry->dev = inode->i_sb->s_dev; + __entry->flags = flags; + ), + TP_printk("dev=(%d,%d) ino=%lu pos=%lld length=%lld written=%zd flags=0x%x", + MAJOR(__entry->dev), MINOR(__entry->dev), + __entry->ino, __entry->pos, __entry->length, + __entry->written, __entry->flags) +); + +DECLARE_EVENT_CLASS(ntfs3_file_class, + TP_PROTO(struct kiocb *iocb, struct iov_iter *iter), + TP_ARGS(iocb, iter), + TP_STRUCT__entry( + __field(loff_t, size) + __field(loff_t, offset) + __field(size_t, count) + __field(unsigned long, ino) + __field(dev_t, dev) + ), + TP_fast_assign( + __entry->size = i_size_read(file_inode(iocb->ki_filp)); + __entry->offset = iocb->ki_pos; + __entry->count = iov_iter_count(iter); + __entry->ino = file_inode(iocb->ki_filp)->i_ino; + __entry->dev = file_inode(iocb->ki_filp)->i_sb->s_dev; + ), + TP_printk("dev=(%d,%d) ino=%lu size=%lld pos=%lld bytecount=%zu", + MAJOR(__entry->dev), MINOR(__entry->dev), + __entry->ino, __entry->size, __entry->offset, + __entry->count) +); + +DEFINE_EVENT(ntfs3_file_class, ntfs3_file_read_iter, + TP_PROTO(struct kiocb *iocb, struct iov_iter *iter), + TP_ARGS(iocb, iter) +); + +DEFINE_EVENT(ntfs3_file_class, ntfs3_file_write_iter, + TP_PROTO(struct kiocb *iocb, struct iov_iter *iter), + TP_ARGS(iocb, iter) +); + +#endif /* _TRACE_NTFS3_H */ + +#include