summaryrefslogtreecommitdiff
path: root/fs/xfs/scrub/symlink_repair.c
blob: c2040d931a2fa85c58a59190c6d182e17e7f6328 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (C) 2020 Oracle.  All Rights Reserved.
 * Author: Darrick J. Wong <darrick.wong@oracle.com>
 */
#include "xfs.h"
#include "xfs_fs.h"
#include "xfs_shared.h"
#include "xfs_format.h"
#include "xfs_trans_resv.h"
#include "xfs_mount.h"
#include "xfs_defer.h"
#include "xfs_btree.h"
#include "xfs_bit.h"
#include "xfs_log_format.h"
#include "xfs_trans.h"
#include "xfs_sb.h"
#include "xfs_inode.h"
#include "xfs_inode_fork.h"
#include "xfs_symlink.h"
#include "xfs_bmap.h"
#include "xfs_quota.h"
#include "xfs_da_format.h"
#include "xfs_da_btree.h"
#include "xfs_bmap_btree.h"
#include "xfs_trans_space.h"
#include "scrub/xfs_scrub.h"
#include "scrub/scrub.h"
#include "scrub/common.h"
#include "scrub/trace.h"
#include "scrub/repair.h"

/*
 * Symbolic Link Repair
 * ====================
 *
 * There's not much we can do to repair symbolic links -- we truncate them to
 * the first NULL byte and reinitialize the target.  Zero-length symlinks are
 * turned into links to the current dir.
 */

/* Try to salvage the pathname from rmt blocks. */
STATIC int
xrep_symlink_salvage_remote(
	struct xfs_scrub	*sc)
{
	struct xfs_bmbt_irec	mval[XFS_SYMLINK_MAPS];
	struct xfs_inode	*ip = sc->ip;
	struct xfs_buf		*bp;
	char			*target_buf = sc->buf;
	xfs_failaddr_t		fa;
	xfs_filblks_t		fsblocks;
	xfs_daddr_t		d;
	loff_t			len;
	loff_t			offset;
	unsigned int		byte_cnt;
	bool			magic_ok;
	bool			hdr_ok;
	int			n;
	int			nmaps = XFS_SYMLINK_MAPS;
	int			error;

	/* We'll only read until the buffer is full. */
	len = max_t(loff_t, ip->i_d.di_size, XFS_SYMLINK_MAXLEN);
	fsblocks = xfs_symlink_blocks(sc->mp, len);
	error = xfs_bmapi_read(ip, 0, fsblocks, mval, &nmaps, 0);
	if (error)
		return error;

	offset = 0;
	for (n = 0; n < nmaps; n++) {
		struct xfs_dsymlink_hdr	*dsl;

		d = XFS_FSB_TO_DADDR(sc->mp, mval[n].br_startblock);

		/* Read the rmt block.  We'll run the verifiers manually. */
		error = xfs_trans_read_buf(sc->mp, sc->tp, sc->mp->m_ddev_targp,
				d, XFS_FSB_TO_BB(sc->mp, mval[n].br_blockcount),
				0, &bp, NULL);
		if (error)
			return error;
		bp->b_ops = &xfs_symlink_buf_ops;

		/* How many bytes do we expect to get out of this buffer? */
		byte_cnt = XFS_FSB_TO_B(sc->mp, mval[n].br_blockcount);
		byte_cnt = XFS_SYMLINK_BUF_SPACE(sc->mp, byte_cnt);
		byte_cnt = min_t(unsigned int, byte_cnt, len);

		/*
		 * See if the verifiers accept this block.  We're willing to
		 * salvage if the if the offset/byte/ino are ok and either the
		 * verifier passed or the magic is ok.  Anything else and we
		 * stop dead in our tracks.
		 */
		fa = bp->b_ops->verify_struct(bp);
		dsl = bp->b_addr;
		magic_ok = dsl->sl_magic == cpu_to_be32(XFS_SYMLINK_MAGIC);
		hdr_ok = xfs_symlink_hdr_ok(ip->i_ino, offset, byte_cnt, bp);
		if (!hdr_ok || (fa != NULL && !magic_ok))
			break;

		memcpy(target_buf + offset, dsl + 1, byte_cnt);

		len -= byte_cnt;
		offset += byte_cnt;
	}

