blob: 0c5b35351cffcd35dd456e574751093c5c27e9a9 [file] [log] [blame]
/*
* Copyright (C) 2018 Oracle. All Rights Reserved.
*
* Author: Darrick J. Wong <darrick.wong@oracle.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it would be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/statvfs.h>
#include "xfs.h"
#include "handle.h"
#include "path.h"
#include "workqueue.h"
#include "xfs_scrub.h"
#include "common.h"
#include "vfs.h"
#ifndef AT_NO_AUTOMOUNT
# define AT_NO_AUTOMOUNT 0x800
#endif
/*
* Helper functions to assist in traversing a directory tree using regular
* VFS calls.
*/
/* Scan a filesystem tree. */
struct scan_fs_tree {
unsigned int nr_dirs;
pthread_mutex_t lock;
pthread_cond_t wakeup;
struct stat root_sb;
bool moveon;
scan_fs_tree_dir_fn dir_fn;
scan_fs_tree_dirent_fn dirent_fn;
void *arg;
};
/* Per-work-item scan context. */
struct scan_fs_tree_dir {
char *path;
struct scan_fs_tree *sft;
bool rootdir;
};
/* Scan a directory sub tree. */
static void
scan_fs_dir(
struct workqueue *wq,
xfs_agnumber_t agno,
void *arg)
{
struct scrub_ctx *ctx = (struct scrub_ctx *)wq->wq_ctx;
struct scan_fs_tree_dir *sftd = arg;
struct scan_fs_tree *sft = sftd->sft;
DIR *dir;
struct dirent *dirent;
char newpath[PATH_MAX];
struct scan_fs_tree_dir *new_sftd;
struct stat sb;
int dir_fd;
int error;
/* Open the directory. */
dir_fd = open(sftd->path, O_RDONLY | O_NOATIME | O_NOFOLLOW | O_NOCTTY);
if (dir_fd < 0) {
if (errno != ENOENT)
str_errno(ctx, sftd->path);
goto out;
}
/* Caller-specific directory checks. */
if (!sft->dir_fn(ctx, sftd->path, dir_fd, sft->arg)) {
sft->moveon = false;
close(dir_fd);
goto out;
}
/* Iterate the directory entries. */
dir = fdopendir(dir_fd);
if (!dir) {
str_errno(ctx, sftd->path);
close(dir_fd);
goto out;
}
rewinddir(dir);
for (dirent = readdir(dir); dirent != NULL; dirent = readdir(dir)) {
snprintf(newpath, PATH_MAX, "%s/%s", sftd->path,
dirent->d_name);
/* Get the stat info for this directory entry. */
error = fstatat(dir_fd, dirent->d_name, &sb,
AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW);
if (error) {
str_errno(ctx, newpath);
continue;
}
/* Ignore files on other filesystems. */
if (sb.st_dev != sft->root_sb.st_dev)
continue;
/* Caller-specific directory entry function. */
if (!sft->dirent_fn(ctx, newpath, dir_fd, dirent, &sb,
sft->arg)) {
sft->moveon = false;
break;
}
if (xfs_scrub_excessive_errors(ctx)) {
sft->moveon = false;
break;
}
/* If directory, call ourselves recursively. */
if (S_ISDIR(sb.st_mode) && strcmp(".", dirent->d_name) &&
strcmp("..", dirent->d_name)) {
new_sftd = malloc(sizeof(struct scan_fs_tree_dir));
if (!new_sftd) {
str_errno(ctx, newpath);
sft->moveon = false;
break;
}
new_sftd->path = strdup(newpath);
new_sftd->sft = sft;
new_sftd->rootdir = false;
pthread_mutex_lock(&sft->lock);
sft->nr_dirs++;
pthread_mutex_unlock(&sft->lock);
error = workqueue_add(wq, scan_fs_dir, 0, new_sftd);
if (error) {
str_info(ctx, ctx->mntpoint,
_("Could not queue subdirectory scan work."));
sft->moveon = false;
break;
}
}
}
/* Close dir, go away. */
error = closedir(dir);
if (error)
str_errno(ctx, sftd->path);
out:
pthread_mutex_lock(&sft->lock);
sft->nr_dirs--;
if (sft->nr_dirs == 0)
pthread_cond_signal(&sft->wakeup);
pthread_mutex_unlock(&sft->lock);
free(sftd->path);
free(sftd);
}
/* Scan the entire filesystem. */
bool
scan_fs_tree(
struct scrub_ctx *ctx,
scan_fs_tree_dir_fn dir_fn,
scan_fs_tree_dirent_fn dirent_fn,
void *arg)
{
struct workqueue wq;
struct scan_fs_tree sft;
struct scan_fs_tree_dir *sftd;
int ret;
sft.moveon = true;
sft.nr_dirs = 1;
sft.root_sb = ctx->mnt_sb;
sft.dir_fn = dir_fn;
sft.dirent_fn = dirent_fn;
sft.arg = arg;
pthread_mutex_init(&sft.lock, NULL);
pthread_cond_init(&sft.wakeup, NULL);
sftd = malloc(sizeof(struct scan_fs_tree_dir));
if (!sftd) {
str_errno(ctx, ctx->mntpoint);
return false;
}
sftd->path = strdup(ctx->mntpoint);
sftd->sft = &sft;
sftd->rootdir = true;
ret = workqueue_create(&wq, (struct xfs_mount *)ctx,
scrub_nproc_workqueue(ctx));
if (ret) {
str_info(ctx, ctx->mntpoint, _("Could not create workqueue."));
goto out_free;
}
ret = workqueue_add(&wq, scan_fs_dir, 0, sftd);
if (ret) {
str_info(ctx, ctx->mntpoint,
_("Could not queue directory scan work."));
goto out_free;
}
pthread_mutex_lock(&sft.lock);
pthread_cond_wait(&sft.wakeup, &sft.lock);
assert(sft.nr_dirs == 0);
pthread_mutex_unlock(&sft.lock);
workqueue_destroy(&wq);
return sft.moveon;
out_free:
free(sftd->path);
free(sftd);
return false;
}
#ifndef FITRIM
struct fstrim_range {
__u64 start;
__u64 len;
__u64 minlen;
};
#define FITRIM _IOWR('X', 121, struct fstrim_range) /* Trim */
#endif
/* Call FITRIM to trim all the unused space in a filesystem. */
void
fstrim(
struct scrub_ctx *ctx)
{
struct fstrim_range range = {0};
int error;
range.len = ULLONG_MAX;
error = ioctl(ctx->mnt_fd, FITRIM, &range);
if (error && errno != EOPNOTSUPP && errno != ENOTTY)
perror(_("fstrim"));
}