Commit | Line | Data |
---|---|---|
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 | */ | |
31 | int | |
32 | xrep_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 | ||
143 | out_trans_cancel: | |
144 | xfs_trans_cancel(tp); | |
145 | out_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 | } | |
155 | out_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. */ | |
164 | bool | |
165 | xrep_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 | */ | |
181 | int | |
182 | xrep_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. */ | |
197 | void | |
198 | xrep_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. */ | |
206 | void | |
207 | xrep_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. */ | |
215 | bool | |
216 | xrep_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. */ | |
228 | void | |
229 | xrep_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. */ | |
237 | void | |
238 | xrep_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 | } |