]> git.proxmox.com Git - mirror_ubuntu-kernels.git/blobdiff - fs/ntfs3/inode.c
ntfs3: remove ->writepage
[mirror_ubuntu-kernels.git] / fs / ntfs3 / inode.c
index d5a3afbbbfd8cf639fa94d71e19f9e007b69bf68..6b50b6e3237876d5c12fc93d6a15250beb1fb9c0 100644 (file)
@@ -81,7 +81,7 @@ static struct inode *ntfs_read_mft(struct inode *inode,
                         le16_to_cpu(ref->seq), le16_to_cpu(rec->seq));
                goto out;
        } else if (!is_rec_inuse(rec)) {
-               err = -EINVAL;
+               err = -ESTALE;
                ntfs_err(sb, "Inode r=%x is not in use!", (u32)ino);
                goto out;
        }
@@ -92,8 +92,10 @@ static struct inode *ntfs_read_mft(struct inode *inode,
                goto out;
        }
 
-       if (!is_rec_base(rec))
-               goto Ok;
+       if (!is_rec_base(rec)) {
+               err = -EINVAL;
+               goto out;
+       }
 
        /* Record should contain $I30 root. */
        is_dir = rec->flags & RECORD_FLAG_DIR;
@@ -129,6 +131,16 @@ next_attr:
        rsize = attr->non_res ? 0 : le32_to_cpu(attr->res.data_size);
        asize = le32_to_cpu(attr->size);
 
+       if (le16_to_cpu(attr->name_off) + attr->name_len > asize)
+               goto out;
+
+       if (attr->non_res) {
+               t64 = le64_to_cpu(attr->nres.alloc_size);
+               if (le64_to_cpu(attr->nres.data_size) > t64 ||
+                   le64_to_cpu(attr->nres.valid_size) > t64)
+                       goto out;
+       }
+
        switch (attr->type) {
        case ATTR_STD:
                if (attr->non_res ||
@@ -364,7 +376,13 @@ next_attr:
 attr_unpack_run:
        roff = le16_to_cpu(attr->nres.run_off);
 
+       if (roff > asize) {
+               err = -EINVAL;
+               goto out;
+       }
+
        t64 = le64_to_cpu(attr->nres.svcn);
+
        err = run_unpack_ex(run, sbi, ino, t64, le64_to_cpu(attr->nres.evcn),
                            t64, Add2Ptr(attr, roff), asize - roff);
        if (err < 0)
@@ -450,7 +468,6 @@ end_enum:
                inode->i_flags |= S_NOSEC;
        }
 
-Ok:
        if (ino == MFT_REC_MFT && !sb->s_root)
                sbi->mft.ni = NULL;
 
@@ -504,6 +521,9 @@ struct inode *ntfs_iget5(struct super_block *sb, const struct MFT_REF *ref,
                _ntfs_bad_inode(inode);
        }
 
+       if (IS_ERR(inode) && name)
+               ntfs_set_state(sb->s_fs_info, NTFS_DIRTY_ERROR);
+
        return inode;
 }
 
