#include "clusterautoconfig.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define _(String) gettext(String) #include #include "libgfs2.h" #include "gfs2_mkfs.h" #include "metafs.h" #define BUF_SIZE 4096 #define MB (1024 * 1024) static uint64_t override_device_size = 0; static int test = 0; static uint64_t fssize = 0, fsgrowth; int print_level = MSG_NOTICE; extern int create_new_inode(struct gfs2_sbd *sdp); extern int rename2system(struct gfs2_sbd *sdp, char *new_dir, char *new_name); #ifndef FALLOC_FL_KEEP_SIZE #define FALLOC_FL_KEEP_SIZE 0x01 #endif #ifndef BLKDISCARD #define BLKDISCARD _IO(0x12,119) #endif static int discard_blocks(int fd, uint64_t start, uint64_t len) { __uint64_t range[2] = { start, len }; if (ioctl(fd, BLKDISCARD, &range) < 0) return errno; return 0; } /** * usage - Print out the usage message * * This function does not include documentation for the -D option * since normal users have no use for it at all. The -D option is * only for developers. It intended use is in combination with the * -T flag to find out what the result would be of trying different * device sizes without actually having to try them manually. */ static void usage(void) { int i; const char *option, *param, *desc; const char *options[] = { "-h", NULL, _("Display this usage information"), "-q", NULL, _("Quiet, reduce verbosity"), "-T", NULL, _("Do everything except update file system"), "-V", NULL, _("Display version information"), "-v", NULL, _("Increase verbosity"), NULL, NULL, NULL /* Must be kept at the end */ }; printf("%s\n", _("Usage:")); printf(" gfs2_grow [%s] <%s>\n\n", _("options"), _("device")); printf(_("Expands a GFS2 file system after the device containing the file system has been expanded")); printf("\n\n%s\n", _("Options:")); for (i = 0; options[i] != NULL; i += 3) { option = options[i]; param = options[i+1]; desc = options[i+2]; printf("%3s %-15s %s\n", option, param ? param : "", desc); } } static void decode_arguments(int argc, char *argv[], struct gfs2_sbd *sdp) { int opt; while ((opt = getopt(argc, argv, "VD:hqTv?")) != EOF) { switch (opt) { case 'D': /* This option is for testing only */ override_device_size = atoi(optarg); override_device_size <<= 20; break; case 'V': printf(_("%s %s (built %s %s)\n"), argv[0], VERSION, __DATE__, __TIME__); printf(REDHAT_COPYRIGHT "\n"); exit(0); case 'h': usage(); exit(0); case 'q': decrease_verbosity(); break; case 'T': printf( _("(Test mode - file system will not " "be changed)\n")); test = 1; break; case 'v': increase_verbosity(); break; case ':': case '?': /* Unknown flag */ fprintf(stderr, _("Please use '-h' for help.\n")); exit(EXIT_FAILURE); default: fprintf(stderr, _("Invalid option '%c'\n"), opt); exit(EXIT_FAILURE); break; } } if (optind == argc) { usage(); exit(EXIT_FAILURE); } } static lgfs2_rgrps_t rgrps_init(struct gfs2_sbd *sdp) { int ret; int error; uint64_t al_base = 0; uint64_t al_off = 0; struct stat st; blkid_probe pr = blkid_new_probe(); if (pr == NULL || blkid_probe_set_device(pr, sdp->device_fd, 0, 0) != 0 || blkid_probe_enable_superblocks(pr, TRUE) != 0 || blkid_probe_enable_partitions(pr, TRUE) != 0) { fprintf(stderr, _("Failed to create probe\n")); return NULL; } error = fstat(sdp->device_fd, &st); if (error < 0) { fprintf(stderr, _("fstat failed\n")); return NULL; } if (!S_ISREG(st.st_mode) && blkid_probe_enable_topology(pr, TRUE) != 0) { fprintf(stderr, _("Failed to create probe\n")); return NULL; } ret = blkid_do_fullprobe(pr); if (ret == 0 && !S_ISREG(st.st_mode)) { blkid_topology tp = blkid_probe_get_topology(pr); if (tp != NULL) { unsigned long min_io_sz = blkid_topology_get_minimum_io_size(tp); unsigned long opt_io_sz = blkid_topology_get_optimal_io_size(tp); unsigned long phy_sector_sz = blkid_topology_get_physical_sector_size(tp); /* If optimal_io_size is not a multiple of minimum_io_size then the values are not reliable swidth and sunit values, so don't attempt rgrp alignment */ if ((min_io_sz > phy_sector_sz) && (opt_io_sz > phy_sector_sz) && (opt_io_sz % min_io_sz == 0)) { al_base = opt_io_sz / sdp->bsize; al_off = min_io_sz / sdp->bsize; } } } blkid_free_probe(pr); return lgfs2_rgrps_init(sdp, al_base, al_off); } /** * Calculate the size of the filesystem * Reads the lists of resource groups in order to work out where the last block * of the filesystem is located. * Returns: The calculated size */ static uint64_t filesystem_size(lgfs2_rgrps_t rgs) { lgfs2_rgrp_t rg = lgfs2_rgrp_last(rgs); const struct gfs2_rindex *ri = lgfs2_rgrp_index(rg); return ri->ri_data0 + ri->ri_data; } /** * Write the new rg information to disk. */ static unsigned initialize_new_portion(struct gfs2_sbd *sdp, lgfs2_rgrps_t rgs) { unsigned rgcount = 0; uint64_t rgaddr = fssize; discard_blocks(sdp->device_fd, rgaddr * sdp->bsize, fsgrowth * sdp->bsize); /* Build the remaining resource groups */ while (1) { int err = 0; lgfs2_rgrp_t rg; struct gfs2_rindex ri; uint64_t nextaddr; nextaddr = lgfs2_rindex_entry_new(rgs, &ri, rgaddr, 0); if (nextaddr == 0) break; rg = lgfs2_rgrps_append(rgs, &ri, nextaddr - rgaddr); if (rg == NULL) { perror(_("Failed to create resource group")); return 0; } rgaddr = nextaddr; if (metafs_interrupted) return 0; if (!test) err = lgfs2_rgrp_write(sdp->device_fd, rg); if (err != 0) { perror(_("Failed to write resource group")); return 0; } rgcount++; } fsync(sdp->device_fd); return rgcount; } static char *rindex_buffer(lgfs2_rgrps_t rgs, unsigned count) { lgfs2_rgrp_t rg; unsigned i = 0; char *buf; buf = calloc(count, sizeof(struct gfs2_rindex)); if (buf == NULL) { perror(__FUNCTION__); exit(EXIT_FAILURE); } for (rg = lgfs2_rgrp_first(rgs); rg; rg = lgfs2_rgrp_next(rg)) { const struct gfs2_rindex *ri = lgfs2_rgrp_index(rg); gfs2_rindex_out(ri, buf + (sizeof(*ri) * i)); i++; } return buf; } /** * fix_rindex - Add the new entries to the end of the rindex file. */ static void fix_rindex(int rindex_fd, lgfs2_rgrps_t rgs, unsigned old_rg_count, unsigned rgcount) { char *buf; ssize_t count; ssize_t writelen; off_t rindex_size; const size_t entrysize = sizeof(struct gfs2_rindex); log_info( _("%d new rindex entries.\n"), rgcount); buf = rindex_buffer(rgs, rgcount); writelen = rgcount * entrysize; if (test) goto out; rindex_size = lseek(rindex_fd, 0, SEEK_END); if (rindex_size != old_rg_count * entrysize) { log_crit(_("Incorrect rindex size. Want %ld (%d resource groups), have %ld\n"), (long)(old_rg_count * entrysize), old_rg_count, (long)rindex_size); goto out; } /* Write the first entry separately to ensure there's enough space in the fs for the rest */ count = write(rindex_fd, buf, entrysize); if (count != entrysize) { log_crit(_("Error writing first new rindex entry; aborted.\n")); if (count > 0) goto trunc; else goto out; } count = write(rindex_fd, (buf + entrysize), (writelen - entrysize)); if (count != (writelen - entrysize)) { log_crit(_("Error writing new rindex entries; aborted.\n")); if (count > 0) goto trunc; else goto out; } if (fallocate(rindex_fd, FALLOC_FL_KEEP_SIZE, (rindex_size + writelen), entrysize) != 0) perror("fallocate"); fsync(rindex_fd); out: free(buf); return; trunc: count = (count / sizeof(struct gfs2_rindex)) + old_rg_count; log_crit(_("truncating rindex to %ld entries\n"), (long)count * sizeof(struct gfs2_rindex)); if (ftruncate(rindex_fd, (off_t)count * sizeof(struct gfs2_rindex))) log_crit(_("Could not truncate rindex: %s\n"), strerror(errno)); free(buf); } /** * print_info - Print out various bits of (interesting?) information */ static void print_info(struct gfs2_sbd *sdp, char *device, char *mnt_path) { log_notice("FS: %-25s%s\n", _("Mount point:"), mnt_path); log_notice("FS: %-25s%s\n", _("Device:"), device); log_notice("FS: %-25s%llu (0x%llx)\n", _("Size:"), (unsigned long long)fssize, (unsigned long long)fssize); log_notice("DEV: %-24s%llu (0x%llx)\n", _("Length:"), (unsigned long long)sdp->device.length, (unsigned long long)sdp->device.length); log_notice(_("The file system will grow by %lluMB.\n"), (unsigned long long)(fsgrowth * sdp->bsize) / MB); } static int open_rindex(char *metafs_path, int mode) { char *path; int fd; if (asprintf(&path, "%s/rindex", metafs_path) < 0) { perror(_("Failed to open rindex")); return -1; } fd = open(path, (mode | O_CLOEXEC)); if (fd < 0) { perror(path); fprintf(stderr, _("Please run fsck.gfs2\n")); } free(path); return fd; } int main(int argc, char *argv[]) { struct gfs2_sbd sbd, *sdp = &sbd; int rindex_fd; int error = EXIT_SUCCESS; int devflags = (test ? O_RDONLY : O_RDWR) | O_CLOEXEC; setlocale(LC_ALL, ""); textdomain("gfs2-utils"); srandom(time(NULL) ^ getpid()); memset(sdp, 0, sizeof(struct gfs2_sbd)); sdp->bsize = GFS2_DEFAULT_BSIZE; sdp->rgsize = -1; sdp->jsize = GFS2_DEFAULT_JSIZE; sdp->qcsize = GFS2_DEFAULT_QCSIZE; sdp->md.journals = 1; decode_arguments(argc, argv, sdp); for(; (argc - optind) > 0; optind++) { struct metafs mfs = {0}; struct mntent *mnt; unsigned rgcount; unsigned old_rg_count; lgfs2_rgrps_t rgs; error = lgfs2_open_mnt(argv[optind], O_RDONLY|O_CLOEXEC, &sdp->path_fd, devflags, &sdp->device_fd, &mnt); if (error != 0) { fprintf(stderr, _("Error looking up mount '%s': %s\n"), argv[optind], strerror(errno)); exit(EXIT_FAILURE); } if (mnt == NULL) { fprintf(stderr, _("%s: not a mounted gfs2 file system\n"), argv[optind]); continue; } if (lgfs2_get_dev_info(sdp->device_fd, &sdp->dinfo) < 0) { perror(mnt->mnt_fsname); exit(EXIT_FAILURE); } sdp->sd_sb.sb_bsize = GFS2_DEFAULT_BSIZE; sdp->bsize = sdp->sd_sb.sb_bsize; if (compute_constants(sdp)) { log_crit("%s\n", _("Failed to compute file system constants")); exit(EXIT_FAILURE); } if (read_sb(sdp) < 0) { fprintf(stderr, _("Error reading superblock.\n")); exit(EXIT_FAILURE); } if (sdp->gfs1) { fprintf(stderr, _("cannot grow gfs1 filesystem\n")); exit(EXIT_FAILURE); } fix_device_geometry(sdp); mfs.context = copy_context_opt(mnt); if (mount_gfs2_meta(&mfs, mnt->mnt_dir, (print_level > MSG_NOTICE))) { perror(_("Failed to mount GFS2 meta file system")); exit(EXIT_FAILURE); } rindex_fd = open_rindex(mfs.path, (test ? O_RDONLY : O_RDWR)); if (rindex_fd < 0) { cleanup_metafs(&mfs); exit(EXIT_FAILURE); } /* Get master dinode */ sdp->master_dir = lgfs2_inode_read(sdp, sdp->sd_sb.sb_master_dir.no_addr); if (sdp->master_dir == NULL) { perror(_("Could not read master directory")); exit(EXIT_FAILURE); } rgs = rgrps_init(sdp); if (rgs == NULL) { perror(_("Could not initialise resource groups")); error = -1; goto out; } /* Fetch the rindex from disk. We aren't using gfs2 here, */ /* which means that the bitmaps will most likely be cached */ /* and therefore out of date. It shouldn't matter because */ /* we're only going to write out new RG information after */ /* the existing RGs, and only write to the index at EOF. */ log_info(_("Gathering resource group information for %s\n"), argv[optind]); old_rg_count = lgfs2_rindex_read_fd(rindex_fd, rgs); if (old_rg_count == 0) { perror(_("Failed to scan existing resource groups")); error = -EXIT_FAILURE; goto out; } if (metafs_interrupted) goto out; fssize = lgfs2_rgrp_align_addr(rgs, filesystem_size(rgs) + 1); /* We're done with the old rgs now that we have the fssize and rg count */ lgfs2_rgrps_free(&rgs); /* Now lets set up the new ones with alignment and all */ rgs = rgrps_init(sdp); if (rgs == NULL) { perror(_("Could not initialise new resource groups")); error = -1; goto out; } fsgrowth = (sdp->device.length - fssize); rgcount = lgfs2_rgrps_plan(rgs, fsgrowth, ((GFS2_MAX_RGSIZE << 20) / sdp->bsize)); if (rgcount == 0) { log_err( _("The calculated resource group size is too small.\n")); log_err( _("%s has not grown.\n"), argv[optind]); error = -1; goto out; } print_info(sdp, mnt->mnt_fsname, mnt->mnt_dir); rgcount = initialize_new_portion(sdp, rgs); if (rgcount == 0 || metafs_interrupted) goto out; fsync(sdp->device_fd); fix_rindex(rindex_fd, rgs, old_rg_count, rgcount); out: lgfs2_rgrps_free(&rgs); close(rindex_fd); cleanup_metafs(&mfs); close(sdp->device_fd); if (metafs_interrupted) break; } close(sdp->path_fd); sync(); if (metafs_interrupted) { log_notice( _("gfs2_grow interrupted.\n")); exit(1); } log_notice( _("gfs2_grow complete.\n")); return error; }