summaryrefslogtreecommitdiff
path: root/fs/ntfs3/frecord.c
diff options
context:
space:
mode:
authorKonstantin Komarov <almaz.alexandrovich@paragon-software.com>2021-08-31 18:52:39 +0300
committerKonstantin Komarov <almaz.alexandrovich@paragon-software.com>2021-08-31 19:07:11 +0300
commit78ab59fee07f22464f32eafebab2bd97ba94ff2d (patch)
tree8c906238a8ffbc639ce1911295f6e961e6212f6e /fs/ntfs3/frecord.c
parenta97131c29c997e81b6fa1d1adf8f3ca07b63a2e1 (diff)
downloadlinux-78ab59fee07f22464f32eafebab2bd97ba94ff2d.tar.gz
fs/ntfs3: Rework file operations
Rename now works "Add new name and remove old name". "Remove old name and add new name" may result in bad inode if we can't add new name and then can't restore (add) old name. Signed-off-by: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
Diffstat (limited to 'fs/ntfs3/frecord.c')
-rw-r--r--fs/ntfs3/frecord.c342
1 files changed, 280 insertions, 62 deletions
diff --git a/fs/ntfs3/frecord.c b/fs/ntfs3/frecord.c
index 9d374827750b..3f48b612ec96 100644
--- a/fs/ntfs3/frecord.c
+++ b/fs/ntfs3/frecord.c
@@ -163,7 +163,7 @@ out:
/*
* ni_load_mi - Load mft_inode corresponded list_entry.
*/
-int ni_load_mi(struct ntfs_inode *ni, struct ATTR_LIST_ENTRY *le,
+int ni_load_mi(struct ntfs_inode *ni, const struct ATTR_LIST_ENTRY *le,
struct mft_inode **mi)
{
CLST rno;
@@ -402,7 +402,7 @@ int ni_remove_attr(struct ntfs_inode *ni, enum ATTR_TYPE type,
if (!attr)
return -ENOENT;
- mi_remove_attr(&ni->mi, attr);
+ mi_remove_attr(ni, &ni->mi, attr);
return 0;
}
@@ -441,7 +441,7 @@ next_le2:
if (!attr)
return -ENOENT;
- mi_remove_attr(mi, attr);
+ mi_remove_attr(ni, mi, attr);
if (PtrOffset(ni->attr_list.le, le) >= ni->attr_list.size)
return 0;
@@ -454,12 +454,11 @@ next_le2:
*
* Return: Not full constructed attribute or NULL if not possible to create.
*/
-static struct ATTRIB *ni_ins_new_attr(struct ntfs_inode *ni,
- struct mft_inode *mi,
- struct ATTR_LIST_ENTRY *le,
- enum ATTR_TYPE type, const __le16 *name,
- u8 name_len, u32 asize, u16 name_off,
- CLST svcn)
+static struct ATTRIB *
+ni_ins_new_attr(struct ntfs_inode *ni, struct mft_inode *mi,
+ struct ATTR_LIST_ENTRY *le, enum ATTR_TYPE type,
+ const __le16 *name, u8 name_len, u32 asize, u16 name_off,
+ CLST svcn, struct ATTR_LIST_ENTRY **ins_le)
{
int err;
struct ATTRIB *attr;
@@ -507,6 +506,8 @@ static struct ATTRIB *ni_ins_new_attr(struct ntfs_inode *ni,
le->ref = ref;
out:
+ if (ins_le)
+ *ins_le = le;
return attr;
}
@@ -599,7 +600,7 @@ static int ni_repack(struct ntfs_inode *ni)
if (next_svcn >= evcn + 1) {
/* We can remove this attribute segment. */
al_remove_le(ni, le);
- mi_remove_attr(mi, attr);
+ mi_remove_attr(NULL, mi, attr);
le = le_p;
continue;
}
@@ -695,8 +696,8 @@ static int ni_try_remove_attr_list(struct ntfs_inode *ni)
free -= asize;
}
- /* Is seems that attribute list can be removed from primary record. */
- mi_remove_attr(&ni->mi, attr_list);
+ /* It seems that attribute list can be removed from primary record. */
+ mi_remove_attr(NULL, &ni->mi, attr_list);
/*
* Repeat the cycle above and move all attributes to primary record.
@@ -724,7 +725,7 @@ static int ni_try_remove_attr_list(struct ntfs_inode *ni)
attr_ins->id = id;
/* Remove from original record. */
- mi_remove_attr(mi, attr);
+ mi_remove_attr(NULL, mi, attr);
}
run_deallocate(sbi, &ni->attr_list.run, true);
@@ -842,7 +843,8 @@ int ni_create_attr_list(struct ntfs_inode *ni)
memcpy(attr, b, asize);
attr->id = le_b[nb]->id;
- WARN_ON(!mi_remove_attr(&ni->mi, b));
+ /* Remove from primary record. */
+ WARN_ON(!mi_remove_attr(NULL, &ni->mi, b));
if (to_free <= asize)
break;
@@ -883,7 +885,8 @@ out:
static int ni_ins_attr_ext(struct ntfs_inode *ni, struct ATTR_LIST_ENTRY *le,
enum ATTR_TYPE type, const __le16 *name, u8 name_len,
u32 asize, CLST svcn, u16 name_off, bool force_ext,
- struct ATTRIB **ins_attr, struct mft_inode **ins_mi)
+ struct ATTRIB **ins_attr, struct mft_inode **ins_mi,
+ struct ATTR_LIST_ENTRY **ins_le)
{
struct ATTRIB *attr;
struct mft_inode *mi;
@@ -956,12 +959,14 @@ static int ni_ins_attr_ext(struct ntfs_inode *ni, struct ATTR_LIST_ENTRY *le,
/* Try to insert attribute into this subrecord. */
attr = ni_ins_new_attr(ni, mi, le, type, name, name_len, asize,
- name_off, svcn);
+ name_off, svcn, ins_le);
if (!attr)
continue;
if (ins_attr)
*ins_attr = attr;
+ if (ins_mi)
+ *ins_mi = mi;
return 0;
}
@@ -977,7 +982,7 @@ insert_ext:
}
attr = ni_ins_new_attr(ni, mi, le, type, name, name_len, asize,
- name_off, svcn);
+ name_off, svcn, ins_le);
if (!attr)
goto out2;
@@ -1016,7 +1021,8 @@ out:
static int ni_insert_attr(struct ntfs_inode *ni, enum ATTR_TYPE type,
const __le16 *name, u8 name_len, u32 asize,
u16 name_off, CLST svcn, struct ATTRIB **ins_attr,
- struct mft_inode **ins_mi)
+ struct mft_inode **ins_mi,
+ struct ATTR_LIST_ENTRY **ins_le)
{
struct ntfs_sb_info *sbi = ni->mi.sbi;
int err;
@@ -1045,7 +1051,7 @@ static int ni_insert_attr(struct ntfs_inode *ni, enum ATTR_TYPE type,
if (asize <= free) {
attr = ni_ins_new_attr(ni, &ni->mi, NULL, type, name, name_len,
- asize, name_off, svcn);
+ asize, name_off, svcn, ins_le);
if (attr) {
if (ins_attr)
*ins_attr = attr;
@@ -1059,7 +1065,8 @@ static int ni_insert_attr(struct ntfs_inode *ni, enum ATTR_TYPE type,
if (!is_mft || type != ATTR_DATA || svcn) {
/* This ATTRIB will be external. */
err = ni_ins_attr_ext(ni, NULL, type, name, name_len, asize,
- svcn, name_off, false, ins_attr, ins_mi);
+ svcn, name_off, false, ins_attr, ins_mi,
+ ins_le);
goto out;
}
@@ -1117,7 +1124,7 @@ static int ni_insert_attr(struct ntfs_inode *ni, enum ATTR_TYPE type,
t16 = le16_to_cpu(attr->name_off);
err = ni_ins_attr_ext(ni, le, attr->type, Add2Ptr(attr, t16),
attr->name_len, t32, attr_svcn(attr), t16,
- false, &eattr, NULL);
+ false, &eattr, NULL, NULL);
if (err)
return err;
@@ -1125,8 +1132,8 @@ static int ni_insert_attr(struct ntfs_inode *ni, enum ATTR_TYPE type,
memcpy(eattr, attr, t32);
eattr->id = id;
- /* Remove attrib from primary record. */
- mi_remove_attr(&ni->mi, attr);
+ /* Remove from primary record. */
+ mi_remove_attr(NULL, &ni->mi, attr);
/* attr now points to next attribute. */
if (attr->type == ATTR_END)
@@ -1136,7 +1143,7 @@ static int ni_insert_attr(struct ntfs_inode *ni, enum ATTR_TYPE type,
;
attr = ni_ins_new_attr(ni, &ni->mi, NULL, type, name, name_len, asize,
- name_off, svcn);
+ name_off, svcn, ins_le);
if (!attr) {
err = -EINVAL;
goto out;
@@ -1251,7 +1258,7 @@ static int ni_expand_mft_list(struct ntfs_inode *ni)
*/
attr = ni_ins_new_attr(ni, mi_min, NULL, ATTR_DATA, NULL, 0,
SIZEOF_NONRESIDENT + run_size,
- SIZEOF_NONRESIDENT, svcn);
+ SIZEOF_NONRESIDENT, svcn, NULL);
if (!attr) {
err = -EINVAL;
goto out;
@@ -1315,14 +1322,15 @@ int ni_expand_list(struct ntfs_inode *ni)
err = ni_ins_attr_ext(ni, le, attr->type, attr_name(attr),
attr->name_len, asize, attr_svcn(attr),
le16_to_cpu(attr->name_off), true,
- &ins_attr, NULL);
+ &ins_attr, NULL, NULL);
if (err)
goto out;
memcpy(ins_attr, attr, asize);
ins_attr->id = le->id;
- mi_remove_attr(&ni->mi, attr);
+ /* Remove from primary record. */
+ mi_remove_attr(NULL, &ni->mi, attr);
done += asize;
goto out;
@@ -1382,7 +1390,7 @@ int ni_insert_nonresident(struct ntfs_inode *ni, enum ATTR_TYPE type,
}
err = ni_insert_attr(ni, type, name, name_len, asize, name_off, svcn,
- &attr, mi);
+ &attr, mi, NULL);
if (err)
goto out;
@@ -1422,7 +1430,8 @@ out:
*/
int ni_insert_resident(struct ntfs_inode *ni, u32 data_size,
enum ATTR_TYPE type, const __le16 *name, u8 name_len,
- struct ATTRIB **new_attr, struct mft_inode **mi)
+ struct ATTRIB **new_attr, struct mft_inode **mi,
+ struct ATTR_LIST_ENTRY **le)
{
int err;
u32 name_size = ALIGN(name_len * sizeof(short), 8);
@@ -1430,7 +1439,7 @@ int ni_insert_resident(struct ntfs_inode *ni, u32 data_size,
struct ATTRIB *attr;
err = ni_insert_attr(ni, type, name, name_len, asize, SIZEOF_RESIDENT,
- 0, &attr, mi);
+ 0, &attr, mi, le);
if (err)
return err;
@@ -1439,8 +1448,13 @@ int ni_insert_resident(struct ntfs_inode *ni, u32 data_size,
attr->res.data_size = cpu_to_le32(data_size);
attr->res.data_off = cpu_to_le16(SIZEOF_RESIDENT + name_size);
- if (type == ATTR_NAME)
+ if (type == ATTR_NAME) {
attr->res.flags = RESIDENT_FLAG_INDEXED;
+
+ /* is_attr_indexed(attr)) == true */
+ le16_add_cpu(&ni->mi.mrec->hard_links, +1);
+ ni->mi.dirty = true;
+ }
attr->res.res = 0;
if (new_attr)
@@ -1452,22 +1466,13 @@ int ni_insert_resident(struct ntfs_inode *ni, u32 data_size,
/*
* ni_remove_attr_le - Remove attribute from record.
*/
-int ni_remove_attr_le(struct ntfs_inode *ni, struct ATTRIB *attr,
- struct ATTR_LIST_ENTRY *le)
+void ni_remove_attr_le(struct ntfs_inode *ni, struct ATTRIB *attr,
+ struct mft_inode *mi, struct ATTR_LIST_ENTRY *le)
{
- int err;
- struct mft_inode *mi;
-
- err = ni_load_mi(ni, le, &mi);
- if (err)
- return err;
-
- mi_remove_attr(mi, attr);
+ mi_remove_attr(ni, mi, attr);
if (le)
al_remove_le(ni, le);
-
- return 0;
}
/*
@@ -1549,10 +1554,12 @@ int ni_delete_all(struct ntfs_inode *ni)
/* ni_fname_name
*
- *Return: File name attribute by its value. */
+ * Return: File name attribute by its value.
+ */
struct ATTR_FILE_NAME *ni_fname_name(struct ntfs_inode *ni,
const struct cpu_str *uni,
const struct MFT_REF *home_dir,
+ struct mft_inode **mi,
struct ATTR_LIST_ENTRY **le)
{
struct ATTRIB *attr = NULL;
@@ -1562,7 +1569,7 @@ struct ATTR_FILE_NAME *ni_fname_name(struct ntfs_inode *ni,
/* Enumerate all names. */
next:
- attr = ni_find_attr(ni, attr, le, ATTR_NAME, NULL, 0, NULL, NULL);
+ attr = ni_find_attr(ni, attr, le, ATTR_NAME, NULL, 0, NULL, mi);
if (!attr)
return NULL;
@@ -1592,6 +1599,7 @@ next:
* Return: File name attribute with given type.
*/
struct ATTR_FILE_NAME *ni_fname_type(struct ntfs_inode *ni, u8 name_type,
+ struct mft_inode **mi,
struct ATTR_LIST_ENTRY **le)
{
struct ATTRIB *attr = NULL;
@@ -1599,10 +1607,12 @@ struct ATTR_FILE_NAME *ni_fname_type(struct ntfs_inode *ni, u8 name_type,
*le = NULL;
+ if (FILE_NAME_POSIX == name_type)
+ return NULL;
+
/* Enumerate all names. */
for (;;) {
- attr = ni_find_attr(ni, attr, le, ATTR_NAME, NULL, 0, NULL,
- NULL);
+ attr = ni_find_attr(ni, attr, le, ATTR_NAME, NULL, 0, NULL, mi);
if (!attr)
return NULL;
@@ -2789,6 +2799,222 @@ out:
}
/*
+ * ni_remove_name - Removes name 'de' from MFT and from directory.
+ * 'de2' and 'undo_step' are used to restore MFT/dir, if error occurs.
+ */
+int ni_remove_name(struct ntfs_inode *dir_ni, struct ntfs_inode *ni,
+ struct NTFS_DE *de, struct NTFS_DE **de2, int *undo_step)
+{
+ int err;
+ struct ntfs_sb_info *sbi = ni->mi.sbi;
+ struct ATTR_FILE_NAME *de_name = (struct ATTR_FILE_NAME *)(de + 1);
+ struct ATTR_FILE_NAME *fname;
+ struct ATTR_LIST_ENTRY *le;
+ struct mft_inode *mi;
+ u16 de_key_size = le16_to_cpu(de->key_size);
+ u8 name_type;
+
+ *undo_step = 0;
+
+ /* Find name in record. */
+ mi_get_ref(&dir_ni->mi, &de_name->home);
+
+ fname = ni_fname_name(ni, (struct cpu_str *)&de_name->name_len,
+ &de_name->home, &mi, &le);
+ if (!fname)
+ return -ENOENT;
+
+ memcpy(&de_name->dup, &fname->dup, sizeof(struct NTFS_DUP_INFO));
+ name_type = paired_name(fname->type);
+
+ /* Mark ntfs as dirty. It will be cleared at umount. */
+ ntfs_set_state(sbi, NTFS_DIRTY_DIRTY);
+
+ /* Step 1: Remove name from directory. */
+ err = indx_delete_entry(&dir_ni->dir, dir_ni, fname, de_key_size, sbi);
+ if (err)
+ return err;
+
+ /* Step 2: Remove name from MFT. */
+ ni_remove_attr_le(ni, attr_from_name(fname), mi, le);
+
+ *undo_step = 2;
+
+ /* Get paired name. */
+ fname = ni_fname_type(ni, name_type, &mi, &le);
+ if (fname) {
+ u16 de2_key_size = fname_full_size(fname);
+
+ *de2 = Add2Ptr(de, 1024);
+ (*de2)->key_size = cpu_to_le16(de2_key_size);
+
+ memcpy(*de2 + 1, fname, de2_key_size);
+
+ /* Step 3: Remove paired name from directory. */
+ err = indx_delete_entry(&dir_ni->dir, dir_ni, fname,
+ de2_key_size, sbi);
+ if (err)
+ return err;
+
+ /* Step 4: Remove paired name from MFT. */
+ ni_remove_attr_le(ni, attr_from_name(fname), mi, le);
+
+ *undo_step = 4;
+ }
+ return 0;
+}
+
+/*
+ * ni_remove_name_undo - Paired function for ni_remove_name.
+ *
+ * Return: True if ok
+ */
+bool ni_remove_name_undo(struct ntfs_inode *dir_ni, struct ntfs_inode *ni,
+ struct NTFS_DE *de, struct NTFS_DE *de2, int undo_step)
+{
+ struct ntfs_sb_info *sbi = ni->mi.sbi;
+ struct ATTRIB *attr;
+ u16 de_key_size = de2 ? le16_to_cpu(de2->key_size) : 0;
+
+ switch (undo_step) {
+ case 4:
+ if (ni_insert_resident(ni, de_key_size, ATTR_NAME, NULL, 0,
+ &attr, NULL, NULL)) {
+ return false;
+ }
+ memcpy(Add2Ptr(attr, SIZEOF_RESIDENT), de2 + 1, de_key_size);
+
+ mi_get_ref(&ni->mi, &de2->ref);
+ de2->size = cpu_to_le16(ALIGN(de_key_size, 8) +
+ sizeof(struct NTFS_DE));
+ de2->flags = 0;
+ de2->res = 0;
+
+ if (indx_insert_entry(&dir_ni->dir, dir_ni, de2, sbi, NULL,
+ 1)) {
+ return false;
+ }
+ fallthrough;
+
+ case 2:
+ de_key_size = le16_to_cpu(de->key_size);
+
+ if (ni_insert_resident(ni, de_key_size, ATTR_NAME, NULL, 0,
+ &attr, NULL, NULL)) {
+ return false;
+ }
+
+ memcpy(Add2Ptr(attr, SIZEOF_RESIDENT), de + 1, de_key_size);
+ mi_get_ref(&ni->mi, &de->ref);
+
+ if (indx_insert_entry(&dir_ni->dir, dir_ni, de, sbi, NULL, 1)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/*
+ * ni_add_name - Add new name in MFT and in directory.
+ */
+int ni_add_name(struct ntfs_inode *dir_ni, struct ntfs_inode *ni,
+ struct NTFS_DE *de)
+{
+ int err;
+ struct ATTRIB *attr;
+ struct ATTR_LIST_ENTRY *le;
+ struct mft_inode *mi;
+ struct ATTR_FILE_NAME *de_name = (struct ATTR_FILE_NAME *)(de + 1);
+ u16 de_key_size = le16_to_cpu(de->key_size);
+
+ mi_get_ref(&ni->mi, &de->ref);
+ mi_get_ref(&dir_ni->mi, &de_name->home);
+
+ /* Insert new name in MFT. */
+ err = ni_insert_resident(ni, de_key_size, ATTR_NAME, NULL, 0, &attr,
+ &mi, &le);
+ if (err)
+ return err;
+
+ memcpy(Add2Ptr(attr, SIZEOF_RESIDENT), de_name, de_key_size);
+
+ /* Insert new name in directory. */
+ err = indx_insert_entry(&dir_ni->dir, dir_ni, de, ni->mi.sbi, NULL, 0);
+ if (err)
+ ni_remove_attr_le(ni, attr, mi, le);
+
+ return err;
+}
+
+/*
+ * ni_rename - Remove one name and insert new name.
+ */
+int ni_rename(struct ntfs_inode *dir_ni, struct ntfs_inode *new_dir_ni,
+ struct ntfs_inode *ni, struct NTFS_DE *de, struct NTFS_DE *new_de,
+ bool *is_bad)
+{
+ int err;
+ struct NTFS_DE *de2 = NULL;
+ int undo = 0;
+
+ /*
+ * There are two possible ways to rename:
+ * 1) Add new name and remove old name.
+ * 2) Remove old name and add new name.
+ *
+ * In most cases (not all!) adding new name in MFT and in directory can
+ * allocate additional cluster(s).
+ * Second way may result to bad inode if we can't add new name
+ * and then can't restore (add) old name.
+ */
+
+ /*
+ * Way 1 - Add new + remove old.
+ */
+ err = ni_add_name(new_dir_ni, ni, new_de);
+ if (!err) {
+ err = ni_remove_name(dir_ni, ni, de, &de2, &undo);
+ if (err && ni_remove_name(new_dir_ni, ni, new_de, &de2, &undo))
+ *is_bad = true;
+ }
+
+ /*
+ * Way 2 - Remove old + add new.
+ */
+ /*
+ * err = ni_remove_name(dir_ni, ni, de, &de2, &undo);
+ * if (!err) {
+ * err = ni_add_name(new_dir_ni, ni, new_de);
+ * if (err && !ni_remove_name_undo(dir_ni, ni, de, de2, undo))
+ * *is_bad = true;
+ * }
+ */
+
+ return err;
+}
+
+/*
+ * ni_is_dirty - Return: True if 'ni' requires ni_write_inode.
+ */
+bool ni_is_dirty(struct inode *inode)
+{
+ struct ntfs_inode *ni = ntfs_i(inode);
+ struct rb_node *node;
+
+ if (ni->mi.dirty || ni->attr_list.dirty ||
+ (ni->ni_flags & NI_FLAG_UPDATE_PARENT))
+ return true;
+
+ for (node = rb_first(&ni->mi_tree); node; node = rb_next(node)) {
+ if (rb_entry(node, struct mft_inode, node)->dirty)
+ return true;
+ }
+
+ return false;
+}
+
+/*
* ni_update_parent
*
* Update duplicate info of ATTR_FILE_NAME in MFT and in parent directories.
@@ -2802,8 +3028,6 @@ static bool ni_update_parent(struct ntfs_inode *ni, struct NTFS_DUP_INFO *dup,
struct ntfs_sb_info *sbi = ni->mi.sbi;
struct super_block *sb = sbi->sb;
bool re_dirty = false;
- bool active = sb->s_flags & SB_ACTIVE;
- bool upd_parent = ni->ni_flags & NI_FLAG_UPDATE_PARENT;
if (ni->mi.mrec->flags & RECORD_FLAG_DIR) {
dup->fa |= FILE_ATTRIBUTE_DIRECTORY;
@@ -2867,19 +3091,9 @@ static bool ni_update_parent(struct ntfs_inode *ni, struct NTFS_DUP_INFO *dup,
struct ATTR_FILE_NAME *fname;
fname = resident_data_ex(attr, SIZEOF_ATTRIBUTE_FILENAME);
- if (!fname)
+ if (!fname || !memcmp(&fname->dup, dup, sizeof(fname->dup)))
continue;
- if (memcmp(&fname->dup, dup, sizeof(fname->dup))) {
- memcpy(&fname->dup, dup, sizeof(fname->dup));
- mi->dirty = true;
- } else if (!upd_parent) {
- continue;
- }
-
- if (!active)
- continue; /* Avoid __wait_on_freeing_inode(inode); */
-
/* ntfs_iget5 may sleep. */
dir = ntfs_iget5(sb, &fname->home, NULL);
if (IS_ERR(dir)) {
@@ -2898,6 +3112,8 @@ static bool ni_update_parent(struct ntfs_inode *ni, struct NTFS_DUP_INFO *dup,
} else {
indx_update_dup(dir_ni, sbi, fname, dup, sync);
ni_unlock(dir_ni);
+ memcpy(&fname->dup, dup, sizeof(fname->dup));
+ mi->dirty = true;
}
}
iput(dir);
@@ -2969,7 +3185,9 @@ int ni_write_inode(struct inode *inode, int sync, const char *hint)
ni->mi.dirty = true;
if (!ntfs_is_meta_file(sbi, inode->i_ino) &&
- (modified || (ni->ni_flags & NI_FLAG_UPDATE_PARENT))) {
+ (modified || (ni->ni_flags & NI_FLAG_UPDATE_PARENT))
+ /* Avoid __wait_on_freeing_inode(inode). */
+ && (sb->s_flags & SB_ACTIVE)) {
dup.cr_time = std->cr_time;
/* Not critical if this function fail. */
re_dirty = ni_update_parent(ni, &dup, sync);
@@ -3033,7 +3251,7 @@ out:
return err;
}
- if (re_dirty && (sb->s_flags & SB_ACTIVE))
+ if (re_dirty)
mark_inode_dirty_sync(inode);
return 0;