@@ -535,17 +555,6 @@ static noinline int ntfs_get_block_vbo(struct inode *inode, u64 vbo,
        clear_buffer_new(bh);
        clear_buffer_uptodate(bh);
 
-       /* Direct write uses 'create=0'. */
-       if (!create && vbo >= ni->i_valid) {
-               /* Out of valid. */
-               return 0;
-       }
-
-       if (vbo >= inode->i_size) {
-               /* Out of size. */
-               return 0;
-       }
-
        if (is_resident(ni)) {
                ni_lock(ni);
                err = attr_data_read_resident(ni, page);
@@ -561,7 +570,8 @@ static noinline int ntfs_get_block_vbo(struct inode *inode, u64 vbo,
        off = vbo & sbi->cluster_mask;
        new = false;
 
-       err = attr_data_get_block(ni, vcn, 1, &lcn, &len, create ? &new : NULL);
+       err = attr_data_get_block(ni, vcn, 1, &lcn, &len, create ? &new : NULL,
+                                 create && sbi->cluster_size > PAGE_SIZE);
        if (err)
                goto out;
 
@@ -579,11 +589,8 @@ static noinline int ntfs_get_block_vbo(struct inode *inode, u64 vbo,
                WARN_ON(1);
        }
 
-       if (new) {
+       if (new)
                set_buffer_new(bh);
-               if ((len << cluster_bits) > block_size)
-                       ntfs_sparse_cluster(inode, page, vcn, len);
-       }
 
        lbo = ((u64)lcn << cluster_bits) + off;
 
@@ -611,7 +618,6 @@ static noinline int ntfs_get_block_vbo(struct inode *inode, u64 vbo,
                }
        } else if (vbo >= valid) {
                /* Read out of valid data. */
-               /* Should never be here 'cause already checked. */
                clear_buffer_mapped(bh);
        } else if (vbo + bytes <= valid) {
                /* Normal read. */
@@ -826,32 +832,29 @@ out:
        return err;
 }
 
-static int ntfs_writepage(struct page *page, struct writeback_control *wbc)
+static int ntfs_resident_writepage(struct page *page,
+               struct writeback_control *wbc, void *data)
 {
-       struct address_space *mapping = page->mapping;
-       struct inode *inode = mapping->host;
-       struct ntfs_inode *ni = ntfs_i(inode);
-       int err;
+       struct address_space *mapping = data;
+       struct ntfs_inode *ni = ntfs_i(mapping->host);
+       int ret;
 
-       if (is_resident(ni)) {
-               ni_lock(ni);
-               err = attr_data_write_resident(ni, page);
-               ni_unlock(ni);
-               if (err != E_NTFS_NONRESIDENT) {
-                       unlock_page(page);
-                       return err;
-               }
-       }
+       ni_lock(ni);
+       ret = attr_data_write_resident(ni, page);
+       ni_unlock(ni);
 
-       return block_write_full_page(page, ntfs_get_block, wbc);
+       if (ret != E_NTFS_NONRESIDENT)
+               unlock_page(page);
+       mapping_set_error(mapping, ret);
+       return ret;
 }
 
 static int ntfs_writepages(struct address_space *mapping,
                           struct writeback_control *wbc)
 {
-       /* Redirect call to 'ntfs_writepage' for resident files. */
        if (is_resident(ntfs_i(mapping->host)))
-               return generic_writepages(mapping, wbc);
+               return write_cache_pages(mapping, wbc, ntfs_resident_writepage,
+                                        mapping);
        return mpage_writepages(mapping, wbc, ntfs_get_block);
 }
 
@@ -953,6 +956,11 @@ int ntfs_write_end(struct file *file, struct address_space *mapping,
                        dirty = true;
                }
 
+               if (pos + err > inode->i_size) {
+                       inode->i_size = pos + err;
+                       dirty = true;
+               }
+
                if (dirty)
                        mark_inode_dirty(inode);
        }
@@ -1162,6 +1170,18 @@ out:
        return ERR_PTR(err);
 }
 
