|
27 | 27 | #include "fsmap.h"
|
28 | 28 | #include <trace/events/ext4.h>
|
29 | 29 |
|
| 30 | +typedef void ext4_update_sb_callback(struct ext4_super_block *es, |
| 31 | + const void *arg); |
| 32 | + |
| 33 | +/* |
| 34 | + * Superblock modification callback function for changing file system |
| 35 | + * label |
| 36 | + */ |
| 37 | +static void ext4_sb_setlabel(struct ext4_super_block *es, const void *arg) |
| 38 | +{ |
| 39 | + /* Sanity check, this should never happen */ |
| 40 | + BUILD_BUG_ON(sizeof(es->s_volume_name) < EXT4_LABEL_MAX); |
| 41 | + |
| 42 | + memcpy(es->s_volume_name, (char *)arg, EXT4_LABEL_MAX); |
| 43 | +} |
| 44 | + |
| 45 | +static |
| 46 | +int ext4_update_primary_sb(struct super_block *sb, handle_t *handle, |
| 47 | + ext4_update_sb_callback func, |
| 48 | + const void *arg) |
| 49 | +{ |
| 50 | + int err = 0; |
| 51 | + struct ext4_sb_info *sbi = EXT4_SB(sb); |
| 52 | + struct buffer_head *bh = sbi->s_sbh; |
| 53 | + struct ext4_super_block *es = sbi->s_es; |
| 54 | + |
| 55 | + trace_ext4_update_sb(sb, bh->b_blocknr, 1); |
| 56 | + |
| 57 | + BUFFER_TRACE(bh, "get_write_access"); |
| 58 | + err = ext4_journal_get_write_access(handle, sb, |
| 59 | + bh, |
| 60 | + EXT4_JTR_NONE); |
| 61 | + if (err) |
| 62 | + goto out_err; |
| 63 | + |
| 64 | + lock_buffer(bh); |
| 65 | + func(es, arg); |
| 66 | + ext4_superblock_csum_set(sb); |
| 67 | + unlock_buffer(bh); |
| 68 | + |
| 69 | + if (buffer_write_io_error(bh) || !buffer_uptodate(bh)) { |
| 70 | + ext4_msg(sbi->s_sb, KERN_ERR, "previous I/O error to " |
| 71 | + "superblock detected"); |
| 72 | + clear_buffer_write_io_error(bh); |
| 73 | + set_buffer_uptodate(bh); |
| 74 | + } |
| 75 | + |
| 76 | + err = ext4_handle_dirty_metadata(handle, NULL, bh); |
| 77 | + if (err) |
| 78 | + goto out_err; |
| 79 | + err = sync_dirty_buffer(bh); |
| 80 | +out_err: |
| 81 | + ext4_std_error(sb, err); |
| 82 | + return err; |
| 83 | +} |
| 84 | + |
| 85 | +/* |
| 86 | + * Update one backup superblock in the group 'grp' using the callback |
| 87 | + * function 'func' and argument 'arg'. If the handle is NULL the |
| 88 | + * modification is not journalled. |
| 89 | + * |
| 90 | + * Returns: 0 when no modification was done (no superblock in the group) |
| 91 | + * 1 when the modification was successful |
| 92 | + * <0 on error |
| 93 | + */ |
| 94 | +static int ext4_update_backup_sb(struct super_block *sb, |
| 95 | + handle_t *handle, ext4_group_t grp, |
| 96 | + ext4_update_sb_callback func, const void *arg) |
| 97 | +{ |
| 98 | + int err = 0; |
| 99 | + ext4_fsblk_t sb_block; |
| 100 | + struct buffer_head *bh; |
| 101 | + unsigned long offset = 0; |
| 102 | + struct ext4_super_block *es; |
| 103 | + |
| 104 | + if (!ext4_bg_has_super(sb, grp)) |
| 105 | + return 0; |
| 106 | + |
| 107 | + /* |
| 108 | + * For the group 0 there is always 1k padding, so we have |
| 109 | + * either adjust offset, or sb_block depending on blocksize |
| 110 | + */ |
| 111 | + if (grp == 0) { |
| 112 | + sb_block = 1 * EXT4_MIN_BLOCK_SIZE; |
| 113 | + offset = do_div(sb_block, sb->s_blocksize); |
| 114 | + } else { |
| 115 | + sb_block = ext4_group_first_block_no(sb, grp); |
| 116 | + offset = 0; |
| 117 | + } |
| 118 | + |
| 119 | + trace_ext4_update_sb(sb, sb_block, handle ? 1 : 0); |
| 120 | + |
| 121 | + bh = ext4_sb_bread(sb, sb_block, 0); |
| 122 | + if (IS_ERR(bh)) |
| 123 | + return PTR_ERR(bh); |
| 124 | + |
| 125 | + if (handle) { |
| 126 | + BUFFER_TRACE(bh, "get_write_access"); |
| 127 | + err = ext4_journal_get_write_access(handle, sb, |
| 128 | + bh, |
| 129 | + EXT4_JTR_NONE); |
| 130 | + if (err) |
| 131 | + goto out_bh; |
| 132 | + } |
| 133 | + |
| 134 | + es = (struct ext4_super_block *) (bh->b_data + offset); |
| 135 | + lock_buffer(bh); |
| 136 | + if (ext4_has_metadata_csum(sb) && |
| 137 | + es->s_checksum != ext4_superblock_csum(sb, es)) { |
| 138 | + ext4_msg(sb, KERN_ERR, "Invalid checksum for backup " |
| 139 | + "superblock %llu\n", sb_block); |
| 140 | + unlock_buffer(bh); |
| 141 | + err = -EFSBADCRC; |
| 142 | + goto out_bh; |
| 143 | + } |
| 144 | + func(es, arg); |
| 145 | + if (ext4_has_metadata_csum(sb)) |
| 146 | + es->s_checksum = ext4_superblock_csum(sb, es); |
| 147 | + set_buffer_uptodate(bh); |
| 148 | + unlock_buffer(bh); |
| 149 | + |
| 150 | + if (err) |
| 151 | + goto out_bh; |
| 152 | + |
| 153 | + if (handle) { |
| 154 | + err = ext4_handle_dirty_metadata(handle, NULL, bh); |
| 155 | + if (err) |
| 156 | + goto out_bh; |
| 157 | + } else { |
| 158 | + BUFFER_TRACE(bh, "marking dirty"); |
| 159 | + mark_buffer_dirty(bh); |
| 160 | + } |
| 161 | + err = sync_dirty_buffer(bh); |
| 162 | + |
| 163 | +out_bh: |
| 164 | + brelse(bh); |
| 165 | + ext4_std_error(sb, err); |
| 166 | + return (err) ? err : 1; |
| 167 | +} |
| 168 | + |
| 169 | +/* |
| 170 | + * Update primary and backup superblocks using the provided function |
| 171 | + * func and argument arg. |
| 172 | + * |
| 173 | + * Only the primary superblock and at most two backup superblock |
| 174 | + * modifications are journalled; the rest is modified without journal. |
| 175 | + * This is safe because e2fsck will re-write them if there is a problem, |
| 176 | + * and we're very unlikely to ever need more than two backups. |
| 177 | + */ |
| 178 | +static |
| 179 | +int ext4_update_superblocks_fn(struct super_block *sb, |
| 180 | + ext4_update_sb_callback func, |
| 181 | + const void *arg) |
| 182 | +{ |
| 183 | + handle_t *handle; |
| 184 | + ext4_group_t ngroups; |
| 185 | + unsigned int three = 1; |
| 186 | + unsigned int five = 5; |
| 187 | + unsigned int seven = 7; |
| 188 | + int err = 0, ret, i; |
| 189 | + ext4_group_t grp, primary_grp; |
| 190 | + struct ext4_sb_info *sbi = EXT4_SB(sb); |
| 191 | + |
| 192 | + /* |
| 193 | + * We can't update superblocks while the online resize is running |
| 194 | + */ |
| 195 | + if (test_and_set_bit_lock(EXT4_FLAGS_RESIZING, |
| 196 | + &sbi->s_ext4_flags)) { |
| 197 | + ext4_msg(sb, KERN_ERR, "Can't modify superblock while" |
| 198 | + "performing online resize"); |
| 199 | + return -EBUSY; |
| 200 | + } |
| 201 | + |
| 202 | + /* |
| 203 | + * We're only going to update primary superblock and two |
| 204 | + * backup superblocks in this transaction. |
| 205 | + */ |
| 206 | + handle = ext4_journal_start_sb(sb, EXT4_HT_MISC, 3); |
| 207 | + if (IS_ERR(handle)) { |
| 208 | + err = PTR_ERR(handle); |
| 209 | + goto out; |
| 210 | + } |
| 211 | + |
| 212 | + /* Update primary superblock */ |
| 213 | + err = ext4_update_primary_sb(sb, handle, func, arg); |
| 214 | + if (err) { |
| 215 | + ext4_msg(sb, KERN_ERR, "Failed to update primary " |
| 216 | + "superblock"); |
| 217 | + goto out_journal; |
| 218 | + } |
| 219 | + |
| 220 | + primary_grp = ext4_get_group_number(sb, sbi->s_sbh->b_blocknr); |
| 221 | + ngroups = ext4_get_groups_count(sb); |
| 222 | + |
| 223 | + /* |
| 224 | + * Update backup superblocks. We have to start from group 0 |
| 225 | + * because it might not be where the primary superblock is |
| 226 | + * if the fs is mounted with -o sb=<backup_sb_block> |
| 227 | + */ |
| 228 | + i = 0; |
| 229 | + grp = 0; |
| 230 | + while (grp < ngroups) { |
| 231 | + /* Skip primary superblock */ |
| 232 | + if (grp == primary_grp) |
| 233 | + goto next_grp; |
| 234 | + |
| 235 | + ret = ext4_update_backup_sb(sb, handle, grp, func, arg); |
| 236 | + if (ret < 0) { |
| 237 | + /* Ignore bad checksum; try to update next sb */ |
| 238 | + if (ret == -EFSBADCRC) |
| 239 | + goto next_grp; |
| 240 | + err = ret; |
| 241 | + goto out_journal; |
| 242 | + } |
| 243 | + |
| 244 | + i += ret; |
| 245 | + if (handle && i > 1) { |
| 246 | + /* |
| 247 | + * We're only journalling primary superblock and |
| 248 | + * two backup superblocks; the rest is not |
| 249 | + * journalled. |
| 250 | + */ |
| 251 | + err = ext4_journal_stop(handle); |
| 252 | + if (err) |
| 253 | + goto out; |
| 254 | + handle = NULL; |
| 255 | + } |
| 256 | +next_grp: |
| 257 | + grp = ext4_list_backups(sb, &three, &five, &seven); |
| 258 | + } |
| 259 | + |
| 260 | +out_journal: |
| 261 | + if (handle) { |
| 262 | + ret = ext4_journal_stop(handle); |
| 263 | + if (ret && !err) |
| 264 | + err = ret; |
| 265 | + } |
| 266 | +out: |
| 267 | + clear_bit_unlock(EXT4_FLAGS_RESIZING, &sbi->s_ext4_flags); |
| 268 | + smp_mb__after_atomic(); |
| 269 | + return err ? err : 0; |
| 270 | +} |
| 271 | + |
30 | 272 | /**
|
31 | 273 | * Swap memory between @a and @b for @len bytes.
|
32 | 274 | *
|
@@ -847,6 +1089,64 @@ static int ext4_ioctl_checkpoint(struct file *filp, unsigned long arg)
|
847 | 1089 | return err;
|
848 | 1090 | }
|
849 | 1091 |
|
| 1092 | +static int ext4_ioctl_setlabel(struct file *filp, const char __user *user_label) |
| 1093 | +{ |
| 1094 | + size_t len; |
| 1095 | + int ret = 0; |
| 1096 | + char new_label[EXT4_LABEL_MAX + 1]; |
| 1097 | + struct super_block *sb = file_inode(filp)->i_sb; |
| 1098 | + |
| 1099 | + if (!capable(CAP_SYS_ADMIN)) |
| 1100 | + return -EPERM; |
| 1101 | + |
| 1102 | + /* |
| 1103 | + * Copy the maximum length allowed for ext4 label with one more to |
| 1104 | + * find the required terminating null byte in order to test the |
| 1105 | + * label length. The on disk label doesn't need to be null terminated. |
| 1106 | + */ |
| 1107 | + if (copy_from_user(new_label, user_label, EXT4_LABEL_MAX + 1)) |
| 1108 | + return -EFAULT; |
| 1109 | + |
| 1110 | + len = strnlen(new_label, EXT4_LABEL_MAX + 1); |
| 1111 | + if (len > EXT4_LABEL_MAX) |
| 1112 | + return -EINVAL; |
| 1113 | + |
| 1114 | + /* |
| 1115 | + * Clear the buffer after the new label |
| 1116 | + */ |
| 1117 | + memset(new_label + len, 0, EXT4_LABEL_MAX - len); |
| 1118 | + |
| 1119 | + ret = mnt_want_write_file(filp); |
| 1120 | + if (ret) |
| 1121 | + return ret; |
| 1122 | + |
| 1123 | + ret = ext4_update_superblocks_fn(sb, ext4_sb_setlabel, new_label); |
| 1124 | + |
| 1125 | + mnt_drop_write_file(filp); |
| 1126 | + return ret; |
| 1127 | +} |
| 1128 | + |
| 1129 | +static int ext4_ioctl_getlabel(struct ext4_sb_info *sbi, char __user *user_label) |
| 1130 | +{ |
| 1131 | + char label[EXT4_LABEL_MAX + 1]; |
| 1132 | + |
| 1133 | + /* |
| 1134 | + * EXT4_LABEL_MAX must always be smaller than FSLABEL_MAX because |
| 1135 | + * FSLABEL_MAX must include terminating null byte, while s_volume_name |
| 1136 | + * does not have to. |
| 1137 | + */ |
| 1138 | + BUILD_BUG_ON(EXT4_LABEL_MAX >= FSLABEL_MAX); |
| 1139 | + |
| 1140 | + memset(label, 0, sizeof(label)); |
| 1141 | + lock_buffer(sbi->s_sbh); |
| 1142 | + strncpy(label, sbi->s_es->s_volume_name, EXT4_LABEL_MAX); |
| 1143 | + unlock_buffer(sbi->s_sbh); |
| 1144 | + |
| 1145 | + if (copy_to_user(user_label, label, sizeof(label))) |
| 1146 | + return -EFAULT; |
| 1147 | + return 0; |
| 1148 | +} |
| 1149 | + |
850 | 1150 | static long __ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
851 | 1151 | {
|
852 | 1152 | struct inode *inode = file_inode(filp);
|
@@ -1261,6 +1561,13 @@ static long __ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
1261 | 1561 | case EXT4_IOC_CHECKPOINT:
|
1262 | 1562 | return ext4_ioctl_checkpoint(filp, arg);
|
1263 | 1563 |
|
| 1564 | + case FS_IOC_GETFSLABEL: |
| 1565 | + return ext4_ioctl_getlabel(EXT4_SB(sb), (void __user *)arg); |
| 1566 | + |
| 1567 | + case FS_IOC_SETFSLABEL: |
| 1568 | + return ext4_ioctl_setlabel(filp, |
| 1569 | + (const void __user *)arg); |
| 1570 | + |
1264 | 1571 | default:
|
1265 | 1572 | return -ENOTTY;
|
1266 | 1573 | }
|
@@ -1336,6 +1643,8 @@ long ext4_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
1336 | 1643 | case EXT4_IOC_GETSTATE:
|
1337 | 1644 | case EXT4_IOC_GET_ES_CACHE:
|
1338 | 1645 | case EXT4_IOC_CHECKPOINT:
|
| 1646 | + case FS_IOC_GETFSLABEL: |
| 1647 | + case FS_IOC_SETFSLABEL: |
1339 | 1648 | break;
|
1340 | 1649 | default:
|
1341 | 1650 | return -ENOIOCTLCMD;
|
|
0 commit comments