blob: 577eb6dc3e868729dd3cb92c732c1e7a8a78b1c8 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2018 Oracle. All Rights Reserved.
* Author: Darrick J. Wong <darrick.wong@oracle.com>
*/
#include "xfs.h"
#include <stdint.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/statvfs.h>
#include "handle.h"
#include "libfrog/paths.h"
#include "libfrog/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 aborted;
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;
};
static void scan_fs_dir(struct workqueue *wq, xfs_agnumber_t agno, void *arg);
/* Increment the number of directories that are queued for processing. */
static void
inc_nr_dirs(
struct scan_fs_tree *sft)
{
pthread_mutex_lock(&sft->lock);
sft->nr_dirs++;
pthread_mutex_unlock(&sft->lock);
}
/*
* Decrement the number of directories that are queued for processing and if
* we ran out of dirs to process, wake up anyone who was waiting for processing
* to finish.
*/
static void
dec_nr_dirs(
struct scan_fs_tree *sft)
{
pthread_mutex_lock(&sft->lock);
sft->nr_dirs--;
if (sft->nr_dirs == 0)
pthread_cond_signal(&sft->wakeup);
pthread_mutex_unlock(&sft->lock);
}
/* Queue a directory for scanning. */
static int
queue_subdir(
struct scrub_ctx *ctx,
struct scan_fs_tree *sft,
struct workqueue *wq,
const char *path,
bool is_rootdir)
{
struct scan_fs_tree_dir *new_sftd;
int error;
new_sftd = malloc(sizeof(struct scan_fs_tree_dir));
if (!new_sftd)
return errno;
new_sftd->path = strdup(path);
if (!new_sftd->path) {
error = errno;
goto out_sftd;
}
new_sftd->sft = sft;
new_sftd->rootdir = is_rootdir;
inc_nr_dirs(sft);
error = -workqueue_add(wq, scan_fs_dir, 0, new_sftd);
if (error) {
dec_nr_dirs(sft);
str_liberror(ctx, error, _("queueing directory scan work"));
goto out_path;
}
return 0;
out_path:
free(new_sftd->path);
out_sftd:
free(new_sftd);
return error;
}
/* 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 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. */
error = sft->dir_fn(ctx, sftd->path, dir_fd, sft->arg);
if (error) {
sft->aborted = true;
error = close(dir_fd);
if (error)
str_errno(ctx, sftd->path);
goto out;
}
/* Iterate the directory entries. */
dir = fdopendir(dir_fd);
if (!dir) {
str_errno(ctx, sftd->path);
sft->aborted = true;
close(dir_fd);
goto out;
}
rewinddir(dir);
for (dirent = readdir(dir);
!sft->aborted && 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. */
error = sft->dirent_fn(ctx, newpath, dir_fd, dirent, &sb,
sft->arg);
if (error) {
sft->aborted = true;
break;
}
if (scrub_excessive_errors(ctx)) {
sft->aborted = true;
break;
}
/* If directory, call ourselves recursively. */
if (S_ISDIR(sb.st_mode) && strcmp(".", dirent->d_name) &&
strcmp("..", dirent->d_name)) {
error = queue_subdir(ctx, sft, wq, newpath, false);
if (error) {
str_liberror(ctx, error,
_("queueing subdirectory scan"));
sft->aborted = true;
break;
}
}
}
/* Close dir, go away. */
error = closedir(dir);
if (error)
str_errno(ctx, sftd->path);
out:
dec_nr_dirs(sft);
free(sftd->path);
free(sftd);
}
/*
* Scan the entire filesystem. This function returns 0 on success; if there
* are errors, this function will log them and returns nonzero.
*/
int
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 = {
.root_sb = ctx->mnt_sb,
.dir_fn = dir_fn,
.dirent_fn = dirent_fn,
.arg = arg,
};
int ret;
ret = pthread_mutex_init(&sft.lock, NULL);
if (ret) {
str_liberror(ctx, ret, _("creating directory scan lock"));
return ret;
}
ret = pthread_cond_init(&sft.wakeup, NULL);
if (ret) {
str_liberror(ctx, ret, _("creating directory scan signal"));
goto out_mutex;
}
ret = -workqueue_create(&wq, (struct xfs_mount *)ctx,
scrub_nproc_workqueue(ctx));
if (ret) {
str_liberror(ctx, ret, _("creating directory scan workqueue"));
goto out_cond;
}
ret = queue_subdir(ctx, &sft, &wq, ctx->mntpoint, true);
if (ret) {
str_liberror(ctx, ret, _("queueing directory scan"));
goto out_wq;
}
/*
* Wait for the wakeup to trigger, which should only happen when the
* last worker thread decrements nr_dirs to zero. Once the worker
* triggers the wakeup and unlocks the sft lock, it's no longer safe
* for any worker thread to access sft, as we now own the lock and are
* about to tear everything down.
*/
pthread_mutex_lock(&sft.lock);
if (sft.nr_dirs)
pthread_cond_wait(&sft.wakeup, &sft.lock);
assert(sft.nr_dirs == 0);
pthread_mutex_unlock(&sft.lock);
ret = -workqueue_terminate(&wq);
if (ret) {
str_liberror(ctx, ret, _("finishing directory scan work"));
goto out_wq;
}
if (!ret && sft.aborted)
ret = -1;
out_wq:
workqueue_destroy(&wq);
out_cond:
pthread_cond_destroy(&sft.wakeup);
out_mutex:
pthread_mutex_destroy(&sft.lock);
return ret;
}
#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"));
}