	/* Ensure we have a zero at the end, and /some/ contents. */
	if (offset == 0)
		sprintf(target_buf, ".");
	else
		target_buf[offset] = 0;
	return 0;
}

/*
 * Try to salvage an inline symlink's contents.  Empty symlinks become a link
 * to the current directory.
 */
STATIC void
xrep_symlink_salvage_inline(
	struct xfs_scrub	*sc)
{
	struct xfs_inode	*ip = sc->ip;
	struct xfs_ifork	*ifp;

	ifp = XFS_IFORK_PTR(ip, XFS_DATA_FORK);
	if (ifp->if_u1.if_data)
		strncpy(sc->buf, ifp->if_u1.if_data, XFS_IFORK_DSIZE(ip));
	if (strlen(sc->buf) == 0)
		sprintf(sc->buf, ".");
}

/* Reset an inline symlink to its fresh configuration. */
STATIC void
xrep_symlink_truncate_inline(
	struct xfs_inode	*ip)
{
	xfs_idestroy_fork(ip, XFS_DATA_FORK);
	ip->i_d.di_format = XFS_DINODE_FMT_EXTENTS;
	ip->i_d.di_nextents = 0;
	memset(&ip->i_df, 0, sizeof(struct xfs_ifork));
	ip->i_df.if_flags |= XFS_IFEXTENTS;
}

/*
 * Salvage an inline symlink's contents and reset data fork.
 * Returns with the inode joined to the transaction.
 */
STATIC int
xrep_symlink_inline(
	struct xfs_scrub	*sc)
{
	/* Salvage whatever link target information we can find. */
	xrep_symlink_salvage_inline(sc);

	/* Truncate the symlink. */
	xrep_symlink_truncate_inline(sc->ip);

	xfs_trans_ijoin(sc->tp, sc->ip, 0);
	return 0;
}

/*
 * Salvage an inline symlink's contents and reset data fork.
 * Returns with the inode joined to the transaction.
 */
STATIC int
xrep_symlink_remote(
	struct xfs_scrub	*sc)
{
	int			error;

	/* Salvage whatever link target information we can find. */
	error = xrep_symlink_salvage_remote(sc);
	if (error)
		return error;

	/* Truncate the symlink. */
	xfs_trans_ijoin(sc->tp, sc->ip, 0);
	return xfs_itruncate_extents(&sc->tp, sc->ip, XFS_DATA_FORK, 0);
}

/*
 * Reinitialize a link target.  Caller must ensure the inode is joined to
 * the transaction.
 */
STATIC int
xrep_symlink_reinitialize(
	struct xfs_scrub	*sc)
{
	xfs_fsblock_t		fs_blocks;
	unsigned int		target_len;
	uint			resblks;
	int			error;

	/* How many blocks do we need? */
	target_len = strlen(sc->buf);
	ASSERT(target_len != 0);
	if (target_len == 0 || target_len > XFS_SYMLINK_MAXLEN)
		return -EFSCORRUPTED;

	/* Set up to reinitialize the target. */
	fs_blocks = xfs_symlink_blocks(sc->mp, target_len);
	resblks = XFS_SYMLINK_SPACE_RES(sc->mp, target_len, fs_blocks);
	error = xfs_trans_reserve_quota_nblks(sc->tp, sc->ip, resblks, 0,
			XFS_QMOPT_RES_REGBLKS);

	/* Try to write the new target back out. */
	error = xfs_symlink_write_target(sc->tp, sc->ip, sc->buf, target_len,
			fs_blocks, resblks);
	if (error)
		return error;

	/* Finish up any block mapping activities. */
	return xfs_defer_finish(&sc->tp);
}

/* Repair a symbolic link. */
int
xrep_symlink(
	struct xfs_scrub	*sc)
{
	struct xfs_ifork	*ifp;
	int			error;

	error = xfs_qm_dqattach_locked(sc->ip, false);
	if (error)
		return error;

	/* Salvage whatever we can of the target. */
	*((char *)sc->buf) = 0;
	ifp = XFS_IFORK_PTR(sc->ip, XFS_DATA_FORK);
	if (ifp->if_flags & XFS_IFINLINE)
		error = xrep_symlink_inline(sc);
	else
		error = xrep_symlink_remote(sc);
	if (error)
		return error;

	/* Now reset the target. */
	return xrep_symlink_reinitialize(sc);
}