summaryrefslogtreecommitdiff
path: root/fs/iomap/swapfile.c
blob: 5fc0ac36dee3d1949c5b72f7d2a4a5b97c712c18 (plain) (blame)
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
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2018 Oracle.  All Rights Reserved.
 * Author: Darrick J. Wong <darrick.wong@oracle.com>
 */
#include <linux/module.h>
#include <linux/compiler.h>
#include <linux/fs.h>
#include <linux/iomap.h>
#include <linux/swap.h>

/* Swapfile activation */

struct iomap_swapfile_info {
	struct iomap iomap;		/* accumulated iomap */
	struct swap_info_struct *sis;
	uint64_t lowest_ppage;		/* lowest physical addr seen (pages) */
	uint64_t highest_ppage;		/* highest physical addr seen (pages) */
	unsigned long nr_pages;		/* number of pages collected */
	int nr_extents;			/* extent count */
	struct file *file;
};

/*
 * Collect physical extents for this swap file.  Physical extents reported to
 * the swap code must be trimmed to align to a page boundary.  The logical
 * offset within the file is irrelevant since the swapfile code maps logical
 * page numbers of the swap device to the physical page-aligned extents.
 */
static int iomap_swapfile_add_extent(struct iomap_swapfile_info *isi)
{
	struct iomap *iomap = &isi->iomap;
	unsigned long nr_pages;
	unsigned long max_pages;
	uint64_t first_ppage;
	uint64_t first_ppage_reported;
	uint64_t next_ppage;
	int error;

	if (unlikely(isi->nr_pages >= isi->sis->max))
		return 0;
	max_pages = isi->sis->max - isi->nr_pages;

	/*
	 * Round the start up and the end down so that the physical
	 * extent aligns to a page boundary.
	 */
	first_ppage = ALIGN(iomap->addr, PAGE_SIZE) >> PAGE_SHIFT;
	next_ppage = ALIGN_DOWN(iomap->addr + iomap->length, PAGE_SIZE) >>
			PAGE_SHIFT;

	/* Skip too-short physical extents. */
	if (first_ppage >= next_ppage)
		return 0;
	nr_pages = next_ppage - first_ppage;
	nr_pages = min(nr_pages, max_pages);

	/*
	 * Calculate how much swap space we're adding; the first page contains
	 * the swap header and doesn't count.  The mm still wants that first
	 * page fed to add_swap_extent, however.
	 */
	first_ppage_reported = first_ppage;
	if (iomap->offset == 0)
		first_ppage_reported++;
	if (isi->lowest_ppage > first_ppage_reported)
		isi->lowest_ppage = first_ppage_reported;
	if (isi->highest_ppage < (next_ppage - 1))
		isi->highest_ppage = next_ppage - 1;

	/* Add extent, set up for the next call. */
	error = add_swap_extent(isi->sis, isi->nr_pages, nr_pages, first_ppage);
	if (error < 0)
		return error;
	isi->nr_extents += error;
	isi->nr_pages += nr_pages;
	return 0;
}

static int iomap_swapfile_fail(struct iomap_swapfile_info *isi, const char *str)
{
	char *buf, *p = ERR_PTR(-ENOMEM);

	buf = kmalloc(PATH_MAX, GFP_KERNEL);
	if (buf)
		p = file_path(isi->file, buf, PATH_MAX);
	pr_err("swapon: file %s %s\n", IS_ERR(p) ? "<unknown>" : p, str);
	kfree(buf);
	return -EINVAL;
}

/*
 * Accumulate iomaps for this swap file.  We have to accumulate iomaps because
 * swap only cares about contiguous page-aligned physical extents and makes no
 * distinction between written and unwritten extents.
 */
static loff_t iomap_swapfile_iter(const struct iomap_iter *iter,
		struct iomap *iomap, struct iomap_swapfile_info *isi)
{
	switch (iomap->type) {
	case IOMAP_MAPPED:
	case IOMAP_UNWRITTEN:
		/* Only real or unwritten extents. */
		break;
	case IOMAP_INLINE:
		/* No inline data. */
		return iomap_swapfile_fail(isi, "is inline");
	default:
		return iomap_swapfile_fail(isi, "has unallocated extents");
	}

	/* No uncommitted metadata or shared blocks. */
	if (iomap->flags & IOMAP_F_DIRTY)
		return iomap_swapfile_fail(isi, "is not committed");
	if (iomap->flags & IOMAP_F_SHARED)
		return iomap_swapfile_fail(isi, "has shared extents");

	/* Only one bdev per swap file. */
	if (iomap->bdev != isi->sis->bdev)
		return iomap_swapfile_fail(isi, "outside the main device");

	if (isi->iomap.length == 0) {
		/* No accumulated extent, so just store it. */
		memcpy(&isi->iomap, iomap, sizeof(isi->iomap));
	} else if (isi->iomap.addr + isi->iomap.length == iomap->addr) {
		/* Append this to the accumulated extent. */
		isi->iomap.length += iomap->length;
	} else {
		/* Otherwise, add the retained iomap and store this one. */
		int error = iomap_swapfile_add_extent(isi);
		if (error)
			return error;
		memcpy(&isi->iomap, iomap, sizeof(isi->iomap));
	}
	return iomap_length(iter);
}

/*
 * Iterate a swap file's iomaps to construct physical extents that can be
 * passed to the swapfile subsystem.
 */
int iomap_swapfile_activate(struct swap_info_struct *sis,
		struct file *swap_file, sector_t *pagespan,
		const struct iomap_ops *ops)
{
	struct inode *inode = swap_file->f_mapping->host;
	struct iomap_iter iter = {
		.inode	= inode,
		.pos	= 0,
		.len	= ALIGN_DOWN(i_size_read(inode), PAGE_SIZE),
		.flags	= IOMAP_REPORT,
	};
	struct iomap_swapfile_info isi = {
		.sis = sis,
		.lowest_ppage = (sector_t)-1ULL,
		.file = swap_file,
	};
	int ret;

	/*
	 * Persist all file mapping metadata so that we won't have any
	 * IOMAP_F_DIRTY iomaps.
	 */
	ret = vfs_fsync(swap_file, 1);
	if (ret)
		return ret;

	while ((ret = iomap_iter(&iter, ops)) > 0)
		iter.processed = iomap_swapfile_iter(&iter, &iter.iomap, &isi);
	if (ret < 0)
		return ret;

	if (isi.iomap.length) {
		ret = iomap_swapfile_add_extent(&isi);
		if (ret)
			return ret;
	}

	/*
	 * If this swapfile doesn't contain even a single page-aligned
	 * contiguous range of blocks, reject this useless swapfile to
	 * prevent confusion later on.
	 */
	if (isi.nr_pages == 0) {
		pr_warn("swapon: Cannot find a single usable page in file.\n");
		return -EINVAL;
	}

	*pagespan = 1 + isi.highest_ppage - isi.lowest_ppage;
	sis->max = isi.nr_pages;
	sis->pages = isi.nr_pages - 1;
	sis->highest_bit = isi.nr_pages - 1;
	return isi.nr_extents;
}
EXPORT_SYMBOL_GPL(iomap_swapfile_activate);