xfs: create temporary files and directories for online repair
[linux-2.6-block.git] / fs / xfs / scrub / tempfile.c
CommitLineData
84c14ee3
DW
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Copyright (c) 2021-2024 Oracle. All Rights Reserved.
4 * Author: Darrick J. Wong <djwong@kernel.org>
5 */
6#include "xfs.h"
7#include "xfs_fs.h"
8#include "xfs_shared.h"
9#include "xfs_format.h"
10#include "xfs_trans_resv.h"
11#include "xfs_mount.h"
12#include "xfs_log_format.h"
13#include "xfs_trans.h"
14#include "xfs_inode.h"
15#include "xfs_ialloc.h"
16#include "xfs_quota.h"
17#include "xfs_bmap_btree.h"
18#include "xfs_trans_space.h"
19#include "xfs_dir2.h"
20#include "xfs_exchrange.h"
21#include "scrub/scrub.h"
22#include "scrub/common.h"
23#include "scrub/trace.h"
24#include "scrub/tempfile.h"
25
26/*
27 * Create a temporary file for reconstructing metadata, with the intention of
28 * atomically exchanging the temporary file's contents with the file that's
29 * being repaired.
30 */
31int
32xrep_tempfile_create(
33 struct xfs_scrub *sc,
34 uint16_t mode)
35{
36 struct xfs_mount *mp = sc->mp;
37 struct xfs_trans *tp = NULL;
38 struct xfs_dquot *udqp = NULL;
39 struct xfs_dquot *gdqp = NULL;
40 struct xfs_dquot *pdqp = NULL;
41 struct xfs_trans_res *tres;
42 struct xfs_inode *dp = mp->m_rootip;
43 xfs_ino_t ino;
44 unsigned int resblks;
45 bool is_dir = S_ISDIR(mode);
46 int error;
47
48 if (xfs_is_shutdown(mp))
49 return -EIO;
50 if (xfs_is_readonly(mp))
51 return -EROFS;
52
53 ASSERT(sc->tp == NULL);
54 ASSERT(sc->tempip == NULL);
55
56 /*
57 * Make sure that we have allocated dquot(s) on disk. The temporary
58 * inode should be completely root owned so that we don't fail due to
59 * quota limits.
60 */
61 error = xfs_qm_vop_dqalloc(dp, GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, 0,
62 XFS_QMOPT_QUOTALL, &udqp, &gdqp, &pdqp);
63 if (error)
64 return error;
65
66 if (is_dir) {
67 resblks = XFS_MKDIR_SPACE_RES(mp, 0);
68 tres = &M_RES(mp)->tr_mkdir;
69 } else {
70 resblks = XFS_IALLOC_SPACE_RES(mp);
71 tres = &M_RES(mp)->tr_create_tmpfile;
72 }
73
74 error = xfs_trans_alloc_icreate(mp, tres, udqp, gdqp, pdqp, resblks,
75 &tp);
76 if (error)
77 goto out_release_dquots;
78
79 /* Allocate inode, set up directory. */
80 error = xfs_dialloc(&tp, dp->i_ino, mode, &ino);
81 if (error)
82 goto out_trans_cancel;
83 error = xfs_init_new_inode(&nop_mnt_idmap, tp, dp, ino, mode, 0, 0,
84 0, false, &sc->tempip);
85 if (error)
86 goto out_trans_cancel;
87
88 /* Change the ownership of the inode to root. */
89 VFS_I(sc->tempip)->i_uid = GLOBAL_ROOT_UID;
90 VFS_I(sc->tempip)->i_gid = GLOBAL_ROOT_GID;
91 sc->tempip->i_diflags &= ~(XFS_DIFLAG_REALTIME | XFS_DIFLAG_RTINHERIT);
92 xfs_trans_log_inode(tp, sc->tempip, XFS_ILOG_CORE);
93
94 /*
95 * Mark our temporary file as private so that LSMs and the ACL code
96 * don't try to add their own metadata or reason about these files.
97 * The file should never be exposed to userspace.
98 */
99 VFS_I(sc->tempip)->i_flags |= S_PRIVATE;
100 VFS_I(sc->tempip)->i_opflags &= ~IOP_XATTR;
101
102 if (is_dir) {
103 error = xfs_dir_init(tp, sc->tempip, dp);
104 if (error)
105 goto out_trans_cancel;
106 }
107
108 /*
109 * Attach the dquot(s) to the inodes and modify them incore.
110 * These ids of the inode couldn't have changed since the new
111 * inode has been locked ever since it was created.
112 */
113 xfs_qm_vop_create_dqattach(tp, sc->tempip, udqp, gdqp, pdqp);
114
115 /*
116 * Put our temp file on the unlinked list so it's purged automatically.
117 * All file-based metadata being reconstructed using this file must be
118 * atomically exchanged with the original file because the contents
119 * here will be purged when the inode is dropped or log recovery cleans
120 * out the unlinked list.
121 */
122 error = xfs_iunlink(tp, sc->tempip);
123 if (error)
124 goto out_trans_cancel;
125
126 error = xfs_trans_commit(tp);
127 if (error)
128 goto out_release_inode;
129
130 trace_xrep_tempfile_create(sc);
131
132 xfs_qm_dqrele(udqp);
133 xfs_qm_dqrele(gdqp);
134 xfs_qm_dqrele(pdqp);
135
136 /* Finish setting up the incore / vfs context. */
137 xfs_setup_iops(sc->tempip);
138 xfs_finish_inode_setup(sc->tempip);
139
140 sc->temp_ilock_flags = 0;
141 return error;
142
143out_trans_cancel:
144 xfs_trans_cancel(tp);
145out_release_inode:
146 /*
147 * Wait until after the current transaction is aborted to finish the
148 * setup of the inode and release the inode. This prevents recursive
149 * transactions and deadlocks from xfs_inactive.
150 */
151 if (sc->tempip) {
152 xfs_finish_inode_setup(sc->tempip);
153 xchk_irele(sc, sc->tempip);
154 }
155out_release_dquots:
156 xfs_qm_dqrele(udqp);
157 xfs_qm_dqrele(gdqp);
158 xfs_qm_dqrele(pdqp);
159
160 return error;
161}
162
163/* Take IOLOCK_EXCL on the temporary file, maybe. */
164bool
165xrep_tempfile_iolock_nowait(
166 struct xfs_scrub *sc)
167{
168 if (xfs_ilock_nowait(sc->tempip, XFS_IOLOCK_EXCL)) {
169 sc->temp_ilock_flags |= XFS_IOLOCK_EXCL;
170 return true;
171 }
172
173 return false;
174}
175
176/*
177 * Take the temporary file's IOLOCK while holding a different inode's IOLOCK.
178 * In theory nobody else should hold the tempfile's IOLOCK, but we use trylock
179 * to avoid deadlocks and lockdep complaints.
180 */
181int
182xrep_tempfile_iolock_polled(
183 struct xfs_scrub *sc)
184{
185 int error = 0;
186
187 while (!xrep_tempfile_iolock_nowait(sc)) {
188 if (xchk_should_terminate(sc, &error))
189 return error;
190 delay(1);
191 }
192
193 return 0;
194}
195
196/* Release IOLOCK_EXCL on the temporary file. */
197void
198xrep_tempfile_iounlock(
199 struct xfs_scrub *sc)
200{
201 xfs_iunlock(sc->tempip, XFS_IOLOCK_EXCL);
202 sc->temp_ilock_flags &= ~XFS_IOLOCK_EXCL;
203}
204
205/* Prepare the temporary file for metadata updates by grabbing ILOCK_EXCL. */
206void
207xrep_tempfile_ilock(
208 struct xfs_scrub *sc)
209{
210 sc->temp_ilock_flags |= XFS_ILOCK_EXCL;
211 xfs_ilock(sc->tempip, XFS_ILOCK_EXCL);
212}
213
214/* Try to grab ILOCK_EXCL on the temporary file. */
215bool
216xrep_tempfile_ilock_nowait(
217 struct xfs_scrub *sc)
218{
219 if (xfs_ilock_nowait(sc->tempip, XFS_ILOCK_EXCL)) {
220 sc->temp_ilock_flags |= XFS_ILOCK_EXCL;
221 return true;
222 }
223
224 return false;
225}
226
227/* Unlock ILOCK_EXCL on the temporary file after an update. */
228void
229xrep_tempfile_iunlock(
230 struct xfs_scrub *sc)
231{
232 xfs_iunlock(sc->tempip, XFS_ILOCK_EXCL);
233 sc->temp_ilock_flags &= ~XFS_ILOCK_EXCL;
234}
235
236/* Release the temporary file. */
237void
238xrep_tempfile_rele(
239 struct xfs_scrub *sc)
240{
241 if (!sc->tempip)
242 return;
243
244 if (sc->temp_ilock_flags) {
245 xfs_iunlock(sc->tempip, sc->temp_ilock_flags);
246 sc->temp_ilock_flags = 0;
247 }
248
249 xchk_irele(sc, sc->tempip);
250 sc->tempip = NULL;
251}