blob: 540b840dc6287d5b8dafede8707f311792309ea3 [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>
#ifdef HAVE_LIBATTR
# include <attr/attributes.h>
#endif
#include <linux/fs.h>
#include "handle.h"
#include "list.h"
#include "libfrog/paths.h"
#include "libfrog/workqueue.h"
#include "xfs_scrub.h"
#include "common.h"
#include "inodes.h"
#include "progress.h"
#include "scrub.h"
#include "descr.h"
#include "unicrash.h"
/* Phase 5: Check directory connectivity. */
/*
* Warn about problematic bytes in a directory/attribute name. That means
* terminal control characters and escape sequences, since that could be used
* to do something naughty to the user's computer and/or break scripts. XFS
* doesn't consider any byte sequence invalid, so don't flag these as errors.
*
* Returns 0 for success or -1 for error. This function logs errors.
*/
static int
simple_check_name(
struct scrub_ctx *ctx,
struct descr *dsc,
const char *namedescr,
const char *name)
{
const char *p;
bool bad = false;
char *errname;
/* Complain about zero length names. */
if (*name == '\0' && should_warn_about_name(ctx)) {
str_warn(ctx, descr_render(dsc), _("Zero length name found."));
return 0;
}
/* control characters */
for (p = name; *p; p++) {
if ((*p >= 1 && *p <= 31) || *p == 127) {
bad = true;
break;
}
}
if (bad && should_warn_about_name(ctx)) {
errname = string_escape(name);
if (!errname) {
str_errno(ctx, descr_render(dsc));
return -1;
}
str_info(ctx, descr_render(dsc),
_("Control character found in %s name \"%s\"."),
namedescr, errname);
free(errname);
}
return 0;
}
/*
* Iterate a directory looking for filenames with problematic
* characters.
*/
static int
check_dirent_names(
struct scrub_ctx *ctx,
struct descr *dsc,
int *fd,
struct xfs_bulkstat *bstat)
{
struct unicrash *uc = NULL;
DIR *dir;
struct dirent *dentry;
int ret;
dir = fdopendir(*fd);
if (!dir) {
str_errno(ctx, descr_render(dsc));
return errno;
}
*fd = -1; /* closedir will close *fd for us */
ret = unicrash_dir_init(&uc, ctx, bstat);
if (ret) {
str_liberror(ctx, ret, descr_render(dsc));
goto out_unicrash;
}
errno = 0;
dentry = readdir(dir);
while (dentry) {
if (uc)
ret = unicrash_check_dir_name(uc, dsc, dentry);
else
ret = simple_check_name(ctx, dsc, _("directory"),
dentry->d_name);
if (ret) {
str_liberror(ctx, ret, descr_render(dsc));
break;
}
errno = 0;
dentry = readdir(dir);
}
if (errno) {
ret = errno;
str_liberror(ctx, ret, descr_render(dsc));
}
unicrash_free(uc);
out_unicrash:
closedir(dir);
return ret;
}
#ifdef HAVE_LIBATTR
/* Routines to scan all of an inode's xattrs for name problems. */
struct attrns_decode {
int flags;
const char *name;
};
static const struct attrns_decode attr_ns[] = {
{0, "user"},
{ATTR_ROOT, "system"},
{ATTR_SECURE, "secure"},
{0, NULL},
};
/*
* Check all the xattr names in a particular namespace of a file handle
* for Unicode normalization problems or collisions.
*/
static int
check_xattr_ns_names(
struct scrub_ctx *ctx,
struct descr *dsc,
struct xfs_handle *handle,
struct xfs_bulkstat *bstat,
const struct attrns_decode *attr_ns)
{
struct attrlist_cursor cur;
char attrbuf[XFS_XATTR_LIST_MAX];
char keybuf[XATTR_NAME_MAX + 1];
struct attrlist *attrlist = (struct attrlist *)attrbuf;
struct attrlist_ent *ent;
struct unicrash *uc = NULL;
int i;
int error;
error = unicrash_xattr_init(&uc, ctx, bstat);
if (error) {
str_liberror(ctx, error, descr_render(dsc));
return error;
}
memset(attrbuf, 0, XFS_XATTR_LIST_MAX);
memset(&cur, 0, sizeof(cur));
memset(keybuf, 0, XATTR_NAME_MAX + 1);
error = attr_list_by_handle(handle, sizeof(*handle), attrbuf,
XFS_XATTR_LIST_MAX, attr_ns->flags, &cur);
while (!error) {
/* Examine the xattrs. */
for (i = 0; i < attrlist->al_count; i++) {
ent = ATTR_ENTRY(attrlist, i);
snprintf(keybuf, XATTR_NAME_MAX, "%s.%s", attr_ns->name,
ent->a_name);
if (uc)
error = unicrash_check_xattr_name(uc, dsc,
keybuf);
else
error = simple_check_name(ctx, dsc,
_("extended attribute"),
keybuf);
if (error) {
str_liberror(ctx, error, descr_render(dsc));
goto out;
}
}
if (!attrlist->al_more)
break;
error = attr_list_by_handle(handle, sizeof(*handle), attrbuf,
XFS_XATTR_LIST_MAX, attr_ns->flags, &cur);
}
if (error) {
if (errno == ESTALE)
errno = 0;
if (errno)
str_errno(ctx, descr_render(dsc));
}
out:
unicrash_free(uc);
return error;
}
/*
* Check all the xattr names in all the xattr namespaces for problematic
* characters.
*/
static int
check_xattr_names(
struct scrub_ctx *ctx,
struct descr *dsc,
struct xfs_handle *handle,
struct xfs_bulkstat *bstat)
{
const struct attrns_decode *ns;
int ret;
for (ns = attr_ns; ns->name; ns++) {
ret = check_xattr_ns_names(ctx, dsc, handle, bstat, ns);
if (ret)
break;
}
return ret;
}
#else
# define check_xattr_names(c, d, h, b) (0)
#endif /* HAVE_LIBATTR */
static int
render_ino_from_handle(
struct scrub_ctx *ctx,
char *buf,
size_t buflen,
void *data)
{
struct xfs_bstat *bstat = data;
return scrub_render_ino_descr(ctx, buf, buflen, bstat->bs_ino,
bstat->bs_gen, NULL);
}
/*
* Verify the connectivity of the directory tree.
* We know that the kernel's open-by-handle function will try to reconnect
* parents of an opened directory, so we'll accept that as sufficient.
*
* Check for potential Unicode collisions in names.
*/
static int
check_inode_names(
struct scrub_ctx *ctx,
struct xfs_handle *handle,
struct xfs_bulkstat *bstat,
void *arg)
{
DEFINE_DESCR(dsc, ctx, render_ino_from_handle);
bool *aborted = arg;
int fd = -1;
int error = 0;
int err2;
descr_set(&dsc, bstat);
background_sleep();
/* Warn about naming problems in xattrs. */
if (bstat->bs_xflags & FS_XFLAG_HASATTR) {
error = check_xattr_names(ctx, &dsc, handle, bstat);
if (error)
goto out;
}
/* Open the dir, let the kernel try to reconnect it to the root. */
if (S_ISDIR(bstat->bs_mode)) {
fd = scrub_open_handle(handle);
if (fd < 0) {
error = errno;
if (error == ESTALE)
return ESTALE;
str_errno(ctx, descr_render(&dsc));
goto out;
}
}
/* Warn about naming problems in the directory entries. */
if (fd >= 0 && S_ISDIR(bstat->bs_mode)) {
error = check_dirent_names(ctx, &dsc, &fd, bstat);
if (error)
goto out;
}
out:
progress_add(1);
if (fd >= 0) {
err2 = close(fd);
if (err2)
str_errno(ctx, descr_render(&dsc));
if (!error && err2)
error = err2;
}
if (error)
*aborted = true;
if (!error && *aborted)
error = ECANCELED;
return error;
}
#ifndef FS_IOC_GETFSLABEL
# define FSLABEL_MAX 256
# define FS_IOC_GETFSLABEL _IOR(0x94, 49, char[FSLABEL_MAX])
#endif /* FS_IOC_GETFSLABEL */
static int
scrub_render_mountpoint(
struct scrub_ctx *ctx,
char *buf,
size_t buflen,
void *data)
{
return snprintf(buf, buflen, _("%s"), ctx->mntpoint);
}
/*
* Check the filesystem label for Unicode normalization problems or misleading
* sequences.
*/
static int
check_fs_label(
struct scrub_ctx *ctx)
{
DEFINE_DESCR(dsc, ctx, scrub_render_mountpoint);
char label[FSLABEL_MAX];
struct unicrash *uc = NULL;
int error;
error = unicrash_fs_label_init(&uc, ctx);
if (error) {
str_liberror(ctx, error, descr_render(&dsc));
return error;
}
descr_set(&dsc, NULL);
/* Retrieve label; quietly bail if we don't support that. */
error = ioctl(ctx->mnt.fd, FS_IOC_GETFSLABEL, &label);
if (error) {
if (errno != EOPNOTSUPP && errno != ENOTTY) {
error = errno;
perror(ctx->mntpoint);
}
goto out;
}
/* Ignore empty labels. */
if (label[0] == 0)
goto out;
/* Otherwise check for weirdness. */
if (uc)
error = unicrash_check_fs_label(uc, &dsc, label);
else
error = simple_check_name(ctx, &dsc, _("filesystem label"),
label);
if (error)
str_liberror(ctx, error, descr_render(&dsc));
out:
unicrash_free(uc);
return error;
}
/* Check directory connectivity. */
int
phase5_func(
struct scrub_ctx *ctx)
{
bool aborted = false;
int ret;
if (ctx->corruptions_found || ctx->unfixable_errors) {
str_info(ctx, ctx->mntpoint,
_("Filesystem has errors, skipping connectivity checks."));
return 0;
}
ret = check_fs_label(ctx);
if (ret)
return ret;
ret = scrub_scan_all_inodes(ctx, check_inode_names, &aborted);
if (ret)
return ret;
if (aborted)
return ECANCELED;
xfs_scrub_report_preen_triggers(ctx);
return 0;
}
/* Estimate how much work we're going to do. */
int
phase5_estimate(
struct scrub_ctx *ctx,
uint64_t *items,
unsigned int *nr_threads,
int *rshift)
{
*items = ctx->mnt_sv.f_files - ctx->mnt_sv.f_ffree;
*nr_threads = scrub_nproc(ctx);
*rshift = 0;
return 0;
}