| /* |
| * 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 "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 "handle.h" |
| #include "list.h" |
| #include "path.h" |
| #include "workqueue.h" |
| #include "xfs_scrub.h" |
| #include "common.h" |
| #include "inodes.h" |
| #include "progress.h" |
| #include "scrub.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. |
| */ |
| static bool |
| xfs_scrub_check_name( |
| struct scrub_ctx *ctx, |
| const char *descr, |
| 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, _("Zero length name found.")); |
| return true; |
| } |
| |
| /* 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); |
| return false; |
| } |
| str_info(ctx, descr, |
| _("Control character found in %s name \"%s\"."), |
| namedescr, errname); |
| free(errname); |
| } |
| |
| return true; |
| } |
| |
| /* |
| * Iterate a directory looking for filenames with problematic |
| * characters. |
| */ |
| static bool |
| xfs_scrub_scan_dirents( |
| struct scrub_ctx *ctx, |
| const char *descr, |
| int *fd, |
| struct xfs_bstat *bstat) |
| { |
| struct unicrash *uc = NULL; |
| DIR *dir; |
| struct dirent *dentry; |
| bool moveon = true; |
| |
| dir = fdopendir(*fd); |
| if (!dir) { |
| str_errno(ctx, descr); |
| goto out; |
| } |
| *fd = -1; /* closedir will close *fd for us */ |
| |
| moveon = unicrash_dir_init(&uc, ctx, bstat); |
| if (!moveon) |
| goto out_unicrash; |
| |
| dentry = readdir(dir); |
| while (dentry) { |
| if (uc) |
| moveon = unicrash_check_dir_name(uc, descr, dentry); |
| else |
| moveon = xfs_scrub_check_name(ctx, descr, |
| _("directory"), dentry->d_name); |
| if (!moveon) |
| break; |
| dentry = readdir(dir); |
| } |
| unicrash_free(uc); |
| |
| out_unicrash: |
| closedir(dir); |
| out: |
| return moveon; |
| } |
| |
| #ifdef HAVE_LIBATTR |
| /* Routines to scan all of an inode's xattrs for name problems. */ |
| struct xfs_attr_ns { |
| int flags; |
| const char *name; |
| }; |
| |
| static const struct xfs_attr_ns 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 bool |
| xfs_scrub_scan_fhandle_namespace_xattrs( |
| struct scrub_ctx *ctx, |
| const char *descr, |
| struct xfs_handle *handle, |
| struct xfs_bstat *bstat, |
| const struct xfs_attr_ns *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; |
| bool moveon = true; |
| int i; |
| int error; |
| |
| moveon = unicrash_xattr_init(&uc, ctx, bstat); |
| if (!moveon) |
| return false; |
| |
| 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) |
| moveon = unicrash_check_xattr_name(uc, descr, |
| keybuf); |
| else |
| moveon = xfs_scrub_check_name(ctx, descr, |
| _("extended attribute"), |
| keybuf); |
| if (!moveon) |
| 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 && errno != ESTALE) |
| str_errno(ctx, descr); |
| out: |
| unicrash_free(uc); |
| return moveon; |
| } |
| |
| /* |
| * Check all the xattr names in all the xattr namespaces for problematic |
| * characters. |
| */ |
| static bool |
| xfs_scrub_scan_fhandle_xattrs( |
| struct scrub_ctx *ctx, |
| const char *descr, |
| struct xfs_handle *handle, |
| struct xfs_bstat *bstat) |
| { |
| const struct xfs_attr_ns *ns; |
| bool moveon = true; |
| |
| for (ns = attr_ns; ns->name; ns++) { |
| moveon = xfs_scrub_scan_fhandle_namespace_xattrs(ctx, descr, |
| handle, bstat, ns); |
| if (!moveon) |
| break; |
| } |
| return moveon; |
| } |
| #else |
| # define xfs_scrub_scan_fhandle_xattrs(c, d, h, b) (true) |
| #endif /* HAVE_LIBATTR */ |
| |
| /* |
| * 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 |
| xfs_scrub_connections( |
| struct scrub_ctx *ctx, |
| struct xfs_handle *handle, |
| struct xfs_bstat *bstat, |
| void *arg) |
| { |
| bool *pmoveon = arg; |
| char descr[DESCR_BUFSZ]; |
| bool moveon = true; |
| xfs_agnumber_t agno; |
| xfs_agino_t agino; |
| int fd = -1; |
| int error; |
| |
| agno = bstat->bs_ino / (1ULL << (ctx->inopblog + ctx->agblklog)); |
| agino = bstat->bs_ino % (1ULL << (ctx->inopblog + ctx->agblklog)); |
| snprintf(descr, DESCR_BUFSZ, _("inode %"PRIu64" (%u/%u)"), |
| (uint64_t)bstat->bs_ino, agno, agino); |
| background_sleep(); |
| |
| /* Warn about naming problems in xattrs. */ |
| if (bstat->bs_xflags & FS_XFLAG_HASATTR) { |
| moveon = xfs_scrub_scan_fhandle_xattrs(ctx, descr, handle, |
| bstat); |
| if (!moveon) |
| goto out; |
| } |
| |
| /* Open the dir, let the kernel try to reconnect it to the root. */ |
| if (S_ISDIR(bstat->bs_mode)) { |
| fd = xfs_open_handle(handle); |
| if (fd < 0) { |
| if (errno == ESTALE) |
| return ESTALE; |
| str_errno(ctx, descr); |
| goto out; |
| } |
| } |
| |
| /* Warn about naming problems in the directory entries. */ |
| if (fd >= 0 && S_ISDIR(bstat->bs_mode)) { |
| moveon = xfs_scrub_scan_dirents(ctx, descr, &fd, bstat); |
| if (!moveon) |
| goto out; |
| } |
| |
| out: |
| progress_add(1); |
| if (fd >= 0) { |
| error = close(fd); |
| if (error) |
| str_errno(ctx, descr); |
| } |
| if (!moveon) |
| *pmoveon = false; |
| return *pmoveon ? 0 : XFS_ITERATE_INODES_ABORT; |
| } |
| |
| /* Check directory connectivity. */ |
| bool |
| xfs_scan_connections( |
| struct scrub_ctx *ctx) |
| { |
| bool moveon = true; |
| bool ret; |
| |
| if (ctx->errors_found) { |
| str_info(ctx, ctx->mntpoint, |
| _("Filesystem has errors, skipping connectivity checks.")); |
| return true; |
| } |
| |
| ret = xfs_scan_all_inodes(ctx, xfs_scrub_connections, &moveon); |
| if (!ret) |
| moveon = false; |
| if (!moveon) |
| return false; |
| xfs_scrub_report_preen_triggers(ctx); |
| return true; |
| } |