mirror of
https://github.com/nilfs-dev/nilfs-utils.git
synced 2026-01-26 13:43:15 +00:00
Apply the NILFS_UTILS_GITID() macro to all major executables and libraries to enable git revision tracking. Signed-off-by: Ryusuke Konishi <konishi.ryusuke@gmail.com>
1855 lines
50 KiB
C
1855 lines
50 KiB
C
/*
|
|
* nilfs-resize.c - resize nilfs2 volume
|
|
*
|
|
* Licensed under GPLv2: the complete text of the GNU General Public License
|
|
* can be found in COPYING file of the nilfs-utils package.
|
|
*
|
|
* Copyright (C) 2011-2012 Nippon Telegraph and Telephone Corporation.
|
|
*
|
|
* Credits:
|
|
* Ryusuke Konishi <konishi.ryusuke@gmail.com>
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif /* HAVE_CONFIG_H */
|
|
|
|
#include <stdio.h>
|
|
|
|
#if HAVE_STDLIB_H
|
|
#include <stdlib.h>
|
|
#endif /* HAVE_STDLIB_H */
|
|
|
|
#if HAVE_SYS_TYPES_H
|
|
#include <sys/types.h>
|
|
#endif /* HAVE_SYS_TYPES_H */
|
|
|
|
#if HAVE_LINUX_TYPES_H
|
|
#include <linux/types.h>
|
|
#endif /* HAVE_LINUX_TYPES_H */
|
|
|
|
#if HAVE_FCNTL_H
|
|
#include <fcntl.h>
|
|
#endif /* HAVE_FCNTL_H */
|
|
|
|
#if HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif /* HAVE_UNISTD_H */
|
|
|
|
#if HAVE_SYS_IOCTL_H
|
|
#include <sys/ioctl.h>
|
|
#endif /* HAVE_SYS_IOCTL_H */
|
|
|
|
#if HAVE_LIMITS_H
|
|
#include <limits.h>
|
|
#endif /* HAVE_LIMITS_H */
|
|
|
|
#if HAVE_STRING_H
|
|
#include <string.h>
|
|
#endif /* HAVE_SYS_STRING_H */
|
|
|
|
#if HAVE_SYSLOG_H
|
|
#include <syslog.h> /* LOG_ERR, and so forth */
|
|
#endif /* HAVE_SYSLOG_H */
|
|
|
|
#if HAVE_TIME_H
|
|
#include <time.h> /* timespec, nanosleep() */
|
|
#endif /* HAVE_TIME_H */
|
|
|
|
/* compat.h must be included before on-disk definitions for sparse checks */
|
|
#include "compat.h"
|
|
#include <linux/nilfs2_ondisk.h>
|
|
|
|
#include <sys/stat.h>
|
|
#include <assert.h>
|
|
#include <stdarg.h> /* va_start, va_end, vfprintf */
|
|
#include <errno.h>
|
|
#include <signal.h>
|
|
#include "nilfs.h"
|
|
#include "util.h"
|
|
#include "nilfs_gc.h"
|
|
#include "check_mount.h"
|
|
|
|
|
|
#ifdef _GNU_SOURCE
|
|
#include <getopt.h>
|
|
static const struct option long_option[] = {
|
|
{"help", no_argument, NULL, 'h'},
|
|
{"verbose", no_argument, NULL, 'v'},
|
|
{"yes", no_argument, NULL, 'y'},
|
|
{"assume-yes", no_argument, NULL, 'y'},
|
|
{"version", no_argument, NULL, 'V'},
|
|
{NULL, 0, NULL, 0}
|
|
};
|
|
#define NILFS_RESIZE_USAGE \
|
|
"Usage: %s [options] device [size]\n" \
|
|
" -h, --help\t\tdisplay this help and exit\n" \
|
|
" -v, --verbose\t\tverbose mode\n" \
|
|
" -y, --yes,--assume-yes\n" \
|
|
" \t\tAssume Yes to all queries and do not prompt\n" \
|
|
" -V, --version\t\tdisplay version and exit\n"
|
|
#else
|
|
#define NILFS_RESIZE_USAGE \
|
|
"Usage: %s [-h] [-v] [-y] [-V] device [size]\n"
|
|
#endif /* _GNU_SOURCE */
|
|
|
|
|
|
/* get device size through ioctl */
|
|
#ifndef BLKGETSIZE64
|
|
#define BLKGETSIZE64 _IOR(0x12, 114, size_t)
|
|
#endif
|
|
|
|
/* options */
|
|
static int show_version_only;
|
|
static int verbose;
|
|
static int assume_yes;
|
|
static int show_progress = 1;
|
|
|
|
/* global variables */
|
|
static uint64_t devsize;
|
|
static unsigned int sector_size = 512;
|
|
static struct nilfs_sustat sustat;
|
|
static uint64_t trunc_start, trunc_end;
|
|
|
|
#define NILFS_RESIZE_NSUINFO 256
|
|
#define NILFS_RESIZE_NSEGNUMS 256
|
|
|
|
static struct nilfs_suinfo suinfo[NILFS_RESIZE_NSUINFO];
|
|
static uint64_t segnums[NILFS_RESIZE_NSEGNUMS];
|
|
|
|
/* filesystem parameters */
|
|
static struct nilfs_layout layout;
|
|
|
|
/* cleaner parameter for shrink */
|
|
static int nsegments_per_clean = 2;
|
|
static struct timespec clean_interval = { 0, 100000000 }; /* 100 msec */
|
|
|
|
/* balloon file (file for forcing active segments to move) */
|
|
#define NILFS_RESIZE_BALLOON_FILENAME_FMT ".nilfs-balloon-%u"
|
|
#define NILFS_RESIZE_BALLOON_FILENAME_BUFSZ 32
|
|
|
|
#define NILFS_RESIZE_BALLOON_MAX_CHUNKSIZE 16392 /* chunk size (bytes) */
|
|
|
|
/* progress meter */
|
|
static int pm_width = 60;
|
|
static int pm_barwidth;
|
|
static int pm_labelwidth = 10;
|
|
static uint64_t pm_max;
|
|
static uint64_t pm_done;
|
|
static int pm_curpos;
|
|
static int pm_in_progress; /* 0: off, 1: on, -1: interrupted */
|
|
static const char *pm_label;
|
|
|
|
|
|
NILFS_UTILS_GITID();
|
|
|
|
/**
|
|
* nilfs_resize_logger - logger function to pass to libraries used
|
|
* @priority: log level
|
|
* @fmt: format string
|
|
* @...: variable arguments that provide values for the conversion
|
|
* descriptors in the format string
|
|
*
|
|
* This function provides a message output function specific to the resize
|
|
* command to relay and display message output from libraries.
|
|
*
|
|
* Here, messages with @priority greater than or equal to %LOG_ERR are
|
|
* redirected to standard error and a newline is inserted at the end of the
|
|
* message.
|
|
*/
|
|
static void nilfs_resize_logger(int priority, const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
|
|
if (priority > LOG_ERR)
|
|
return;
|
|
va_start(args, fmt);
|
|
vfprintf(stderr, fmt, args);
|
|
fprintf(stderr, "\n");
|
|
va_end(args);
|
|
}
|
|
|
|
/**
|
|
* nilfs_resize_progress_show - show progress bar on standard error output
|
|
*
|
|
* This function displays a progress bar on the standard error output showing
|
|
* the current progress and flushes the file descriptor.
|
|
*/
|
|
static void nilfs_resize_progress_show(void)
|
|
{
|
|
int i, len = strlen(pm_label);
|
|
int delta;
|
|
|
|
fwrite(pm_label, sizeof(char), min_t(int, len, pm_labelwidth), stderr);
|
|
fputs(" |", stderr);
|
|
|
|
for (i = 0; i < pm_curpos; i++)
|
|
fputc('*', stderr);
|
|
for (; i < pm_barwidth; i++)
|
|
fputc('-', stderr);
|
|
fputc('|', stderr);
|
|
|
|
delta = pm_barwidth + 1 - pm_curpos;
|
|
for (i = 0; i < delta; i++)
|
|
fputc('\b', stderr);
|
|
fflush(stderr);
|
|
}
|
|
|
|
/**
|
|
* nilfs_resize_progress_init - initialize the progress bar and display the
|
|
* initial bar
|
|
* @maxval: maximum progress value
|
|
* @label: label name displayed to the left of the progress bar
|
|
*
|
|
* This function initializes each variable representing the internal state
|
|
* of the progress bar, displays the progress bar in its initial state with
|
|
* a progress value of 0, and sets &pm_in_progress to one indicating that
|
|
* the progress bar is being displayed.
|
|
*/
|
|
static void nilfs_resize_progress_init(uint64_t maxval, const char *label)
|
|
{
|
|
pm_max = maxval;
|
|
pm_done = 0;
|
|
pm_curpos = 0;
|
|
pm_barwidth = max_t(int, pm_width - pm_labelwidth - 3, 10);
|
|
pm_label = label;
|
|
|
|
nilfs_resize_progress_show();
|
|
pm_in_progress = 1;
|
|
}
|
|
|
|
/**
|
|
* nilfs_resize_progress_update - update progress bar display
|
|
* @done: progress value
|
|
*
|
|
* This function does nothing if &pm_in_progress is off value (0).
|
|
* Otherwise, updates the progress bar with the progress value given by
|
|
* @done and sets the progress value in &pm_done and the updated cursor
|
|
* position in &pm_curpos.
|
|
* If the progress bar display is interrupted by other messages
|
|
* (&pm_in_progress < 0), it calls nilfs_resize_progress_show() to redisplay
|
|
* the entire progress bar. Changing the cursor position is done locally
|
|
* using combination of character output and the backspace character code.
|
|
*/
|
|
static void nilfs_resize_progress_update(uint64_t done)
|
|
{
|
|
int pos, delta;
|
|
int i;
|
|
|
|
if (!pm_in_progress)
|
|
return;
|
|
if (pm_in_progress < 0) {
|
|
/* resuming */
|
|
nilfs_resize_progress_show();
|
|
pm_in_progress = 1;
|
|
}
|
|
pos = pm_barwidth * min_t(uint64_t, done, pm_max) / pm_max;
|
|
|
|
if (pos > pm_curpos) {
|
|
delta = pos - pm_curpos;
|
|
for (i = 0; i < delta; i++)
|
|
fputc('*', stderr);
|
|
} else if (pos < pm_curpos) {
|
|
delta = pm_curpos - pos;
|
|
for (i = 0; i < delta; i++)
|
|
fputc('\b', stderr);
|
|
for (i = 0; i < delta; i++)
|
|
fputc('-', stderr);
|
|
for (i = 0; i < delta; i++)
|
|
fputc('\b', stderr);
|
|
}
|
|
fflush(stderr);
|
|
pm_curpos = pos;
|
|
pm_done = done;
|
|
}
|
|
|
|
/**
|
|
* nilfs_resize_progress_inc - increase/decrease progress
|
|
* @n: difference in progress (positive values are incremented, negative
|
|
* values are decremented)
|
|
*
|
|
* This function increases the progress by @n (decreases it if it is a
|
|
* negative value) and updates the progress bar display with
|
|
* nilfs_resize_progress_update().
|
|
*/
|
|
static void nilfs_resize_progress_inc(long n)
|
|
{
|
|
nilfs_resize_progress_update(pm_done + n);
|
|
}
|
|
|
|
/**
|
|
* nilfs_resize_progress_exit - stop updating progress bar
|
|
*
|
|
* This function does nothing if the progress bar is not in display mode.
|
|
* If not, it sets &pm_in_progress to display disabled state (0).
|
|
* If the progress bar is being updated and is not interrupted by other
|
|
* messages, it outputs a newline code to the standard error output to
|
|
* advance the line.
|
|
*/
|
|
static void nilfs_resize_progress_exit(void)
|
|
{
|
|
if (!pm_in_progress)
|
|
return;
|
|
if (pm_in_progress > 0)
|
|
fputc('\n', stderr);
|
|
pm_in_progress = 0;
|
|
}
|
|
|
|
/**
|
|
* msg - dedicated message output function (for coexistence with progress bar)
|
|
* @fmt: format string
|
|
* @...: variable arguments that provide values for the conversion
|
|
* descriptors in the format string
|
|
*
|
|
* This function is used to print a message concurrently with the progress
|
|
* bar. The message is relayed to standard error.
|
|
* If the progress bar is being updated, it outputs a new line code to
|
|
* change the line, and sets the progress bar status to "interrupted"
|
|
* (&pm_in_progress = -1).
|
|
*/
|
|
static void msg(const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
|
|
if (pm_in_progress > 0) {
|
|
fputc('\n', stderr);
|
|
pm_in_progress = -1;
|
|
}
|
|
|
|
va_start(args, fmt);
|
|
vfprintf(stderr, fmt, args);
|
|
va_end(args);
|
|
}
|
|
|
|
#define verbose_msg(fmt, ...) \
|
|
do { \
|
|
if (verbose) msg(fmt, ##__VA_ARGS__); \
|
|
} while (0)
|
|
|
|
#define errx(fmt, ...) \
|
|
msg("Error: " fmt "\n", ##__VA_ARGS__)
|
|
#define err(fmt, ...) \
|
|
msg("Error: " fmt ": %s\n", ##__VA_ARGS__, strerror(errno))
|
|
|
|
#define verbose_err(fmt, ...) \
|
|
do { \
|
|
if (verbose) err(fmt, ##__VA_ARGS__); \
|
|
} while (0)
|
|
|
|
#define ERR_INDENT " "
|
|
#define ERR_NEWLINE "\n" ERR_INDENT
|
|
|
|
/**
|
|
* nilfs_resize_load_layout - get nilfs layout information
|
|
* @nilfs: nilfs object
|
|
*
|
|
* This is a wrapper function for nilfs_get_layout() and stores layout
|
|
* information in the global variable &layout. This uses msg() to
|
|
* output an error message when errors occur.
|
|
*
|
|
* Return: 0 on success, -1 on failure.
|
|
*/
|
|
static int nilfs_resize_load_layout(const struct nilfs *nilfs)
|
|
{
|
|
ssize_t res;
|
|
|
|
res = nilfs_get_layout(nilfs, &layout, sizeof(layout));
|
|
if (unlikely(res < 0)) {
|
|
err("failed to get layout information");
|
|
return -1;
|
|
}
|
|
assert(res >= sizeof(layout));
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* nilfs_resize_update_sustat - update nilfs segment usage statistics
|
|
* @nilfs: nilfs object
|
|
*
|
|
* This is a wrapper function for nilfs_get_sustat() and stores segment
|
|
* usage statistics in the global variable &sustat. This uses msg()
|
|
* to output an error message when errors occur.
|
|
*
|
|
* Return: 0 on success, -1 on failure.
|
|
*/
|
|
static int nilfs_resize_update_sustat(struct nilfs *nilfs)
|
|
{
|
|
int ret;
|
|
|
|
ret = nilfs_get_sustat(nilfs, &sustat);
|
|
if (unlikely(ret < 0)) {
|
|
err("failed to get segment usage status");
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* nilfs_resize_lock_cleaner - block signals and lock cleaner
|
|
* @nilfs: nilfs object
|
|
* @sigset: place to store the old signal set
|
|
*
|
|
* This is a wrapper function for sigprocmask() and nilfs_lock_cleaner(),
|
|
* and uses msg() to output an error message when errors occur.
|
|
* On success, %SIGINT and %SIGTERM are blocked, and the cleaner is locked.
|
|
*
|
|
* Return: 0 on success, -1 on failure.
|
|
*/
|
|
static int nilfs_resize_lock_cleaner(struct nilfs *nilfs, sigset_t *sigset)
|
|
{
|
|
sigset_t newset;
|
|
int ret;
|
|
|
|
sigemptyset(&newset);
|
|
sigaddset(&newset, SIGINT);
|
|
sigaddset(&newset, SIGTERM);
|
|
ret = sigprocmask(SIG_BLOCK, &newset, sigset);
|
|
if (unlikely(ret < 0)) {
|
|
err("cannot block signals");
|
|
return -1;
|
|
}
|
|
|
|
ret = nilfs_lock_cleaner(nilfs);
|
|
if (unlikely(ret < 0)) {
|
|
err("failed to lock cleaner");
|
|
sigprocmask(SIG_SETMASK, sigset, NULL);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* nilfs_resize_unlock_cleaner - unlock cleaner and unblock signals
|
|
* @nilfs: nilfs object
|
|
* @sigset: place to store the original signal set
|
|
*/
|
|
static void nilfs_resize_unlock_cleaner(struct nilfs *nilfs,
|
|
const sigset_t *sigset)
|
|
{
|
|
nilfs_unlock_cleaner(nilfs);
|
|
sigprocmask(SIG_SETMASK, sigset, NULL);
|
|
}
|
|
|
|
/**
|
|
* nilfs_resize_calc_nrsvsegs - calculate the number of reserved segments
|
|
* @nsegs: number of segments
|
|
*/
|
|
static uint64_t nilfs_resize_calc_nrsvsegs(uint64_t nsegs)
|
|
{
|
|
return max_t(uint64_t, NILFS_MIN_NRSVSEGS,
|
|
DIV_ROUND_UP(nsegs * layout.reserved_segments_ratio, 100));
|
|
}
|
|
|
|
/**
|
|
* nilfs_resize_calc_size_of_segments - calculate the size of the area on the
|
|
* device where the segments are aligned
|
|
* @nsegs: number of segments
|
|
*
|
|
* Return: total byte count of segment area (including the area of the first
|
|
* super block at the beginning of the partition).
|
|
*/
|
|
static uint64_t nilfs_resize_calc_size_of_segments(uint64_t nsegs)
|
|
{
|
|
return (nsegs * layout.blocks_per_segment) << layout.blocksize_bits;
|
|
}
|
|
|
|
/**
|
|
* nilfs_resize_check_free_space - check if free space remains after resizing
|
|
* @nilfs: nilfs object
|
|
* @newnsegs: number of segments
|
|
*
|
|
* This function checks whether there is free space for at least the number
|
|
* of reserved segments when resizing to the number of segments specified by
|
|
* @newnsegs, and outputs an error message if not.
|
|
*
|
|
* In verbose mode, this function outputs information about the remaining
|
|
* space if there is enough space.
|
|
*
|
|
* Return: 0 if there is enough space, -1 otherwise.
|
|
*/
|
|
static int nilfs_resize_check_free_space(struct nilfs *nilfs, uint64_t newnsegs)
|
|
{
|
|
uint64_t nrsvsegs, nsegs, nbytes;
|
|
|
|
nrsvsegs = nilfs_resize_calc_nrsvsegs(newnsegs);
|
|
|
|
if (sustat.ss_ncleansegs < sustat.ss_nsegs - newnsegs + nrsvsegs) {
|
|
uint64_t nsegs, nbytes;
|
|
|
|
nsegs = (sustat.ss_nsegs - newnsegs) - sustat.ss_ncleansegs
|
|
+ nrsvsegs;
|
|
nbytes = nilfs_resize_calc_size_of_segments(newnsegs + nsegs) +
|
|
4096;
|
|
|
|
errx("Insufficient free space (needs %" PRIu64
|
|
" more segment%s)." ERR_NEWLINE
|
|
"The device size must be at least %" PRIu64 " bytes.",
|
|
nsegs, nsegs != 1 ? "s" : "", nbytes);
|
|
return -1;
|
|
} else if (verbose) {
|
|
nsegs = sustat.ss_ncleansegs - (sustat.ss_nsegs - newnsegs)
|
|
- nrsvsegs;
|
|
nbytes = nilfs_resize_calc_size_of_segments(nsegs);
|
|
msg("%" PRIu64 " free segment%s (%" PRIu64 " bytes) "
|
|
"will be left after shrinkage.\n",
|
|
nsegs, nsegs != 1 ? "s" : "", nbytes);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* nilfs_resize_restore_alloc_range - restore the allocatable range of segments
|
|
* @nilfs: nilfs object
|
|
*
|
|
* This function uses the saved original layout information to reset the
|
|
* range of allocatable segments back to before the start of shrinking.
|
|
*/
|
|
static void nilfs_resize_restore_alloc_range(struct nilfs *nilfs)
|
|
{
|
|
int ret;
|
|
|
|
ret = nilfs_resize_update_sustat(nilfs);
|
|
if (unlikely(ret < 0))
|
|
return;
|
|
nilfs_set_alloc_range(nilfs, 0, layout.devsize);
|
|
}
|
|
|
|
/**
|
|
* nilfs_resize_prod_fs - force the file system update to move active segments
|
|
* @nilfs: nilfs object
|
|
* @nblk_write: write data size (in blocks)
|
|
*
|
|
* This function creates a temporary file (balloon file) with a random pattern
|
|
* of size @nblk_write on the root directory that @nilfs holds, writes it to
|
|
* the log via nilfs_sync(), and then deletes it and also deletes checkpoints
|
|
* that contain the ballon file.
|
|
*
|
|
* Return: 0 on success, -1 on error.
|
|
*/
|
|
static int nilfs_resize_prod_fs(struct nilfs *nilfs, unsigned long nblk_write)
|
|
{
|
|
char filename[NILFS_RESIZE_BALLOON_FILENAME_BUFSZ];
|
|
ssize_t rest = nblk_write * layout.blocksize;
|
|
const char *dev = nilfs_get_dev(nilfs);
|
|
const char *srcdev = "/dev/urandom";
|
|
int dirfd = nilfs_get_root_fd(nilfs);
|
|
int out_fd = -1, in_fd = -1;
|
|
nilfs_cno_t cno, scno, ecno;
|
|
sigset_t newset, sigset;
|
|
int read_failed = 0;
|
|
int ret, res = -1;
|
|
unsigned char *data_buf;
|
|
|
|
if (!nblk_write)
|
|
return 0;
|
|
|
|
data_buf = malloc(NILFS_RESIZE_BALLOON_MAX_CHUNKSIZE);
|
|
if (unlikely(!data_buf))
|
|
return -1;
|
|
|
|
in_fd = open(srcdev, O_RDONLY | O_CLOEXEC);
|
|
if (in_fd < 0)
|
|
verbose_err("cannot open %s - use calculated values", srcdev);
|
|
|
|
ret = nilfs_sync(nilfs, &scno);
|
|
if (unlikely(ret < 0))
|
|
scno = 0;
|
|
|
|
/* Block signals */
|
|
sigemptyset(&newset);
|
|
sigaddset(&newset, SIGINT);
|
|
sigaddset(&newset, SIGTERM);
|
|
ret = sigprocmask(SIG_BLOCK, &newset, &sigset);
|
|
if (unlikely(ret < 0)) {
|
|
err("cannot block signals");
|
|
goto failed;
|
|
}
|
|
|
|
/* Write a balloon file to the filesystem */
|
|
snprintf(filename, NILFS_RESIZE_BALLOON_FILENAME_BUFSZ,
|
|
NILFS_RESIZE_BALLOON_FILENAME_FMT, (unsigned)getpid());
|
|
|
|
out_fd = openat(dirfd, filename, O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC,
|
|
S_IWUSR | S_IRUSR);
|
|
if (unlikely(out_fd < 0)) {
|
|
err("failed to create balloon file on %s", dev);
|
|
goto failed_unblock_signals;
|
|
}
|
|
|
|
while (rest > 0) {
|
|
size_t request = min_t(size_t, rest,
|
|
NILFS_RESIZE_BALLOON_MAX_CHUNKSIZE);
|
|
ssize_t count;
|
|
unsigned char *cp;
|
|
|
|
if (in_fd >= 0 && !read_failed) {
|
|
count = read(in_fd, data_buf, request);
|
|
if (likely(count > 0)) {
|
|
request = count;
|
|
goto inflate_balloon;
|
|
}
|
|
verbose_err("failed to read balloon data from %s", srcdev);
|
|
read_failed = 1;
|
|
}
|
|
/*
|
|
* Fallback path.
|
|
*
|
|
* Since cryptographically-secure random numbers are not required,
|
|
* use the old pseudo-random number function rand() instead of
|
|
* getrandom() or anything else that depends on the environment.
|
|
*/
|
|
for (cp = data_buf; cp < data_buf + request; cp++)
|
|
*cp = rand() & 0xff;
|
|
|
|
inflate_balloon:
|
|
count = write(out_fd, data_buf, request);
|
|
if (unlikely(count < 0)) {
|
|
err("failed to inflate balloon file on %s", dev);
|
|
break;
|
|
}
|
|
rest -= count;
|
|
}
|
|
|
|
ret = nilfs_sync(nilfs, &ecno);
|
|
if (unlikely(ret < 0))
|
|
ecno = 0;
|
|
close(out_fd);
|
|
|
|
/* Delete the balloon file */
|
|
ret = unlinkat(dirfd, filename, 0);
|
|
if (unlikely(ret < 0))
|
|
verbose_err("Balloon file deletion on %s failed", dev);
|
|
|
|
/* Delete checkpoints created during prodding */
|
|
if (likely(ecno > 0)) {
|
|
for (cno = (scno ? scno + 1 : ecno); cno <= ecno; cno++) {
|
|
nilfs_delete_checkpoint(nilfs, cno);
|
|
}
|
|
}
|
|
nilfs_sync(nilfs, &cno);
|
|
res = 0;
|
|
|
|
failed_unblock_signals:
|
|
sigprocmask(SIG_SETMASK, &sigset, NULL); /* Unblock signals */
|
|
|
|
nilfs_resize_update_sustat(nilfs);
|
|
failed:
|
|
if (in_fd >= 0)
|
|
close(in_fd);
|
|
free(data_buf);
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* nilfs_resize_find_movable_segments - find movable segments within a
|
|
* specified range
|
|
* @nilfs: nilfs object
|
|
* @start: starting segment number of search range (inclusive)
|
|
* @end: ending segment number of search range (inclusive)
|
|
* @segnumv: array of 64-bit integers that stores the discovered segment
|
|
* numbers
|
|
* @maxsegnums: maximum number of segments to search (maximum number of
|
|
* segment numbers that can be stored in @segnumv)
|
|
*
|
|
* This function searches for segments that are reclaimable and not
|
|
* protected by superblock log pointers within the range of the segment
|
|
* sequence specified by [@start, @end], and stores their numbers in
|
|
* @segnumv.
|
|
*
|
|
* Return: on success, the number of movable segments discovered, -1 on
|
|
* error.
|
|
*/
|
|
static ssize_t
|
|
nilfs_resize_find_movable_segments(struct nilfs *nilfs, uint64_t start,
|
|
uint64_t end, uint64_t *segnumv,
|
|
unsigned long maxsegnums)
|
|
{
|
|
uint64_t segnum, *snp;
|
|
unsigned long rest, count;
|
|
ssize_t nsi, i;
|
|
int ret;
|
|
|
|
assert(start <= end);
|
|
|
|
segnum = start;
|
|
rest = min_t(uint64_t, maxsegnums, end - start + 1);
|
|
for (snp = segnumv; rest > 0 && segnum <= end; ) {
|
|
count = min_t(unsigned long, rest, NILFS_RESIZE_NSUINFO);
|
|
nsi = nilfs_get_suinfo(nilfs, segnum, suinfo, count);
|
|
if (unlikely(nsi < 0)) {
|
|
err("operation failed during searching movable segments");
|
|
return -1;
|
|
}
|
|
for (i = 0; i < nsi; i++, segnum++) {
|
|
if (!nilfs_suinfo_reclaimable(&suinfo[i]))
|
|
continue;
|
|
|
|
if (nilfs_suinfo_empty(&suinfo[i])) {
|
|
/* Scrapped segments can be removed */
|
|
*snp++ = segnum;
|
|
rest--;
|
|
continue;
|
|
}
|
|
|
|
ret = nilfs_segment_is_protected(nilfs, segnum,
|
|
sustat.ss_prot_seq);
|
|
if (unlikely(ret < 0)) {
|
|
err("failed to read segment");
|
|
return -1;
|
|
}
|
|
if (ret)
|
|
continue;
|
|
*snp++ = segnum;
|
|
rest--;
|
|
}
|
|
}
|
|
return snp - segnumv; /* return the number of found segments */
|
|
}
|
|
|
|
#if 0
|
|
/**
|
|
* nilfs_resize_get_latest_segment - find latest segment within a specified
|
|
* range
|
|
* @nilfs: nilfs object
|
|
* @start: starting segment number of search range (inclusive)
|
|
* @end: ending segment number of search range (inclusive)
|
|
* @segnump place to store the number of the most recently updated
|
|
* segment
|
|
*
|
|
* This function searches for dirty (in-use) and non-error segments in
|
|
* the range specified by [@start, @end] of the segment sequence, and
|
|
* stores the number of the most recently updated segment in @segnump.
|
|
*
|
|
* Return: 1 on success, 0 if no segments were in use and not in error,
|
|
* -1 on error.
|
|
*/
|
|
static int
|
|
nilfs_resize_get_latest_segment(struct nilfs *nilfs, uint64_t start,
|
|
uint64_t end, uint64_t *segnump)
|
|
{
|
|
uint64_t segnum, latest_segnum = (~(uint64_t)0);
|
|
unsigned long count;
|
|
ssize_t nsi, i;
|
|
uint64_t latest_time = 0;
|
|
int ret = 0;
|
|
|
|
assert(start <= end);
|
|
|
|
for (segnum = start; segnum <= end; segnum += nsi) {
|
|
count = min_t(unsigned long, end - segnum + 1,
|
|
NILFS_RESIZE_NSUINFO);
|
|
nsi = nilfs_get_suinfo(nilfs, segnum, suinfo, count);
|
|
if (unlikely(nsi < 0)) {
|
|
err("operation failed during searching latest segment");
|
|
return -1;
|
|
}
|
|
assert(nsi > 0);
|
|
for (i = 0; i < nsi; i++) {
|
|
if (nilfs_suinfo_dirty(&suinfo[i]) &&
|
|
!nilfs_suinfo_error(&suinfo[i]) &&
|
|
suinfo[i].sui_lastmod > latest_time) {
|
|
latest_time = suinfo[i].sui_lastmod;
|
|
latest_segnum = segnum + i;
|
|
}
|
|
}
|
|
}
|
|
if (latest_time > 0) {
|
|
*segnump = latest_segnum;
|
|
ret = 1;
|
|
}
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* nilfs_resize_find_active_segments - find active (grabbed by log writer)
|
|
* segments within a specified range
|
|
* @nilfs: nilfs object
|
|
* @start: starting segment number of search range (inclusive)
|
|
* @end: ending segment number of search range (inclusive)
|
|
* @segnumv: array of 64-bit integers that stores the discovered segment
|
|
* numbers
|
|
* @maxsegnums: maximum number of segments to search (maximum number of
|
|
* segment numbers that can be stored in @segnumv)
|
|
* @nblocks: place to store the total number of used blocks in the found
|
|
* active segments (optional)
|
|
*
|
|
* This function searches for segments that are reclaimable and not
|
|
* protected by superblock log pointers within the range of the segment
|
|
* sequence specified by [@start, @end], and stores their numbers in
|
|
* @segnumv. Also, if @nblocks is not NULL, it stores the total number of
|
|
* used blocks included in the found active segments.
|
|
*
|
|
* Return: on success, the number of movable segments discovered, -1 on
|
|
* error.
|
|
*/
|
|
static ssize_t
|
|
nilfs_resize_find_active_segments(struct nilfs *nilfs, uint64_t start,
|
|
uint64_t end, uint64_t *segnumv,
|
|
unsigned long maxsegnums,
|
|
unsigned long *nblocks)
|
|
{
|
|
uint64_t segnum, *snp;
|
|
unsigned long rest, count, nb = 0;
|
|
ssize_t nsi, i;
|
|
|
|
assert(start <= end);
|
|
|
|
segnum = start;
|
|
rest = min_t(uint64_t, maxsegnums, end - start + 1);
|
|
for (snp = segnumv; rest > 0 && segnum <= end; ) {
|
|
count = min_t(unsigned long, rest, NILFS_RESIZE_NSUINFO);
|
|
nsi = nilfs_get_suinfo(nilfs, segnum, suinfo, count);
|
|
if (unlikely(nsi < 0)) {
|
|
err("operation failed during searching active segments");
|
|
return -1;
|
|
}
|
|
for (i = 0; i < nsi; i++, segnum++) {
|
|
if (nilfs_suinfo_active(&suinfo[i]) &&
|
|
!nilfs_suinfo_error(&suinfo[i])) {
|
|
*snp++ = segnum;
|
|
rest--;
|
|
nb += suinfo[i].sui_nblocks;
|
|
}
|
|
}
|
|
}
|
|
if (nblocks)
|
|
*nblocks = nb;
|
|
return snp - segnumv; /* return the number of found segments */
|
|
}
|
|
|
|
/**
|
|
* nilfs_resize_find_reclaimable_segments - find reclaimable segments within a
|
|
* specified range
|
|
* @nilfs: nilfs object
|
|
* @start: starting segment number of search range (inclusive)
|
|
* @end: ending segment number of search range (inclusive)
|
|
* @segnumv: array of 64-bit integers that stores the discovered segment
|
|
* numbers
|
|
* @maxsegnums: maximum number of segments to search (maximum number of
|
|
* segment numbers that can be stored in @segnumv)
|
|
*
|
|
* This function searches for reclaimable (dirty, non-error, and non-active)
|
|
* segments within the range of the segment sequence specified by
|
|
* [@start, @end], and stores their numbers in @segnumv.
|
|
*
|
|
* Return: on success, the number of reclaimable segments discovered, -1 on
|
|
* error.
|
|
*/
|
|
static ssize_t
|
|
nilfs_resize_find_reclaimable_segments(struct nilfs *nilfs, uint64_t start,
|
|
uint64_t end, uint64_t *segnumv,
|
|
unsigned long maxsegnums)
|
|
{
|
|
uint64_t segnum, *snp;
|
|
unsigned long rest, count;
|
|
ssize_t nsi, i;
|
|
|
|
assert(start <= end);
|
|
|
|
segnum = start;
|
|
rest = min_t(uint64_t, maxsegnums, end - start + 1);
|
|
for (snp = segnumv; rest > 0 && segnum <= end; ) {
|
|
count = min_t(unsigned long, rest, NILFS_RESIZE_NSUINFO);
|
|
nsi = nilfs_get_suinfo(nilfs, segnum, suinfo, count);
|
|
if (unlikely(nsi < 0)) {
|
|
err("operation failed during searching reclaimable segments");
|
|
return -1;
|
|
}
|
|
for (i = 0; i < nsi; i++, segnum++) {
|
|
if (nilfs_suinfo_reclaimable(&suinfo[i])) {
|
|
*snp++ = segnum;
|
|
rest--;
|
|
}
|
|
}
|
|
}
|
|
return snp - segnumv; /* return the number of found segments */
|
|
}
|
|
|
|
/**
|
|
* nilfs_resize_count_inuse_segments - count the number of in-use segments
|
|
* within a specified range
|
|
* @nilfs: nilfs object
|
|
* @start: starting segment number of search range (inclusive)
|
|
* @end: ending segment number of search range (inclusive)
|
|
*
|
|
* This function counts and returns the number of in-use (dirty) segments
|
|
* within the range of the segment sequence specified by [@start, @end].
|
|
*
|
|
* Return: number of in-use segments on success, -1 on error.
|
|
*/
|
|
static ssize_t
|
|
nilfs_resize_count_inuse_segments(struct nilfs *nilfs, uint64_t start,
|
|
uint64_t end)
|
|
{
|
|
uint64_t segnum;
|
|
unsigned long rest, count;
|
|
ssize_t nsi, i;
|
|
ssize_t nfound = 0;
|
|
|
|
assert(start <= end);
|
|
|
|
segnum = start;
|
|
rest = end - start + 1;
|
|
while (rest > 0 && segnum <= end) {
|
|
count = min_t(unsigned long, rest, NILFS_RESIZE_NSUINFO);
|
|
nsi = nilfs_get_suinfo(nilfs, segnum, suinfo, count);
|
|
if (unlikely(nsi < 0)) {
|
|
err("operation failed during counting in-use segments");
|
|
return -1;
|
|
}
|
|
for (i = 0; i < nsi; i++, segnum++) {
|
|
if (nilfs_suinfo_dirty(&suinfo[i])) {
|
|
nfound++;
|
|
rest--;
|
|
}
|
|
}
|
|
}
|
|
return nfound; /* return the number of found segments */
|
|
}
|
|
|
|
#define NILFS_RESIZE_SEGMENT_PROTECTED 0x01
|
|
#define NILFS_RESIZE_SEGMENT_UNRECLAIMABLE 0x02
|
|
|
|
/**
|
|
* nilfs_resize_verify_failure - check the cause of movement failure for a
|
|
* segment number array
|
|
* @nilfs: nilfs object
|
|
* @segnumv: array of segment numbers
|
|
* @nsegs: number of segment numbers stored in @segnumv
|
|
*
|
|
* This function examines the usage of each segment specified by the segment
|
|
* number array @segnumv, determines why they are not movable (unreclaimable
|
|
* and/or protected), and returns its summary.
|
|
*
|
|
* Return: OR value of the following reason bit flags.
|
|
* * %NILFS_RESIZE_SEGMENT_PROTECTED - Protected segments exist
|
|
* * %NILFS_RESIZE_SEGMENT_UNRECLAIMABLE - Unreclaimable segments exist
|
|
*/
|
|
static int nilfs_resize_verify_failure(struct nilfs *nilfs,
|
|
uint64_t *segnumv, unsigned long nsegs)
|
|
{
|
|
struct nilfs_suinfo si;
|
|
int reason = 0;
|
|
int i, ret;
|
|
|
|
for (i = 0; i < nsegs; i++) {
|
|
if (nilfs_get_suinfo(nilfs, segnumv[i], &si, 1) == 1) {
|
|
if (!nilfs_suinfo_reclaimable(&si))
|
|
reason |= NILFS_RESIZE_SEGMENT_UNRECLAIMABLE;
|
|
else if (nilfs_suinfo_empty(&si))
|
|
continue; /* Scrapped segment */
|
|
if (nilfs_suinfo_active(&si)) {
|
|
/*
|
|
* Active segments may not have been written
|
|
* either, so we determine them to be protected
|
|
* without reading the segment summary.
|
|
*/
|
|
reason |= NILFS_RESIZE_SEGMENT_PROTECTED;
|
|
continue;
|
|
}
|
|
}
|
|
ret = nilfs_segment_is_protected(nilfs, segnumv[i],
|
|
sustat.ss_prot_seq);
|
|
if (ret > 0)
|
|
reason |= NILFS_RESIZE_SEGMENT_PROTECTED;
|
|
}
|
|
return reason;
|
|
}
|
|
|
|
/**
|
|
* nilfs_resize_move_segments - attempt to move segments and find out why
|
|
* some segments cannot be moved
|
|
* @nilfs: nilfs object
|
|
* @segnumv: array of segment numbers
|
|
* @nsegs: number of segment numbers stored in @segnumv
|
|
* @reason: place to store the reason as an OR value of bit flags, if
|
|
* there are segments that failed to move
|
|
*
|
|
* This function attempts to move each segment specified by the segment
|
|
* number array @segnumv, and if a successfully moved (evicted) segment
|
|
* is in the area to be truncated, it calls nilfs_resize_progress_inc()
|
|
* to advance the displayed truncation progress by the number of successful
|
|
* segments.
|
|
*
|
|
* If there are segments that fail to be moved and @reason is not %NULL, it
|
|
* uses nilfs_resize_verify_failure() to check the reason for the failure
|
|
* and stores it in @reason as an OR value of the following bit flags:
|
|
* * %NILFS_RESIZE_SEGMENT_PROTECTED - Protected segments exist
|
|
* * %NILFS_RESIZE_SEGMENT_UNRECLAIMABLE - Unreclaimable segments exist
|
|
*
|
|
* Return: Number of segments moved on success, -1 on error.
|
|
*/
|
|
static ssize_t nilfs_resize_move_segments(struct nilfs *nilfs,
|
|
uint64_t *segnumv,
|
|
unsigned long nsegs, int *reason)
|
|
{
|
|
unsigned long rest = nsegs, nc;
|
|
uint64_t *snp = segnumv;
|
|
ssize_t nmoved = 0, i, nhits;
|
|
int rv = 0;
|
|
int ret;
|
|
|
|
for (snp = segnumv; rest > 0; snp += nc, rest -= nc) {
|
|
nc = min_t(unsigned long, rest, nsegments_per_clean);
|
|
|
|
ret = nilfs_resize_update_sustat(nilfs);
|
|
if (unlikely(ret < 0))
|
|
return -1;
|
|
|
|
ret = nilfs_reclaim_segment(nilfs, snp, nc,
|
|
sustat.ss_prot_seq, 0);
|
|
if (unlikely(ret < 0))
|
|
return -1;
|
|
|
|
nmoved += ret;
|
|
|
|
/* updating progress bar */
|
|
for (i = 0, nhits = 0; i < ret; i++) {
|
|
if (snp[i] >= trunc_start && snp[i] <= trunc_end)
|
|
nhits++;
|
|
}
|
|
if (nhits)
|
|
nilfs_resize_progress_inc(nhits);
|
|
|
|
/* check reason of gc failure */
|
|
if (ret < nc && reason)
|
|
rv |= nilfs_resize_verify_failure(nilfs, snp + ret,
|
|
nc - ret);
|
|
|
|
nanosleep(&clean_interval, NULL);
|
|
}
|
|
if (reason)
|
|
*reason = rv;
|
|
return nmoved;
|
|
}
|
|
|
|
static int __nilfs_resize_try_update_log_cursor(struct nilfs *nilfs)
|
|
{
|
|
nilfs_cno_t cno;
|
|
int ret = -1;
|
|
|
|
nilfs_sync(nilfs, &cno);
|
|
|
|
if (nilfs_freeze(nilfs) == 0 && nilfs_thaw(nilfs) == 0)
|
|
ret = 0;
|
|
|
|
nilfs_resize_update_sustat(nilfs);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* nilfs_resize_try_to_update_log_cursor - attempt to update log cursors
|
|
* @nilfs: nilfs object
|
|
* @reason: string representing the trigger for updating log cursors
|
|
*
|
|
* This function updates "log cursors", the starting points of segment
|
|
* chains pointed to by the two superblocks (the starting segments of log
|
|
* tracking during recovery mount). Updates are performed using sync,
|
|
* freeze and thaw ioctls, which are effective for converging these
|
|
* cursors.
|
|
* Then, to obtain the latest information on the segments protected by the
|
|
* log cursors, it updates the segment usage statistics &sustat by calling
|
|
* nilfs_resize_update_sustat().
|
|
*
|
|
* In verbose mode, this function outputs a message containing the reason
|
|
* for the execution before updating log cursors, and a message containing
|
|
* the result after updating them.
|
|
*
|
|
* Return: 0 on success, -1 on failure.
|
|
*/
|
|
static int nilfs_resize_try_update_log_cursor(struct nilfs *nilfs,
|
|
const char *reason)
|
|
{
|
|
int ret;
|
|
|
|
verbose_msg("%s.\nTrying to update log cursor ..", reason);
|
|
|
|
ret = __nilfs_resize_try_update_log_cursor(nilfs);
|
|
|
|
verbose_msg(ret == 0 ? " ok.\n" : " failed.\n");
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* nilfs_resize_reclaim_nibble - somehow move a specified number of segments
|
|
* within a specified range or in front of it
|
|
* @nilfs: nilfs object
|
|
* @start: starting segment number of the segment range to reclaim
|
|
* @end: ending segment number of the segment range to reclaim
|
|
* @count: number of segments to attempt to reclaim
|
|
* @unprotect: flag whether to attempt to unprotect protected segments by
|
|
* updating log cursors
|
|
*
|
|
* This function moves up to @count movable segments in the range specified
|
|
* by [@start, @end]. If there are not enough movable segments in the range,
|
|
* it tries to move movable segments in the [0, @start) range to force
|
|
* active or protected segments out of the area to be truncated.
|
|
* If no movable segments are found in both ranges, it attempts to update
|
|
* log cursors once.
|
|
*
|
|
* If the @unprotect flag is true and no attempt has been made to update
|
|
* log cursors, an attempt will be made even if the same or more segments
|
|
* have been moved.
|
|
*
|
|
* Return: 0 if the specified number of segments can be moved, -1 if not.
|
|
*/
|
|
static int nilfs_resize_reclaim_nibble(struct nilfs *nilfs,
|
|
unsigned long long start,
|
|
unsigned long long end,
|
|
unsigned long count, int unprotect)
|
|
{
|
|
uint64_t segnumv[2], segnum;
|
|
ssize_t nfound, nmoved = 0;
|
|
unsigned long nc;
|
|
unsigned long long end2 = end;
|
|
int log_cursor_updated = 0;
|
|
int prodded = 0;
|
|
int ret;
|
|
|
|
segnum = start;
|
|
|
|
retry:
|
|
while (segnum <= end) {
|
|
ssize_t nm;
|
|
|
|
nc = min_t(unsigned long, count - nmoved, ARRAY_SIZE(segnumv));
|
|
nfound = nilfs_resize_find_movable_segments(
|
|
nilfs, segnum, end, segnumv, nc);
|
|
if (unlikely(nfound < 0))
|
|
goto failed;
|
|
if (nfound == 0)
|
|
break;
|
|
|
|
segnum = segnumv[nfound - 1] + 1; /* set next segnum */
|
|
nm = nilfs_resize_move_segments(nilfs, segnumv, nfound, NULL);
|
|
if (unlikely(nm < 0))
|
|
goto failed;
|
|
|
|
nmoved += nm;
|
|
if (nmoved >= count) {
|
|
if (unprotect && !log_cursor_updated) {
|
|
nilfs_resize_try_update_log_cursor(
|
|
nilfs, "Hit protected segment");
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (end >= start) {
|
|
segnum = 0;
|
|
end = start - 1;
|
|
goto retry;
|
|
}
|
|
if (!log_cursor_updated) {
|
|
ret = nilfs_resize_try_update_log_cursor(
|
|
nilfs, "No movable segment");
|
|
if (ret < 0)
|
|
goto failed;
|
|
segnum = start;
|
|
end = end2;
|
|
log_cursor_updated = 1;
|
|
goto retry;
|
|
}
|
|
if (!prodded) {
|
|
unsigned long nblocks, max_blocks;
|
|
|
|
nfound = nilfs_resize_find_active_segments(
|
|
nilfs, start, end2, segnumv, 2, &nblocks);
|
|
if (unlikely(nfound < 0))
|
|
goto failed;
|
|
|
|
if (nfound > 0) {
|
|
max_blocks = layout.blocks_per_segment * 2;
|
|
assert(max_blocks > nblocks);
|
|
|
|
verbose_msg("Active segment%s blocking but there are "
|
|
"no movable segments.\n"
|
|
"Try forcing a filesystem update.\n",
|
|
nfound == 1 ? " is" : "s are");
|
|
ret = nilfs_resize_prod_fs(nilfs, max_blocks - nblocks);
|
|
if (unlikely(ret < 0)) {
|
|
err("forced filesystem update failed");
|
|
goto failed;
|
|
}
|
|
verbose_msg("Succeeded - retry moving segments.\n");
|
|
|
|
segnum = start;
|
|
end = end2;
|
|
log_cursor_updated = 0;
|
|
prodded = 1;
|
|
goto retry;
|
|
}
|
|
}
|
|
errx("couldn't move any segments.");
|
|
return -1;
|
|
failed:
|
|
err("operation failed during moving segments");
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* nilfs_resize_move_out_active_segments - kick active segments from
|
|
* specified range
|
|
* @nilfs: nilfs object
|
|
* @start: starting segment number of the range to be evicted
|
|
* @end: ending segment number of the range to be evicted
|
|
*
|
|
* This function kicks out active segments in the range specified by
|
|
* [@start, @end] by calling nilfs_resize_reclaim_nibble() with a count
|
|
* equal to that number. This active segment eviction will be attempted
|
|
* up to 5 times.
|
|
*
|
|
* Return: 0 if there is no active segment, 1 if the eviction was successful
|
|
* in at least one attempt, or -1 in case of an error or if eviction cannot
|
|
* be achieved.
|
|
*/
|
|
static int nilfs_resize_move_out_active_segments(struct nilfs *nilfs,
|
|
unsigned long long start,
|
|
unsigned long long end)
|
|
{
|
|
static const struct timespec retry_interval = { 0, 500000000 };
|
|
/* 500 msec */
|
|
ssize_t nfound;
|
|
int retrycnt = 0;
|
|
int ret;
|
|
|
|
while (1) {
|
|
ret = nilfs_resize_update_sustat(nilfs);
|
|
if (unlikely(ret < 0))
|
|
return -1;
|
|
|
|
nfound = nilfs_resize_find_active_segments(
|
|
nilfs, start, end, segnums, NILFS_RESIZE_NSEGNUMS, NULL);
|
|
if (unlikely(nfound < 0))
|
|
return -1;
|
|
if (!nfound)
|
|
break;
|
|
|
|
if (retrycnt >= 6) {
|
|
errx("Failed to move active segments -- give up.");
|
|
return -1;
|
|
}
|
|
if (!retrycnt)
|
|
verbose_msg("Active segments are found in the range.\n"
|
|
"Trying to move them.\n");
|
|
|
|
ret = nilfs_resize_reclaim_nibble(nilfs, start, end, nfound, 0);
|
|
if (unlikely(ret < 0))
|
|
return -1;
|
|
retrycnt++;
|
|
nanosleep(&retry_interval, NULL);
|
|
}
|
|
return retrycnt > 0;
|
|
}
|
|
|
|
/**
|
|
* nilfs_resize_reclaim_range - reclaim segments to shrink the file system
|
|
* to a specified number of segments
|
|
* @nilfs: nilfs object
|
|
* @newnsegs: target number of segments for shrink
|
|
*
|
|
* This function evicts in-use segments from the range that exceeds
|
|
* @newnsegs limit so that the used segment space stays below the @newnsegs
|
|
* limit.
|
|
*
|
|
* It first tries to evict active segments from outside the range, and if
|
|
* successful, reclaims the remaining reclaimable segments.
|
|
* If part of the eviction of reclaimable segments fails due to the
|
|
* presence of protected segments by log cursors, it attempts to remove
|
|
* them using nilfs_resize_reclaim_nibble().
|
|
*
|
|
* While the progress bar is being displayed, the progress is evaluated
|
|
* and the progress bar is updated between these operations.
|
|
*
|
|
* Return: 0 on success, -1 on failure.
|
|
*/
|
|
static int nilfs_resize_reclaim_range(struct nilfs *nilfs, uint64_t newnsegs)
|
|
{
|
|
unsigned long long start, end, segnum;
|
|
ssize_t nfound;
|
|
int ret;
|
|
|
|
ret = nilfs_resize_update_sustat(nilfs);
|
|
if (unlikely(ret < 0))
|
|
return -1;
|
|
|
|
if (newnsegs > sustat.ss_nsegs) {
|
|
errx("Confused. Number of segments became larger than requested size.");
|
|
return -1;
|
|
}
|
|
if (newnsegs == sustat.ss_nsegs)
|
|
return 0;
|
|
|
|
start = newnsegs;
|
|
end = sustat.ss_nsegs - 1;
|
|
|
|
/* Move out active segments first */
|
|
ret = nilfs_resize_move_out_active_segments(nilfs, start, end);
|
|
if (unlikely(ret < 0))
|
|
goto out;
|
|
if (ret && pm_in_progress) {
|
|
nfound = nilfs_resize_count_inuse_segments(nilfs, start, end);
|
|
if (nfound >= 0) {
|
|
if (nfound > pm_max)
|
|
pm_max = nfound;
|
|
nilfs_resize_progress_update(pm_max - nfound);
|
|
}
|
|
}
|
|
|
|
ret = -1;
|
|
segnum = start;
|
|
while (segnum <= end) {
|
|
ssize_t nfound;
|
|
ssize_t nmoved;
|
|
int reason = 0;
|
|
|
|
nfound = nilfs_resize_find_reclaimable_segments(
|
|
nilfs, segnum, end, segnums, NILFS_RESIZE_NSEGNUMS);
|
|
if (unlikely(nfound < 0))
|
|
goto out;
|
|
|
|
if (nfound == 0)
|
|
break;
|
|
|
|
/*
|
|
* Updates @segnum before calling nilfs_resize_move_segments()
|
|
* because segnums may be changed during reclamation.
|
|
*/
|
|
segnum = segnums[nfound - 1] + 1;
|
|
nmoved = nilfs_resize_move_segments(
|
|
nilfs, segnums, nfound, &reason);
|
|
if (unlikely(nmoved < 0)) {
|
|
err("operation failed during moving reclaimable segments");
|
|
goto out;
|
|
}
|
|
|
|
if (nmoved < nfound &&
|
|
(reason & NILFS_RESIZE_SEGMENT_PROTECTED)) {
|
|
nilfs_resize_reclaim_nibble(nilfs, start, end, 2, 1);
|
|
if (pm_in_progress) {
|
|
nfound = nilfs_resize_count_inuse_segments(
|
|
nilfs, start, end);
|
|
if (nfound >= 0) {
|
|
if (nfound > pm_max)
|
|
pm_max = nfound;
|
|
nilfs_resize_progress_update(
|
|
pm_max - nfound);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ret = 0;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* nilfs_print_resize_error - output error message when resize operation fails
|
|
* @ec: error number
|
|
* @shrink: expand or shrink flag (non-zero for shrink)
|
|
*/
|
|
static void nilfs_print_resize_error(int ec, int shrink)
|
|
{
|
|
err("failed to %s the filesystem", shrink ? "shrink" : "extend");
|
|
if (ec == ENOTTY)
|
|
msg(ERR_INDENT "This kernel does not support the resize API.\n");
|
|
}
|
|
|
|
/**
|
|
* nilfs_resize_prompt - output a confirmation prompt to confirm resizing,
|
|
* prompting the user to enter "y" (yes) or something
|
|
* else
|
|
* @newsize: target device size after resizing (in bytes)
|
|
*/
|
|
static int nilfs_resize_prompt(uint64_t newsize)
|
|
{
|
|
int c;
|
|
|
|
msg("Do you wish to proceed (y/N)? ");
|
|
return ((c = getchar()) == 'y' || c == 'Y') ? 0 : -1;
|
|
}
|
|
|
|
/**
|
|
* nilfs_shrink_online - shrink a mounted file system
|
|
* @nilfs: nilfs object
|
|
* @device: device pathname
|
|
* @newsize: target device size after resizing (in bytes)
|
|
*
|
|
* This function shrinks the file system so that the device size can be
|
|
* truncated to @newsize bytes.
|
|
*
|
|
* This function first calculates the maximum number of segments that can
|
|
* truncate the file system below the target byte size, taking into account
|
|
* the second superblock.
|
|
*
|
|
* Then, if it is possible to downsize from calculating the remaining
|
|
* capacity, it prompts to confirm shrinking unless the "yes" option is
|
|
* specified.
|
|
*
|
|
* If the user requests execution interactively (or the confirmation is
|
|
* skipped with the "yes" option), it first sets the allocatable area of
|
|
* new segments to [0, @newsize) by calling nilfs_set_alloc_range ioctl.
|
|
*
|
|
* Next, it counts the reclaimable segments (segments in use) in the area
|
|
* to be truncated and displays the initial progress bar.
|
|
*
|
|
* It then calls nilfs_resize_reclaim_range() to move the data in the used
|
|
* segments in the area to be truncated, and if successful, it locks
|
|
* the cleaner and calls nilfs_resize ioctl to instruct the file system
|
|
* to finalize the resize. If resize ioctl fails with error number %EBUSY,
|
|
* reclamation and resize finalization will be retried at most two more
|
|
* times.
|
|
*
|
|
* Return: %EXIT_SUCCESS on success, %EXIT_FAILURE on failure.
|
|
*/
|
|
static int nilfs_shrink_online(struct nilfs *nilfs, const char *device,
|
|
uint64_t newsize)
|
|
{
|
|
static char *label = "progress";
|
|
sigset_t sigset;
|
|
int status = EXIT_FAILURE;
|
|
unsigned long long newsb2off; /* new offset of secondary super block */
|
|
uint64_t newnsegs;
|
|
ssize_t nuses;
|
|
unsigned retry;
|
|
int ret;
|
|
|
|
/* set logger callback */
|
|
nilfs_gc_logger = nilfs_resize_logger;
|
|
|
|
ret = nilfs_resize_update_sustat(nilfs);
|
|
if (unlikely(ret < 0))
|
|
return -1;
|
|
|
|
msg("Partition size = %" PRIu64 " bytes.\n"
|
|
"Shrink the filesystem size from %" PRIu64 " bytes to %" PRIu64
|
|
" bytes.\n",
|
|
devsize, layout.devsize, newsize);
|
|
|
|
if (newsize >= 4096) {
|
|
newsb2off = NILFS_SB2_OFFSET_BYTES(newsize);
|
|
newnsegs = (newsb2off >> layout.blocksize_bits) /
|
|
layout.blocks_per_segment;
|
|
} else {
|
|
/*
|
|
* Set dummy parameters to make the size check below fail
|
|
* while avoiding underflow.
|
|
*/
|
|
newnsegs = 0;
|
|
}
|
|
|
|
ret = nilfs_resize_check_free_space(nilfs, newnsegs);
|
|
if (ret < 0)
|
|
goto out;
|
|
|
|
if (newnsegs < sustat.ss_nsegs) {
|
|
uint64_t truncsegs = sustat.ss_nsegs - newnsegs;
|
|
|
|
msg("%" PRIu64 " segment%s will be truncated from segnum %"
|
|
PRIu64 ".\n",
|
|
truncsegs, truncsegs != 1 ? "s" : "", newnsegs);
|
|
} else if (newnsegs == sustat.ss_nsegs) {
|
|
msg("No segments will be truncated.\n");
|
|
} else {
|
|
errx("Confused. Number of segments became larger than requested size:\n"
|
|
" old-nsegs=%" PRIu64 " new-nsegs=%" PRIu64,
|
|
(uint64_t)sustat.ss_nsegs, newnsegs);
|
|
goto out;
|
|
}
|
|
|
|
if (!assume_yes && nilfs_resize_prompt(newsize) < 0)
|
|
goto out;
|
|
|
|
ret = nilfs_set_alloc_range(nilfs, 0, newsize);
|
|
if (unlikely(ret < 0)) {
|
|
err("failed to limit allocation range");
|
|
if (errno == ENOTTY)
|
|
msg(ERR_INDENT "This kernel does not support the set allocation range API.\n");
|
|
goto out;
|
|
}
|
|
|
|
if (newnsegs < sustat.ss_nsegs) {
|
|
trunc_start = newnsegs;
|
|
trunc_end = sustat.ss_nsegs - 1;
|
|
|
|
nuses = nilfs_resize_count_inuse_segments(nilfs, newnsegs,
|
|
sustat.ss_nsegs - 1);
|
|
if (nuses > 0 && show_progress) {
|
|
msg("Moving %zd in-use segment%s.\n",
|
|
nuses, nuses > 1 ? "s" : "");
|
|
nilfs_resize_progress_init(nuses, label);
|
|
}
|
|
}
|
|
|
|
for (retry = 0; retry < 4; retry++) {
|
|
int ec;
|
|
|
|
/* shrinker retry loop */
|
|
ret = nilfs_resize_reclaim_range(nilfs, newnsegs);
|
|
if (unlikely(ret < 0))
|
|
goto restore_alloc_range;
|
|
|
|
ret = nilfs_resize_lock_cleaner(nilfs, &sigset);
|
|
if (unlikely(ret < 0))
|
|
goto restore_alloc_range;
|
|
|
|
verbose_msg("Truncating segments.\n");
|
|
|
|
ret = nilfs_resize(nilfs, newsize);
|
|
ec = errno;
|
|
|
|
nilfs_resize_unlock_cleaner(nilfs, &sigset);
|
|
if (likely(ret == 0)) {
|
|
status = EXIT_SUCCESS;
|
|
goto out;
|
|
}
|
|
|
|
if (ec != EBUSY) {
|
|
nilfs_print_resize_error(ec, 1);
|
|
goto restore_alloc_range;
|
|
}
|
|
|
|
ret = nilfs_resize_load_layout(nilfs);
|
|
if (unlikely(ret < 0))
|
|
goto restore_alloc_range;
|
|
|
|
ret = nilfs_resize_update_sustat(nilfs);
|
|
if (unlikely(ret < 0))
|
|
goto restore_alloc_range;
|
|
|
|
ret = nilfs_resize_check_free_space(nilfs, newnsegs);
|
|
if (ret < 0)
|
|
goto restore_alloc_range;
|
|
|
|
verbose_msg("In-use segment found again. will retry moving them.. (retry = %d)\n",
|
|
retry + 1);
|
|
}
|
|
errx("retries failed -- give up");
|
|
out:
|
|
nilfs_resize_progress_exit();
|
|
return status;
|
|
|
|
restore_alloc_range:
|
|
nilfs_resize_restore_alloc_range(nilfs);
|
|
goto out;
|
|
}
|
|
|
|
/**
|
|
* nilfs_extend_online - extend a mounted file system
|
|
* @nilfs: nilfs object
|
|
* @device: device pathname
|
|
* @newsize: target device size after resizing (in bytes)
|
|
*
|
|
* This function expands the file system to use up to @newsize bytes on the
|
|
* device.
|
|
*
|
|
* This function first prompts to confirm the expansion unless the "yes"
|
|
* option is specified.
|
|
*
|
|
* If the user requests execution interactively (or the confirmation is
|
|
* skipped with the "yes" option), it locks the cleaner and calls
|
|
* nilfs_resize ioctl to instruct the file system to finalize the resize.
|
|
*
|
|
* Return: %EXIT_SUCCESS on success, %EXIT_FAILURE on failure.
|
|
*/
|
|
static int nilfs_extend_online(struct nilfs *nilfs, const char *device,
|
|
uint64_t newsize)
|
|
{
|
|
int status = EXIT_FAILURE;
|
|
sigset_t sigset;
|
|
int ret;
|
|
|
|
msg("Partition size = %" PRIu64 " bytes.\n"
|
|
"Extend the filesystem size from %" PRIu64 " bytes to %" PRIu64
|
|
" bytes.\n",
|
|
devsize, layout.devsize, newsize);
|
|
if (!assume_yes && nilfs_resize_prompt(newsize) < 0)
|
|
goto out;
|
|
|
|
ret = nilfs_resize_lock_cleaner(nilfs, &sigset);
|
|
if (unlikely(ret < 0))
|
|
goto out;
|
|
|
|
ret = nilfs_resize(nilfs, newsize);
|
|
if (unlikely(ret < 0)) {
|
|
nilfs_print_resize_error(errno, 0);
|
|
goto out_unlock;
|
|
}
|
|
status = EXIT_SUCCESS;
|
|
|
|
out_unlock:
|
|
nilfs_resize_unlock_cleaner(nilfs, &sigset);
|
|
out:
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* nilfs_resize_online - resize a mounted file system
|
|
* @nilfs: nilfs object
|
|
* @device: device pathname
|
|
* @newsize: target device size after resizing (in bytes)
|
|
*
|
|
* This function resizes the file system so that the device size fits
|
|
* @newsize bytes.
|
|
*
|
|
* It first opens nilfs file system with nilfs_open() and reads its
|
|
* layout information. Then, if @newsize is larger than the current
|
|
* device size recognized by the file system, it calls
|
|
* nilfs_extend_online(), or if it is smaller than the current device size,
|
|
* calls nilfs_shrink_online(). The nilfs object is eventually closed using
|
|
* nilfs_close().
|
|
*
|
|
* Return: %EXIT_SUCCESS on success, %EXIT_FAILURE on failure.
|
|
*/
|
|
static int nilfs_resize_online(const char *device, uint64_t newsize)
|
|
{
|
|
struct nilfs *nilfs;
|
|
nilfs_cno_t cno;
|
|
int status = EXIT_FAILURE;
|
|
int ret;
|
|
|
|
nilfs = nilfs_open(device, NULL,
|
|
NILFS_OPEN_RAW | NILFS_OPEN_RDWR | NILFS_OPEN_GCLK);
|
|
if (nilfs == NULL) {
|
|
err("cannot open NILFS on %s", device);
|
|
goto out;
|
|
}
|
|
|
|
ret = nilfs_resize_load_layout(nilfs);
|
|
if (unlikely(ret < 0))
|
|
goto out_unlock;
|
|
|
|
if (newsize == layout.devsize) {
|
|
msg("No need to resize the filesystem on %s.\n"
|
|
"It already fits the device.\n", device);
|
|
status = EXIT_SUCCESS;
|
|
goto out_unlock;
|
|
}
|
|
|
|
nilfs_sync(nilfs, &cno);
|
|
|
|
if (newsize > layout.devsize)
|
|
status = nilfs_extend_online(nilfs, device, newsize);
|
|
else
|
|
status = nilfs_shrink_online(nilfs, device, newsize);
|
|
|
|
if (status == EXIT_SUCCESS)
|
|
msg("Done.\n");
|
|
else
|
|
msg("Aborted.\n");
|
|
|
|
out_unlock:
|
|
nilfs_close(nilfs);
|
|
out:
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* nilfs_resize_usage - show command usage
|
|
* @stream: destination file stream
|
|
*/
|
|
static void nilfs_resize_usage(FILE *stream)
|
|
{
|
|
fprintf(stream, NILFS_RESIZE_USAGE, getprogname());
|
|
}
|
|
|
|
/**
|
|
* nilfs_resize_parse_options - parse command options
|
|
* @argc: argument count of command line including command pathname
|
|
* @argv: argument vector
|
|
*/
|
|
static void nilfs_resize_parse_options(int argc, char *argv[])
|
|
{
|
|
#ifdef _GNU_SOURCE
|
|
int option_index;
|
|
#endif /* _GNU_SOURCE */
|
|
int c;
|
|
|
|
#ifdef _GNU_SOURCE
|
|
while ((c = getopt_long(argc, argv, "hvyMPV",
|
|
long_option, &option_index)) >= 0) {
|
|
#else
|
|
while ((c = getopt(argc, argv, "hvyMPV")) >= 0) {
|
|
#endif /* _GNU_SOURCE */
|
|
switch (c) {
|
|
case 'h':
|
|
nilfs_resize_usage(stdout);
|
|
exit(EXIT_SUCCESS);
|
|
break;
|
|
case 'v':
|
|
verbose = 1;
|
|
break;
|
|
case 'y':
|
|
assume_yes = 1;
|
|
break;
|
|
case 'V':
|
|
show_version_only = 1;
|
|
break;
|
|
default:
|
|
nilfs_resize_usage(stderr);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* nilfs_resize_parse_size - parse the size argument
|
|
* @arg: size argument string
|
|
* @sizep: place to store the size
|
|
*/
|
|
static int nilfs_resize_parse_size(const char *arg, uint64_t *sizep)
|
|
{
|
|
uint64_t size;
|
|
char *endptr;
|
|
|
|
assert(arg && *arg != '\0');
|
|
|
|
size = (uint64_t)strtoull(arg, &endptr, 0);
|
|
if (*endptr == '\0') {
|
|
;
|
|
} else if (endptr[1] == '\0') {
|
|
switch (endptr[0]) {
|
|
case 's':
|
|
size <<= 9;
|
|
break;
|
|
case 'K':
|
|
size <<= 10;
|
|
break;
|
|
case 'M':
|
|
size <<= 20;
|
|
break;
|
|
case 'G':
|
|
size <<= 30;
|
|
break;
|
|
case 'T':
|
|
size <<= 40;
|
|
break;
|
|
case 'P':
|
|
size <<= 50;
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
} else {
|
|
return -1;
|
|
}
|
|
*sizep = size;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* nilfs_resize_get_device_size - get the actual size of the device
|
|
* @device: device pathname
|
|
*/
|
|
static int nilfs_resize_get_device_size(const char *device)
|
|
{
|
|
int devfd, ret = -1;
|
|
|
|
devfd = open(device, O_RDONLY);
|
|
if (devfd < 0) {
|
|
err("cannot open device: %s.", device);
|
|
goto out;
|
|
}
|
|
|
|
ret = ioctl(devfd, BLKGETSIZE64, &devsize);
|
|
if (unlikely(ret != 0)) {
|
|
err("cannot get device size: %s.", device);
|
|
goto out_close_dev;
|
|
}
|
|
ret = 0;
|
|
|
|
out_close_dev:
|
|
close(devfd);
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* main - main function of nilfs-resize command
|
|
* @argc: argument count of command line including command pathname
|
|
* @argv: argument vector
|
|
*/
|
|
int main(int argc, char *argv[])
|
|
{
|
|
uint64_t size;
|
|
struct stat statbuf;
|
|
char *device;
|
|
int status, ret;
|
|
|
|
nilfs_resize_parse_options(argc, argv);
|
|
if (show_version_only) {
|
|
msg("%s version %s\n", getprogname(), PACKAGE_VERSION);
|
|
status = EXIT_SUCCESS;
|
|
goto out;
|
|
}
|
|
|
|
if (optind == argc) {
|
|
nilfs_resize_usage(stderr);
|
|
status = EXIT_FAILURE;
|
|
goto out;
|
|
}
|
|
|
|
status = EXIT_FAILURE;
|
|
|
|
device = argv[optind++];
|
|
if (stat(device, &statbuf) < 0) {
|
|
err("cannot find %s", device);
|
|
goto out;
|
|
} else if (!S_ISBLK(statbuf.st_mode)) {
|
|
errx("device must be a block device.");
|
|
goto out;
|
|
}
|
|
|
|
ret = nilfs_resize_get_device_size(device);
|
|
if (unlikely(ret < 0))
|
|
goto out;
|
|
|
|
if (optind != argc) {
|
|
ret = nilfs_resize_parse_size(argv[optind], &size);
|
|
if (unlikely(ret < 0)) {
|
|
errx("bad size argument: %s", argv[optind]);
|
|
goto out;
|
|
}
|
|
if (size > devsize) {
|
|
errx("size larger than partition size (%" PRIu64
|
|
" bytes).", devsize);
|
|
goto out;
|
|
}
|
|
|
|
optind++;
|
|
if (optind < argc) {
|
|
errx("too many arguments.");
|
|
goto out;
|
|
}
|
|
|
|
if (size & (sector_size - 1)) {
|
|
uint64_t size2;
|
|
|
|
size2 = size & ~(uint64_t)(sector_size - 1);
|
|
msg("size %" PRIu64 " is not aligned to sector size. "
|
|
"truncated to %" PRIu64 ".\n",
|
|
size, size2);
|
|
size = size2;
|
|
}
|
|
} else {
|
|
size = devsize;
|
|
}
|
|
|
|
ret = check_mount(device);
|
|
if (unlikely(ret < 0)) {
|
|
msg("Error checking mount status of %s: %s\n", device,
|
|
strerror(errno));
|
|
} else if (!ret) {
|
|
errx("%s is not currently mounted. Offline resizing"
|
|
ERR_NEWLINE "is not supported at present.", device);
|
|
} else {
|
|
status = nilfs_resize_online(device, size);
|
|
}
|
|
out:
|
|
exit(status);
|
|
}
|
|
|
|
|