+/*
+ * ntfs_create_inode
+ *
+ * Helper function for:
+ * - ntfs_create
+ * - ntfs_mknod
+ * - ntfs_symlink
+ * - ntfs_mkdir
+ * - ntfs_atomic_open
+ * 
+ * NOTE: if fnd != NULL (ntfs_atomic_open) then @dir is locked
+ */
 struct inode *ntfs_create_inode(struct user_namespace *mnt_userns,
                                struct inode *dir, struct dentry *dentry,
                                const struct cpu_str *uni, umode_t mode,
@@ -1191,7 +1211,8 @@ struct inode *ntfs_create_inode(struct user_namespace *mnt_userns,
        struct REPARSE_DATA_BUFFER *rp = NULL;
        bool rp_inserted = false;
 
-       ni_lock_dir(dir_ni);
+       if (!fnd)
+               ni_lock_dir(dir_ni);
 
        dir_root = indx_get_root(&dir_ni->dir, dir_ni, NULL, NULL);
        if (!dir_root) {
@@ -1254,6 +1275,10 @@ struct inode *ntfs_create_inode(struct user_namespace *mnt_userns,
                fa = FILE_ATTRIBUTE_ARCHIVE;
        }
 
+       /* If option "hide_dot_files" then set hidden attribute for dot files. */
+       if (sbi->options->hide_dot_files && name->name[0] == '.')
+               fa |= FILE_ATTRIBUTE_HIDDEN;
+
        if (!(mode & 0222))
                fa |= FILE_ATTRIBUTE_READONLY;
 
@@ -1339,6 +1364,13 @@ struct inode *ntfs_create_inode(struct user_namespace *mnt_userns,
        mi_get_ref(&ni->mi, &new_de->ref);
 
        fname = (struct ATTR_FILE_NAME *)(new_de + 1);
+
+       if (sbi->options->windows_names &&
+           !valid_windows_name(sbi, (struct le_str *)&fname->name_len)) {
+               err = -EINVAL;
+               goto out4;
+       }
+
        mi_get_ref(&dir_ni->mi, &fname->home);
        fname->dup.cr_time = fname->dup.m_time = fname->dup.c_time =
                fname->dup.a_time = std5->cr_time;
@@ -1502,8 +1534,8 @@ struct inode *ntfs_create_inode(struct user_namespace *mnt_userns,
                                cpu_to_le64(ntfs_up_cluster(sbi, nsize));
 
                        err = attr_allocate_clusters(sbi, &ni->file.run, 0, 0,
-                                                    clst, NULL, 0, &alen, 0,
-                                                    NULL);
+                                                    clst, NULL, ALLOCATE_DEF,
+                                                    &alen, 0, NULL, NULL);
                        if (err)
                                goto out5;
 
@@ -1550,7 +1582,8 @@ struct inode *ntfs_create_inode(struct user_namespace *mnt_userns,
                goto out6;
 
        /* Unlock parent directory before ntfs_init_acl. */
-       ni_unlock(dir_ni);
+       if (!fnd)
+               ni_unlock(dir_ni);
 
        inode->i_generation = le16_to_cpu(rec->seq);
 
@@ -1610,7 +1643,8 @@ struct inode *ntfs_create_inode(struct user_namespace *mnt_userns,
 out7:
 
        /* Undo 'indx_insert_entry'. */
-       ni_lock_dir(dir_ni);
+       if (!fnd)
+               ni_lock_dir(dir_ni);
        indx_delete_entry(&dir_ni->dir, dir_ni, new_de + 1,
                          le16_to_cpu(new_de->key_size), sbi);
        /* ni_unlock(dir_ni); will be called later. */
@@ -1619,10 +1653,8 @@ out6:
                ntfs_remove_reparse(sbi, IO_REPARSE_TAG_SYMLINK, &new_de->ref);
 
 out5:
-       if (S_ISDIR(mode) || run_is_empty(&ni->file.run))
-               goto out4;
-
-       run_deallocate(sbi, &ni->file.run, false);
+       if (!S_ISDIR(mode))
+               run_deallocate(sbi, &ni->file.run, false);
 
 out4:
        clear_rec_inuse(rec);
@@ -1638,7 +1670,8 @@ out2:
 
 out1:
        if (err) {
-               ni_unlock(dir_ni);
+               if (!fnd)
+                       ni_unlock(dir_ni);
                return ERR_PTR(err);
        }
 
@@ -1746,7 +1779,103 @@ void ntfs_evict_inode(struct inode *inode)
        ni_clear(ntfs_i(inode));
 }
 
-static noinline int ntfs_readlink_hlp(struct inode *inode, char *buffer,
+/*
+ * ntfs_translate_junction
+ *
+ * Translate a Windows junction target to the Linux equivalent.
+ * On junctions, targets are always absolute (they include the drive
+ * letter). We have no way of knowing if the target is for the current
+ * mounted device or not so we just assume it is.
+ */
+static int ntfs_translate_junction(const struct super_block *sb,
+                                  const struct dentry *link_de, char *target,
+                                  int target_len, int target_max)
+{
+       int tl_len, err = target_len;
+       char *link_path_buffer = NULL, *link_path;
+       char *translated = NULL;
+       char *target_start;
+       int copy_len;
+
+       link_path_buffer = kmalloc(PATH_MAX, GFP_NOFS);
+       if (!link_path_buffer) {
+               err = -ENOMEM;
+               goto out;
+       }
+       /* Get link path, relative to mount point */
+       link_path = dentry_path_raw(link_de, link_path_buffer, PATH_MAX);
+       if (IS_ERR(link_path)) {
+               ntfs_err(sb, "Error getting link path");
+               err = -EINVAL;
+               goto out;
+       }
+
+       translated = kmalloc(PATH_MAX, GFP_NOFS);
+       if (!translated) {
+               err = -ENOMEM;
+               goto out;
+       }
+
+       /* Make translated path a relative path to mount point */
+       strcpy(translated, "./");
+       ++link_path; /* Skip leading / */
+       for (tl_len = sizeof("./") - 1; *link_path; ++link_path) {
+               if (*link_path == '/') {
+                       if (PATH_MAX - tl_len < sizeof("../")) {
+                               ntfs_err(sb,
+                                        "Link path %s has too many components",
+                                        link_path);
+                               err = -EINVAL;
+                               goto out;
+                       }
+                       strcpy(translated + tl_len, "../");
+                       tl_len += sizeof("../") - 1;
+               }
+       }
+
+       /* Skip drive letter */
+       target_start = target;
+       while (*target_start && *target_start != ':')
+               ++target_start;
+
+       if (!*target_start) {
+               ntfs_err(sb, "Link target (%s) missing drive separator",
+                        target);
+               err = -EINVAL;
+               goto out;
+       }
+
+       /* Skip drive separator and leading /, if exists */
+       target_start += 1 + (target_start[1] == '/');
+       copy_len = target_len - (target_start - target);
+
+       if (PATH_MAX - tl_len <= copy_len) {
+               ntfs_err(sb, "Link target %s too large for buffer (%d <= %d)",
+                        target_start, PATH_MAX - tl_len, copy_len);
+               err = -EINVAL;
+               goto out;
+       }
+
+       /* translated path has a trailing / and target_start does not */
+       strcpy(translated + tl_len, target_start);
+       tl_len += copy_len;
+       if (target_max <= tl_len) {
+               ntfs_err(sb, "Target path %s too large for buffer (%d <= %d)",
+                        translated, target_max, tl_len);
+               err = -EINVAL;
+               goto out;
+       }
+       strcpy(target, translated);
+       err = tl_len;
+
+out:
+       kfree(link_path_buffer);
+       kfree(translated);
+       return err;
+}
+
+static noinline int ntfs_readlink_hlp(const struct dentry *link_de,
+                                     struct inode *inode, char *buffer,
                                      int buflen)
 {
        int i, err = -EINVAL;
@@ -1889,6 +2018,11 @@ static noinline int ntfs_readlink_hlp(struct inode *inode, char *buffer,
 
        /* Always set last zero. */
        buffer[err] = 0;
+
+       /* If this is a junction, translate the link target. */
+       if (rp->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT)
+               err = ntfs_translate_junction(sb, link_de, buffer, err, buflen);
+
 out:
        kfree(to_free);
        return err;
@@ -1907,7 +2041,7 @@ static const char *ntfs_get_link(struct dentry *de, struct inode *inode,
        if (!ret)
                return ERR_PTR(-ENOMEM);
 
-       err = ntfs_readlink_hlp(inode, ret, PAGE_SIZE);
+       err = ntfs_readlink_hlp(de, inode, ret, PAGE_SIZE);
        if (err < 0) {
                kfree(ret);
                return ERR_PTR(err);
@@ -1929,13 +2063,13 @@ const struct inode_operations ntfs_link_inode_operations = {
 const struct address_space_operations ntfs_aops = {
        .read_folio     = ntfs_read_folio,
        .readahead      = ntfs_readahead,
-       .writepage      = ntfs_writepage,
        .writepages     = ntfs_writepages,
        .write_begin    = ntfs_write_begin,
        .write_end      = ntfs_write_end,
        .direct_IO      = ntfs_direct_IO,
        .bmap           = ntfs_bmap,
        .dirty_folio    = block_dirty_folio,
+       .migrate_folio  = buffer_migrate_folio,
        .invalidate_folio = block_invalidate_folio,
 };