Commit | Line | Data |
---|---|---|
6fa3eb70 S |
1 | /* |
2 | * kernel/power/tuxonice_file.c | |
3 | * | |
4 | * Copyright (C) 2005-2010 Nigel Cunningham (nigel at tuxonice net) | |
5 | * | |
6 | * Distributed under GPLv2. | |
7 | * | |
8 | * This file encapsulates functions for usage of a simple file as a | |
9 | * backing store. It is based upon the swapallocator, and shares the | |
10 | * same basic working. Here, though, we have nothing to do with | |
11 | * swapspace, and only one device to worry about. | |
12 | * | |
13 | * The user can just | |
14 | * | |
15 | * echo TuxOnIce > /path/to/my_file | |
16 | * | |
17 | * dd if=/dev/zero bs=1M count=<file_size_desired> >> /path/to/my_file | |
18 | * | |
19 | * and | |
20 | * | |
21 | * echo /path/to/my_file > /sys/power/tuxonice/file/target | |
22 | * | |
23 | * then put what they find in /sys/power/tuxonice/resume | |
24 | * as their resume= parameter in lilo.conf (and rerun lilo if using it). | |
25 | * | |
26 | * Having done this, they're ready to hibernate and resume. | |
27 | * | |
28 | * TODO: | |
29 | * - File resizing. | |
30 | */ | |
31 | ||
32 | #include <linux/blkdev.h> | |
33 | #include <linux/mount.h> | |
34 | #include <linux/fs.h> | |
35 | #include <linux/fs_uuid.h> | |
36 | ||
37 | #include "tuxonice.h" | |
38 | #include "tuxonice_modules.h" | |
39 | #include "tuxonice_bio.h" | |
40 | #include "tuxonice_alloc.h" | |
41 | #include "tuxonice_builtin.h" | |
42 | #include "tuxonice_sysfs.h" | |
43 | #include "tuxonice_ui.h" | |
44 | #include "tuxonice_io.h" | |
45 | ||
46 | #define target_is_normal_file() (S_ISREG(target_inode->i_mode)) | |
47 | ||
48 | static struct toi_module_ops toi_fileops; | |
49 | ||
50 | static struct file *target_file; | |
51 | static struct block_device *toi_file_target_bdev; | |
52 | static unsigned long pages_available, pages_allocated; | |
53 | static char toi_file_target[256]; | |
54 | static struct inode *target_inode; | |
55 | static int file_target_priority; | |
56 | static int used_devt; | |
57 | static int target_claim; | |
58 | static dev_t toi_file_dev_t; | |
59 | static int sig_page_index; | |
60 | ||
61 | /* For test_toi_file_target */ | |
62 | static struct toi_bdev_info *file_chain; | |
63 | ||
64 | static int has_contiguous_blocks(struct toi_bdev_info *dev_info, int page_num) | |
65 | { | |
66 | int j; | |
67 | sector_t last = 0; | |
68 | ||
69 | for (j = 0; j < dev_info->blocks_per_page; j++) { | |
70 | sector_t this = bmap(target_inode, | |
71 | page_num * dev_info->blocks_per_page + j); | |
72 | ||
73 | if (!this || (last && (last + 1) != this)) | |
74 | break; | |
75 | ||
76 | last = this; | |
77 | } | |
78 | ||
79 | return j == dev_info->blocks_per_page; | |
80 | } | |
81 | ||
82 | static unsigned long get_usable_pages(struct toi_bdev_info *dev_info) | |
83 | { | |
84 | unsigned long result = 0; | |
85 | struct block_device *bdev = dev_info->bdev; | |
86 | int i; | |
87 | ||
88 | switch (target_inode->i_mode & S_IFMT) { | |
89 | case S_IFSOCK: | |
90 | case S_IFCHR: | |
91 | case S_IFIFO: /* Socket, Char, Fifo */ | |
92 | return -1; | |
93 | case S_IFREG: /* Regular file: current size - holes + free | |
94 | space on part */ | |
95 | for (i = 0; i < (target_inode->i_size >> PAGE_SHIFT); i++) { | |
96 | if (has_contiguous_blocks(dev_info, i)) | |
97 | result++; | |
98 | } | |
99 | break; | |
100 | case S_IFBLK: /* Block device */ | |
101 | if (!bdev->bd_disk) { | |
102 | toi_message(TOI_IO, TOI_VERBOSE, 0, "bdev->bd_disk null."); | |
103 | return 0; | |
104 | } | |
105 | ||
106 | result = (bdev->bd_part ? | |
107 | bdev->bd_part->nr_sects : | |
108 | get_capacity(bdev->bd_disk)) >> (PAGE_SHIFT - 9); | |
109 | } | |
110 | ||
111 | ||
112 | return result; | |
113 | } | |
114 | ||
115 | static int toi_file_register_storage(void) | |
116 | { | |
117 | struct toi_bdev_info *devinfo; | |
118 | int result = 0; | |
119 | struct fs_info *fs_info; | |
120 | ||
121 | toi_message(TOI_IO, TOI_VERBOSE, 0, "toi_file_register_storage."); | |
122 | if (!strlen(toi_file_target)) { | |
123 | toi_message(TOI_IO, TOI_VERBOSE, 0, "Register file storage: " | |
124 | "No target filename set."); | |
125 | return 0; | |
126 | } | |
127 | ||
128 | target_file = filp_open(toi_file_target, O_RDONLY | O_LARGEFILE, 0); | |
129 | toi_message(TOI_IO, TOI_VERBOSE, 0, "filp_open %s returned %p.", | |
130 | toi_file_target, target_file); | |
131 | ||
132 | if (IS_ERR(target_file) || !target_file) { | |
133 | target_file = NULL; | |
134 | toi_file_dev_t = name_to_dev_t(toi_file_target); | |
135 | if (!toi_file_dev_t) { | |
136 | struct kstat stat; | |
137 | int error = vfs_stat(toi_file_target, &stat); | |
138 | printk(KERN_INFO "Open file %s returned %p and " | |
139 | "name_to_devt failed.\n", toi_file_target, target_file); | |
140 | if (error) { | |
141 | printk(KERN_INFO "Stating the file also failed." | |
142 | " Nothing more we can do.\n"); | |
143 | return 0; | |
144 | } else | |
145 | toi_file_dev_t = stat.rdev; | |
146 | } | |
147 | ||
148 | toi_file_target_bdev = toi_open_by_devnum(toi_file_dev_t); | |
149 | if (IS_ERR(toi_file_target_bdev)) { | |
150 | printk(KERN_INFO "Got a dev_num (%lx) but failed to " | |
151 | "open it.\n", (unsigned long)toi_file_dev_t); | |
152 | toi_file_target_bdev = NULL; | |
153 | return 0; | |
154 | } | |
155 | used_devt = 1; | |
156 | target_inode = toi_file_target_bdev->bd_inode; | |
157 | } else | |
158 | target_inode = target_file->f_mapping->host; | |
159 | ||
160 | toi_message(TOI_IO, TOI_VERBOSE, 0, "Succeeded in opening the target."); | |
161 | if (S_ISLNK(target_inode->i_mode) || S_ISDIR(target_inode->i_mode) || | |
162 | S_ISSOCK(target_inode->i_mode) || S_ISFIFO(target_inode->i_mode)) { | |
163 | printk(KERN_INFO "File support works with regular files," | |
164 | " character files and block devices.\n"); | |
165 | /* Cleanup routine will undo the above */ | |
166 | return 0; | |
167 | } | |
168 | ||
169 | if (!used_devt) { | |
170 | if (S_ISBLK(target_inode->i_mode)) { | |
171 | toi_file_target_bdev = I_BDEV(target_inode); | |
172 | if (!blkdev_get(toi_file_target_bdev, FMODE_WRITE | FMODE_READ, NULL)) | |
173 | target_claim = 1; | |
174 | } else | |
175 | toi_file_target_bdev = target_inode->i_sb->s_bdev; | |
176 | if (!toi_file_target_bdev) { | |
177 | printk(KERN_INFO "%s is not a valid file allocator " | |
178 | "target.\n", toi_file_target); | |
179 | return 0; | |
180 | } | |
181 | toi_file_dev_t = toi_file_target_bdev->bd_dev; | |
182 | } | |
183 | ||
184 | devinfo = toi_kzalloc(39, sizeof(struct toi_bdev_info), GFP_ATOMIC); | |
185 | if (!devinfo) { | |
186 | printk("Failed to allocate a toi_bdev_info struct for the file allocator.\n"); | |
187 | return -ENOMEM; | |
188 | } | |
189 | ||
190 | devinfo->bdev = toi_file_target_bdev; | |
191 | devinfo->allocator = &toi_fileops; | |
192 | devinfo->allocator_index = 0; | |
193 | ||
194 | fs_info = fs_info_from_block_dev(toi_file_target_bdev); | |
195 | if (fs_info && !IS_ERR(fs_info)) { | |
196 | memcpy(devinfo->uuid, &fs_info->uuid, 16); | |
197 | free_fs_info(fs_info); | |
198 | } else | |
199 | result = (int)PTR_ERR(fs_info); | |
200 | ||
201 | /* Unlike swap code, only complain if fs_info_from_block_dev returned | |
202 | * -ENOMEM. The 'file' might be a full partition, so might validly not | |
203 | * have an identifiable type, UUID etc. | |
204 | */ | |
205 | if (result) | |
206 | printk(KERN_DEBUG "Failed to get fs_info for file device (%d).\n", result); | |
207 | devinfo->dev_t = toi_file_dev_t; | |
208 | devinfo->prio = file_target_priority; | |
209 | devinfo->bmap_shift = target_inode->i_blkbits - 9; | |
210 | devinfo->blocks_per_page = (1 << (PAGE_SHIFT - target_inode->i_blkbits)); | |
211 | sprintf(devinfo->name, "file %s", toi_file_target); | |
212 | file_chain = devinfo; | |
213 | toi_message(TOI_IO, TOI_VERBOSE, 0, "Dev_t is %lx. Prio is %d. Bmap " | |
214 | "shift is %d. Blocks per page %d.", | |
215 | devinfo->dev_t, devinfo->prio, devinfo->bmap_shift, devinfo->blocks_per_page); | |
216 | ||
217 | /* Keep one aside for the signature */ | |
218 | pages_available = get_usable_pages(devinfo) - 1; | |
219 | ||
220 | toi_message(TOI_IO, TOI_VERBOSE, 0, "Registering file storage, %lu " | |
221 | "pages.", pages_available); | |
222 | ||
223 | toi_bio_ops.register_storage(devinfo); | |
224 | return 0; | |
225 | } | |
226 | ||
227 | static unsigned long toi_file_storage_available(void) | |
228 | { | |
229 | return pages_available; | |
230 | } | |
231 | ||
232 | static int toi_file_allocate_storage(struct toi_bdev_info *chain, unsigned long request) | |
233 | { | |
234 | unsigned long available = pages_available - pages_allocated; | |
235 | unsigned long to_add = min(available, request); | |
236 | ||
237 | toi_message(TOI_IO, TOI_VERBOSE, 0, "Pages available is %lu. Allocated " | |
238 | "is %lu. Allocating %lu pages from file.", | |
239 | pages_available, pages_allocated, to_add); | |
240 | pages_allocated += to_add; | |
241 | ||
242 | return to_add; | |
243 | } | |
244 | ||
245 | /** | |
246 | * __populate_block_list - add an extent to the chain | |
247 | * @min: Start of the extent (first physical block = sector) | |
248 | * @max: End of the extent (last physical block = sector) | |
249 | * | |
250 | * If TOI_TEST_BIO is set, print a debug message, outputting the min and max | |
251 | * fs block numbers. | |
252 | **/ | |
253 | static int __populate_block_list(struct toi_bdev_info *chain, int min, int max) | |
254 | { | |
255 | if (test_action_state(TOI_TEST_BIO)) | |
256 | toi_message(TOI_IO, TOI_VERBOSE, 0, "Adding extent %d-%d.", | |
257 | min << chain->bmap_shift, ((max + 1) << chain->bmap_shift) - 1); | |
258 | ||
259 | return toi_add_to_extent_chain(&chain->blocks, min, max); | |
260 | } | |
261 | ||
262 | static int get_main_pool_phys_params(struct toi_bdev_info *chain) | |
263 | { | |
264 | int i, extent_min = -1, extent_max = -1, result = 0, have_sig_page = 0; | |
265 | unsigned long pages_mapped = 0; | |
266 | ||
267 | toi_message(TOI_IO, TOI_VERBOSE, 0, "Getting file allocator blocks."); | |
268 | ||
269 | if (chain->blocks.first) | |
270 | toi_put_extent_chain(&chain->blocks); | |
271 | ||
272 | if (!target_is_normal_file()) { | |
273 | result = (pages_available > 0) ? | |
274 | __populate_block_list(chain, chain->blocks_per_page, | |
275 | (pages_allocated + 1) * chain->blocks_per_page - 1) : 0; | |
276 | return result; | |
277 | } | |
278 | ||
279 | /* | |
280 | * FIXME: We are assuming the first page is contiguous. Is that | |
281 | * assumption always right? | |
282 | */ | |
283 | ||
284 | for (i = 0; i < (target_inode->i_size >> PAGE_SHIFT); i++) { | |
285 | sector_t new_sector; | |
286 | ||
287 | if (!has_contiguous_blocks(chain, i)) | |
288 | continue; | |
289 | ||
290 | if (!have_sig_page) { | |
291 | have_sig_page = 1; | |
292 | sig_page_index = i; | |
293 | continue; | |
294 | } | |
295 | ||
296 | pages_mapped++; | |
297 | ||
298 | /* Ignore first page - it has the header */ | |
299 | if (pages_mapped == 1) | |
300 | continue; | |
301 | ||
302 | new_sector = bmap(target_inode, (i * chain->blocks_per_page)); | |
303 | ||
304 | /* | |
305 | * I'd love to be able to fill in holes and resize | |
306 | * files, but not yet... | |
307 | */ | |
308 | ||
309 | if (new_sector == extent_max + 1) | |
310 | extent_max += chain->blocks_per_page; | |
311 | else { | |
312 | if (extent_min > -1) { | |
313 | result = __populate_block_list(chain, extent_min, extent_max); | |
314 | if (result) | |
315 | return result; | |
316 | } | |
317 | ||
318 | extent_min = new_sector; | |
319 | extent_max = extent_min + chain->blocks_per_page - 1; | |
320 | } | |
321 | ||
322 | if (pages_mapped == pages_allocated) | |
323 | break; | |
324 | } | |
325 | ||
326 | if (extent_min > -1) { | |
327 | result = __populate_block_list(chain, extent_min, extent_max); | |
328 | if (result) | |
329 | return result; | |
330 | } | |
331 | ||
332 | return 0; | |
333 | } | |
334 | ||
335 | static void toi_file_free_storage(struct toi_bdev_info *chain) | |
336 | { | |
337 | pages_allocated = 0; | |
338 | file_chain = NULL; | |
339 | } | |
340 | ||
341 | /** | |
342 | * toi_file_print_debug_stats - print debug info | |
343 | * @buffer: Buffer to data to populate | |
344 | * @size: Size of the buffer | |
345 | **/ | |
346 | static int toi_file_print_debug_stats(char *buffer, int size) | |
347 | { | |
348 | int len = scnprintf(buffer, size, "- File Allocator active.\n"); | |
349 | ||
350 | len += scnprintf(buffer + len, size - len, " Storage available for " | |
351 | "image: %lu pages.\n", pages_available); | |
352 | ||
353 | return len; | |
354 | } | |
355 | ||
356 | static void toi_file_cleanup(int finishing_cycle) | |
357 | { | |
358 | if (toi_file_target_bdev) { | |
359 | if (target_claim) { | |
360 | blkdev_put(toi_file_target_bdev, FMODE_WRITE | FMODE_READ); | |
361 | target_claim = 0; | |
362 | } | |
363 | ||
364 | if (used_devt) { | |
365 | blkdev_put(toi_file_target_bdev, FMODE_READ | FMODE_NDELAY); | |
366 | used_devt = 0; | |
367 | } | |
368 | toi_file_target_bdev = NULL; | |
369 | target_inode = NULL; | |
370 | } | |
371 | ||
372 | if (target_file) { | |
373 | filp_close(target_file, NULL); | |
374 | target_file = NULL; | |
375 | } | |
376 | ||
377 | pages_available = 0; | |
378 | } | |
379 | ||
380 | /** | |
381 | * test_toi_file_target - sysfs callback for /sys/power/tuxonince/file/target | |
382 | * | |
383 | * Test wheter the target file is valid for hibernating. | |
384 | **/ | |
385 | static void test_toi_file_target(void) | |
386 | { | |
387 | int result = toi_file_register_storage(); | |
388 | sector_t sector; | |
389 | char buf[50]; | |
390 | struct fs_info *fs_info; | |
391 | ||
392 | if (result || !file_chain) | |
393 | return; | |
394 | ||
395 | /* This doesn't mean we're in business. Is any storage available? */ | |
396 | if (!pages_available) | |
397 | goto out; | |
398 | ||
399 | toi_file_allocate_storage(file_chain, 1); | |
400 | result = get_main_pool_phys_params(file_chain); | |
401 | if (result) | |
402 | goto out; | |
403 | ||
404 | ||
405 | sector = bmap(target_inode, sig_page_index * | |
406 | file_chain->blocks_per_page) << file_chain->bmap_shift; | |
407 | ||
408 | /* Use the uuid, or the dev_t if that fails */ | |
409 | fs_info = fs_info_from_block_dev(toi_file_target_bdev); | |
410 | if (!fs_info || IS_ERR(fs_info)) { | |
411 | bdevname(toi_file_target_bdev, buf); | |
412 | sprintf(resume_file, "/dev/%s:%llu", buf, (unsigned long long)sector); | |
413 | } else { | |
414 | int i; | |
415 | hex_dump_to_buffer(fs_info->uuid, 16, 32, 1, buf, 50, 0); | |
416 | ||
417 | /* Remove the spaces */ | |
418 | for (i = 1; i < 16; i++) { | |
419 | buf[2 * i] = buf[3 * i]; | |
420 | buf[2 * i + 1] = buf[3 * i + 1]; | |
421 | } | |
422 | buf[32] = 0; | |
423 | sprintf(resume_file, "UUID=%s:0x%llx", buf, (unsigned long long)sector); | |
424 | free_fs_info(fs_info); | |
425 | } | |
426 | ||
427 | toi_attempt_to_parse_resume_device(0); | |
428 | out: | |
429 | toi_file_free_storage(file_chain); | |
430 | toi_bio_ops.free_storage(); | |
431 | } | |
432 | ||
433 | static struct toi_sysfs_data sysfs_params[] = { | |
434 | SYSFS_STRING("target", SYSFS_RW, toi_file_target, 256, | |
435 | SYSFS_NEEDS_SM_FOR_WRITE, test_toi_file_target), | |
436 | SYSFS_INT("enabled", SYSFS_RW, &toi_fileops.enabled, 0, 1, 0, NULL), | |
437 | SYSFS_INT("priority", SYSFS_RW, &file_target_priority, -4095, | |
438 | 4096, 0, NULL), | |
439 | }; | |
440 | ||
441 | static struct toi_bio_allocator_ops toi_bio_fileops = { | |
442 | .register_storage = toi_file_register_storage, | |
443 | .storage_available = toi_file_storage_available, | |
444 | .allocate_storage = toi_file_allocate_storage, | |
445 | .bmap = get_main_pool_phys_params, | |
446 | .free_storage = toi_file_free_storage, | |
447 | }; | |
448 | ||
449 | static struct toi_module_ops toi_fileops = { | |
450 | .type = BIO_ALLOCATOR_MODULE, | |
451 | .name = "file storage", | |
452 | .directory = "file", | |
453 | .module = THIS_MODULE, | |
454 | .print_debug_info = toi_file_print_debug_stats, | |
455 | .cleanup = toi_file_cleanup, | |
456 | .bio_allocator_ops = &toi_bio_fileops, | |
457 | ||
458 | .sysfs_data = sysfs_params, | |
459 | .num_sysfs_entries = sizeof(sysfs_params) / sizeof(struct toi_sysfs_data), | |
460 | }; | |
461 | ||
462 | /* ---- Registration ---- */ | |
463 | static __init int toi_file_load(void) | |
464 | { | |
465 | return toi_register_module(&toi_fileops); | |
466 | } | |
467 | ||
468 | #ifdef MODULE | |
469 | static __exit void toi_file_unload(void) | |
470 | { | |
471 | toi_unregister_module(&toi_fileops); | |
472 | } | |
473 | module_init(toi_file_load); | |
474 | module_exit(toi_file_unload); | |
475 | MODULE_LICENSE("GPL"); | |
476 | MODULE_AUTHOR("Nigel Cunningham"); | |
477 | MODULE_DESCRIPTION("TuxOnIce FileAllocator"); | |
478 | #else | |
479 | late_initcall(toi_file_load); | |
480 | #endif |