diff options
author | Dr. Tilmann Bubeck <t.bubeck@reinform.de> | 2013-04-08 12:54:05 -0400 |
---|---|---|
committer | Theodore Ts'o <tytso@mit.edu> | 2013-04-08 12:54:05 -0400 |
commit | 393d1d1d76933886d5e1ce603214c9987589c6d5 (patch) | |
tree | 2f2368a9d787ccb8e69f61a3e5023ef9c4abfd8b /fs/ext4/ioctl.c | |
parent | f78ee70db40040e6f38a5134527d4760254d6683 (diff) |
ext4: implementation of a new ioctl called EXT4_IOC_SWAP_BOOT
Add a new ioctl, EXT4_IOC_SWAP_BOOT which swaps i_blocks and
associated attributes (like i_blocks, i_size, i_flags, ...) from the
specified inode with inode EXT4_BOOT_LOADER_INO (#5). This is
typically used to store a boot loader in a secure part of the
filesystem, where it can't be changed by a normal user by accident.
The data blocks of the previous boot loader will be associated with
the given inode.
This usercode program is a simple example of the usage:
int main(int argc, char *argv[])
{
int fd;
int err;
if ( argc != 2 ) {
printf("usage: ext4-swap-boot-inode FILE-TO-SWAP\n");
exit(1);
}
fd = open(argv[1], O_WRONLY);
if ( fd < 0 ) {
perror("open");
exit(1);
}
err = ioctl(fd, EXT4_IOC_SWAP_BOOT);
if ( err < 0 ) {
perror("ioctl");
exit(1);
}
close(fd);
exit(0);
}
[ Modified by Theodore Ts'o to fix a number of bugs in the original code.]
Signed-off-by: Dr. Tilmann Bubeck <t.bubeck@reinform.de>
Signed-off-by: "Theodore Ts'o" <tytso@mit.edu>
Diffstat (limited to 'fs/ext4/ioctl.c')
-rw-r--r-- | fs/ext4/ioctl.c | 197 |
1 files changed, 197 insertions, 0 deletions
diff --git a/fs/ext4/ioctl.c b/fs/ext4/ioctl.c index a07b7bc0856a..cbc3acea6bcf 100644 --- a/fs/ext4/ioctl.c +++ b/fs/ext4/ioctl.c | |||
@@ -17,9 +17,201 @@ | |||
17 | #include <asm/uaccess.h> | 17 | #include <asm/uaccess.h> |
18 | #include "ext4_jbd2.h" | 18 | #include "ext4_jbd2.h" |
19 | #include "ext4.h" | 19 | #include "ext4.h" |
20 | #include "ext4_extents.h" | ||
20 | 21 | ||
21 | #define MAX_32_NUM ((((unsigned long long) 1) << 32) - 1) | 22 | #define MAX_32_NUM ((((unsigned long long) 1) << 32) - 1) |
22 | 23 | ||
24 | /** | ||
25 | * Swap memory between @a and @b for @len bytes. | ||
26 | * | ||
27 | * @a: pointer to first memory area | ||
28 | * @b: pointer to second memory area | ||
29 | * @len: number of bytes to swap | ||
30 | * | ||
31 | */ | ||
32 | static void memswap(void *a, void *b, size_t len) | ||
33 | { | ||
34 | unsigned char *ap, *bp; | ||
35 | unsigned char tmp; | ||
36 | |||
37 | ap = (unsigned char *)a; | ||
38 | bp = (unsigned char *)b; | ||
39 | while (len-- > 0) { | ||
40 | tmp = *ap; | ||
41 | *ap = *bp; | ||
42 | *bp = tmp; | ||
43 | ap++; | ||
44 | bp++; | ||
45 | } | ||
46 | } | ||
47 | |||
48 | /** | ||
49 | * Swap i_data and associated attributes between @inode1 and @inode2. | ||
50 | * This function is used for the primary swap between inode1 and inode2 | ||
51 | * and also to revert this primary swap in case of errors. | ||
52 | * | ||
53 | * Therefore you have to make sure, that calling this method twice | ||
54 | * will revert all changes. | ||
55 | * | ||
56 | * @inode1: pointer to first inode | ||
57 | * @inode2: pointer to second inode | ||
58 | */ | ||
59 | static void swap_inode_data(struct inode *inode1, struct inode *inode2) | ||
60 | { | ||
61 | loff_t isize; | ||
62 | struct ext4_inode_info *ei1; | ||
63 | struct ext4_inode_info *ei2; | ||
64 | |||
65 | ei1 = EXT4_I(inode1); | ||
66 | ei2 = EXT4_I(inode2); | ||
67 | |||
68 | memswap(&inode1->i_flags, &inode2->i_flags, sizeof(inode1->i_flags)); | ||
69 | memswap(&inode1->i_version, &inode2->i_version, | ||
70 | sizeof(inode1->i_version)); | ||
71 | memswap(&inode1->i_blocks, &inode2->i_blocks, | ||
72 | sizeof(inode1->i_blocks)); | ||
73 | memswap(&inode1->i_bytes, &inode2->i_bytes, sizeof(inode1->i_bytes)); | ||
74 | memswap(&inode1->i_atime, &inode2->i_atime, sizeof(inode1->i_atime)); | ||
75 | memswap(&inode1->i_mtime, &inode2->i_mtime, sizeof(inode1->i_mtime)); | ||
76 | |||
77 | memswap(ei1->i_data, ei2->i_data, sizeof(ei1->i_data)); | ||
78 | memswap(&ei1->i_flags, &ei2->i_flags, sizeof(ei1->i_flags)); | ||
79 | memswap(&ei1->i_disksize, &ei2->i_disksize, sizeof(ei1->i_disksize)); | ||
80 | memswap(&ei1->i_es_tree, &ei2->i_es_tree, sizeof(ei1->i_es_tree)); | ||
81 | memswap(&ei1->i_es_lru_nr, &ei2->i_es_lru_nr, sizeof(ei1->i_es_lru_nr)); | ||
82 | |||
83 | isize = i_size_read(inode1); | ||
84 | i_size_write(inode1, i_size_read(inode2)); | ||
85 | i_size_write(inode2, isize); | ||
86 | } | ||
87 | |||
88 | /** | ||
89 | * Swap the information from the given @inode and the inode | ||
90 | * EXT4_BOOT_LOADER_INO. It will basically swap i_data and all other | ||
91 | * important fields of the inodes. | ||
92 | * | ||
93 | * @sb: the super block of the filesystem | ||
94 | * @inode: the inode to swap with EXT4_BOOT_LOADER_INO | ||
95 | * | ||
96 | */ | ||
97 | static long swap_inode_boot_loader(struct super_block *sb, | ||
98 | struct inode *inode) | ||
99 | { | ||
100 | handle_t *handle; | ||
101 | int err; | ||
102 | struct inode *inode_bl; | ||
103 | struct ext4_inode_info *ei; | ||
104 | struct ext4_inode_info *ei_bl; | ||
105 | struct ext4_sb_info *sbi; | ||
106 | |||
107 | if (inode->i_nlink != 1 || !S_ISREG(inode->i_mode)) { | ||
108 | err = -EINVAL; | ||
109 | goto swap_boot_out; | ||
110 | } | ||
111 | |||
112 | if (!inode_owner_or_capable(inode) || !capable(CAP_SYS_ADMIN)) { | ||
113 | err = -EPERM; | ||
114 | goto swap_boot_out; | ||
115 | } | ||
116 | |||
117 | sbi = EXT4_SB(sb); | ||
118 | ei = EXT4_I(inode); | ||
119 | |||
120 | inode_bl = ext4_iget(sb, EXT4_BOOT_LOADER_INO); | ||
121 | if (IS_ERR(inode_bl)) { | ||
122 | err = PTR_ERR(inode_bl); | ||
123 | goto swap_boot_out; | ||
124 | } | ||
125 | ei_bl = EXT4_I(inode_bl); | ||
126 | |||
127 | filemap_flush(inode->i_mapping); | ||
128 | filemap_flush(inode_bl->i_mapping); | ||
129 | |||
130 | /* Protect orig inodes against a truncate and make sure, | ||
131 | * that only 1 swap_inode_boot_loader is running. */ | ||
132 | ext4_inode_double_lock(inode, inode_bl); | ||
133 | |||
134 | truncate_inode_pages(&inode->i_data, 0); | ||
135 | truncate_inode_pages(&inode_bl->i_data, 0); | ||
136 | |||
137 | /* Wait for all existing dio workers */ | ||
138 | ext4_inode_block_unlocked_dio(inode); | ||
139 | ext4_inode_block_unlocked_dio(inode_bl); | ||
140 | inode_dio_wait(inode); | ||
141 | inode_dio_wait(inode_bl); | ||
142 | |||
143 | handle = ext4_journal_start(inode_bl, EXT4_HT_MOVE_EXTENTS, 2); | ||
144 | if (IS_ERR(handle)) { | ||
145 | err = -EINVAL; | ||
146 | goto swap_boot_out; | ||
147 | } | ||
148 | |||
149 | /* Protect extent tree against block allocations via delalloc */ | ||
150 | ext4_double_down_write_data_sem(inode, inode_bl); | ||
151 | |||
152 | if (inode_bl->i_nlink == 0) { | ||
153 | /* this inode has never been used as a BOOT_LOADER */ | ||
154 | set_nlink(inode_bl, 1); | ||
155 | i_uid_write(inode_bl, 0); | ||
156 | i_gid_write(inode_bl, 0); | ||
157 | inode_bl->i_flags = 0; | ||
158 | ei_bl->i_flags = 0; | ||
159 | inode_bl->i_version = 1; | ||
160 | i_size_write(inode_bl, 0); | ||
161 | inode_bl->i_mode = S_IFREG; | ||
162 | if (EXT4_HAS_INCOMPAT_FEATURE(sb, | ||
163 | EXT4_FEATURE_INCOMPAT_EXTENTS)) { | ||
164 | ext4_set_inode_flag(inode_bl, EXT4_INODE_EXTENTS); | ||
165 | ext4_ext_tree_init(handle, inode_bl); | ||
166 | } else | ||
167 | memset(ei_bl->i_data, 0, sizeof(ei_bl->i_data)); | ||
168 | } | ||
169 | |||
170 | swap_inode_data(inode, inode_bl); | ||
171 | |||
172 | inode->i_ctime = inode_bl->i_ctime = ext4_current_time(inode); | ||
173 | |||
174 | spin_lock(&sbi->s_next_gen_lock); | ||
175 | inode->i_generation = sbi->s_next_generation++; | ||
176 | inode_bl->i_generation = sbi->s_next_generation++; | ||
177 | spin_unlock(&sbi->s_next_gen_lock); | ||
178 | |||
179 | ext4_discard_preallocations(inode); | ||
180 | |||
181 | err = ext4_mark_inode_dirty(handle, inode); | ||
182 | if (err < 0) { | ||
183 | ext4_warning(inode->i_sb, | ||
184 | "couldn't mark inode #%lu dirty (err %d)", | ||
185 | inode->i_ino, err); | ||
186 | /* Revert all changes: */ | ||
187 | swap_inode_data(inode, inode_bl); | ||
188 | } else { | ||
189 | err = ext4_mark_inode_dirty(handle, inode_bl); | ||
190 | if (err < 0) { | ||
191 | ext4_warning(inode_bl->i_sb, | ||
192 | "couldn't mark inode #%lu dirty (err %d)", | ||
193 | inode_bl->i_ino, err); | ||
194 | /* Revert all changes: */ | ||
195 | swap_inode_data(inode, inode_bl); | ||
196 | ext4_mark_inode_dirty(handle, inode); | ||
197 | } | ||
198 | } | ||
199 | |||
200 | ext4_journal_stop(handle); | ||
201 | |||
202 | ext4_double_up_write_data_sem(inode, inode_bl); | ||
203 | |||
204 | ext4_inode_resume_unlocked_dio(inode); | ||
205 | ext4_inode_resume_unlocked_dio(inode_bl); | ||
206 | |||
207 | ext4_inode_double_unlock(inode, inode_bl); | ||
208 | |||
209 | iput(inode_bl); | ||
210 | |||
211 | swap_boot_out: | ||
212 | return err; | ||
213 | } | ||
214 | |||
23 | long ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) | 215 | long ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) |
24 | { | 216 | { |
25 | struct inode *inode = file_inode(filp); | 217 | struct inode *inode = file_inode(filp); |
@@ -353,6 +545,11 @@ group_add_out: | |||
353 | return err; | 545 | return err; |
354 | } | 546 | } |
355 | 547 | ||
548 | case EXT4_IOC_SWAP_BOOT: | ||
549 | if (!(filp->f_mode & FMODE_WRITE)) | ||
550 | return -EBADF; | ||
551 | return swap_inode_boot_loader(sb, inode); | ||
552 | |||
356 | case EXT4_IOC_RESIZE_FS: { | 553 | case EXT4_IOC_RESIZE_FS: { |
357 | ext4_fsblk_t n_blocks_count; | 554 | ext4_fsblk_t n_blocks_count; |
358 | struct super_block *sb = inode->i_sb; | 555 | struct super_block *sb = inode->i_sb; |