blob: d57ead4f165d809b466c8c5fb67274d3b13dee26 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2021 Oracle. All Rights Reserved.
* Author: Darrick J. Wong <djwong@kernel.org>
*/
#include "libxfs.h"
#include "command.h"
#include "output.h"
#include "init.h"
#include "io.h"
#include "type.h"
#include "input.h"
#include "faddr.h"
#include "fprint.h"
#include "field.h"
#include "inode.h"
/* Path lookup */
/* Key for looking up metadata inodes. */
struct dirpath {
/* Array of string pointers. */
char **path;
/* Number of strings in path. */
unsigned int depth;
};
static void
path_free(
struct dirpath *dirpath)
{
unsigned int i;
for (i = 0; i < dirpath->depth; i++)
free(dirpath->path[i]);
free(dirpath->path);
free(dirpath);
}
/* Chop a freeform string path into a structured path. */
static struct dirpath *
path_parse(
const char *path)
{
struct dirpath *dirpath;
const char *p = path;
const char *endp = path + strlen(path);
dirpath = calloc(sizeof(*dirpath), 1);
if (!dirpath)
return NULL;
while (p < endp) {
char **new_path;
const char *next_slash;
next_slash = strchr(p, '/');
if (next_slash == p) {
p++;
continue;
}
if (!next_slash)
next_slash = endp;
new_path = realloc(dirpath->path,
(dirpath->depth + 1) * sizeof(char *));
if (!new_path) {
path_free(dirpath);
return NULL;
}
dirpath->path = new_path;
dirpath->path[dirpath->depth] = strndup(p, next_slash - p);
dirpath->depth++;
p = next_slash + 1;
}
return dirpath;
}
/* Given a directory and a structured path, walk the path and set the cursor. */
static int
path_navigate(
struct xfs_mount *mp,
xfs_ino_t rootino,
struct dirpath *dirpath)
{
struct xfs_inode *dp;
xfs_ino_t ino = rootino;
unsigned int i;
int error;
error = -libxfs_iget(mp, NULL, ino, 0, &dp);
if (error)
return error;
for (i = 0; i < dirpath->depth; i++) {
struct xfs_name xname = {
.name = (unsigned char *)dirpath->path[i],
.len = strlen(dirpath->path[i]),
};
if (!S_ISDIR(VFS_I(dp)->i_mode)) {
error = ENOTDIR;
goto rele;
}
error = -libxfs_dir_lookup(NULL, dp, &xname, &ino, NULL);
if (error)
goto rele;
if (!xfs_verify_ino(mp, ino)) {
error = EFSCORRUPTED;
goto rele;
}
libxfs_irele(dp);
dp = NULL;
error = -libxfs_iget(mp, NULL, ino, 0, &dp);
switch (error) {
case EFSCORRUPTED:
case EFSBADCRC:
case 0:
break;
default:
return error;
}
}
set_cur_inode(ino);
rele:
if (dp)
libxfs_irele(dp);
return error;
}
/* Walk a directory path to an inode and set the io cursor to that inode. */
static int
path_walk(
char *path)
{
struct dirpath *dirpath;
char *p = path;
xfs_ino_t rootino = mp->m_sb.sb_rootino;
int error = 0;
if (*p == '/') {
/* Absolute path, start from the root inode. */
p++;
} else {
/* Relative path, start from current dir. */
if (iocur_top->typ != &typtab[TYP_INODE] ||
!S_ISDIR(iocur_top->mode))
return ENOTDIR;
rootino = iocur_top->ino;
}
dirpath = path_parse(p);
if (!dirpath)
return ENOMEM;
error = path_navigate(mp, rootino, dirpath);
path_free(dirpath);
return error;
}
static void
path_help(void)
{
dbprintf(_(
"\n"
" Navigate to an inode via directory path.\n"
));
}
static int
path_f(
int argc,
char **argv)
{
int c;
int error;
while ((c = getopt(argc, argv, "")) != -1) {
switch (c) {
default:
path_help();
return 0;
}
}
error = path_walk(argv[optind]);
if (error) {
dbprintf("%s: %s\n", argv[optind], strerror(error));
exitcode = 1;
}
return 0;
}
static struct cmdinfo path_cmd = {
.name = "path",
.altname = NULL,
.cfunc = path_f,
.argmin = 1,
.argmax = 1,
.canpush = 0,
.args = "",
.help = path_help,
};
/* List a directory's entries. */
static const char *filetype_strings[XFS_DIR3_FT_MAX] = {
[XFS_DIR3_FT_UNKNOWN] = "unknown",
[XFS_DIR3_FT_REG_FILE] = "regular",
[XFS_DIR3_FT_DIR] = "directory",
[XFS_DIR3_FT_CHRDEV] = "chardev",
[XFS_DIR3_FT_BLKDEV] = "blkdev",
[XFS_DIR3_FT_FIFO] = "fifo",
[XFS_DIR3_FT_SOCK] = "socket",
[XFS_DIR3_FT_SYMLINK] = "symlink",
[XFS_DIR3_FT_WHT] = "whiteout",
};
static const char *
get_dstr(
struct xfs_mount *mp,
uint8_t filetype)
{
if (!xfs_has_ftype(mp))
return filetype_strings[XFS_DIR3_FT_UNKNOWN];
if (filetype >= XFS_DIR3_FT_MAX)
return filetype_strings[XFS_DIR3_FT_UNKNOWN];
return filetype_strings[filetype];
}
static void
dir_emit(
struct xfs_mount *mp,
xfs_dir2_dataptr_t off,
char *name,
ssize_t namelen,
xfs_ino_t ino,
uint8_t dtype)
{
char *display_name;
struct xfs_name xname = { .name = (unsigned char *)name };
const char *dstr = get_dstr(mp, dtype);
xfs_dahash_t hash;
bool good;
if (namelen < 0) {
/* Negative length means that name is null-terminated. */
display_name = name;
xname.len = strlen(name);
good = true;
} else {
/*
* Otherwise, name came from a directory entry, so we have to
* copy the string to a buffer so that we can add the null
* terminator.
*/
display_name = malloc(namelen + 1);
memcpy(display_name, name, namelen);
display_name[namelen] = 0;
xname.len = namelen;
good = libxfs_dir2_namecheck(name, namelen);
}
hash = libxfs_dir2_hashname(mp, &xname);
dbprintf("%-10u %-18llu %-14s 0x%08llx %3d %s %s\n", off & 0xFFFFFFFF,
ino, dstr, hash, xname.len,
display_name, good ? _("(good)") : _("(corrupt)"));
if (display_name != name)
free(display_name);
}
static int
list_sfdir(
struct xfs_da_args *args)
{
struct xfs_inode *dp = args->dp;
struct xfs_mount *mp = dp->i_mount;
struct xfs_da_geometry *geo = args->geo;
struct xfs_dir2_sf_entry *sfep;
struct xfs_dir2_sf_hdr *sfp = dp->i_df.if_data;
xfs_ino_t ino;
xfs_dir2_dataptr_t off;
unsigned int i;
uint8_t filetype;
/* . and .. entries */
off = xfs_dir2_db_off_to_dataptr(geo, geo->datablk,
geo->data_entry_offset);
dir_emit(args->dp->i_mount, off, ".", -1, dp->i_ino, XFS_DIR3_FT_DIR);
ino = libxfs_dir2_sf_get_parent_ino(sfp);
off = xfs_dir2_db_off_to_dataptr(geo, geo->datablk,
geo->data_entry_offset +
libxfs_dir2_data_entsize(mp, sizeof(".") - 1));
dir_emit(args->dp->i_mount, off, "..", -1, ino, XFS_DIR3_FT_DIR);
/* Walk everything else. */
sfep = xfs_dir2_sf_firstentry(sfp);
for (i = 0; i < sfp->count; i++) {
ino = libxfs_dir2_sf_get_ino(mp, sfp, sfep);
filetype = libxfs_dir2_sf_get_ftype(mp, sfep);
off = xfs_dir2_db_off_to_dataptr(geo, geo->datablk,
xfs_dir2_sf_get_offset(sfep));
dir_emit(args->dp->i_mount, off, (char *)sfep->name,
sfep->namelen, ino, filetype);
sfep = libxfs_dir2_sf_nextentry(mp, sfp, sfep);
}
return 0;
}
/* List entries in block format directory. */
static int
list_blockdir(
struct xfs_da_args *args)
{
struct xfs_inode *dp = args->dp;
struct xfs_mount *mp = dp->i_mount;
struct xfs_buf *bp;
struct xfs_da_geometry *geo = mp->m_dir_geo;
xfs_dir2_dataptr_t diroff;
unsigned int offset;
unsigned int end;
int error;
error = xfs_dir3_block_read(NULL, dp, args->owner, &bp);
if (error)
return error;
end = xfs_dir3_data_end_offset(geo, bp->b_addr);
for (offset = geo->data_entry_offset; offset < end;) {
struct xfs_dir2_data_unused *dup = bp->b_addr + offset;
struct xfs_dir2_data_entry *dep = bp->b_addr + offset;
uint8_t filetype;
if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) {
/* Unused entry */
offset += be16_to_cpu(dup->length);
continue;
}
/* Real entry */
diroff = xfs_dir2_db_off_to_dataptr(geo, geo->datablk, offset);
offset += libxfs_dir2_data_entsize(mp, dep->namelen);
filetype = libxfs_dir2_data_get_ftype(dp->i_mount, dep);
dir_emit(mp, diroff, (char *)dep->name, dep->namelen,
be64_to_cpu(dep->inumber), filetype);
}
libxfs_trans_brelse(args->trans, bp);
return error;
}
/* List entries in leaf format directory. */
static int
list_leafdir(
struct xfs_da_args *args)
{
struct xfs_bmbt_irec map;
struct xfs_iext_cursor icur;
struct xfs_inode *dp = args->dp;
struct xfs_mount *mp = dp->i_mount;
struct xfs_buf *bp = NULL;
struct xfs_ifork *ifp = xfs_ifork_ptr(dp, XFS_DATA_FORK);
struct xfs_da_geometry *geo = mp->m_dir_geo;
xfs_dir2_off_t dirboff;
xfs_dablk_t dabno = 0;
int error = 0;
/* Read extent map. */
error = -libxfs_iread_extents(NULL, dp, XFS_DATA_FORK);
if (error)
return error;
while (dabno < geo->leafblk) {
unsigned int offset;
unsigned int length;
/* Find mapping for leaf block. */
if (!xfs_iext_lookup_extent(dp, ifp, dabno, &icur, &map))
break;
if (map.br_startoff >= geo->leafblk)
break;
libxfs_trim_extent(&map, dabno, geo->leafblk - dabno);
/* Read the directory block of that first mapping. */
error = xfs_dir3_data_read(NULL, dp, args->owner,
map.br_startoff, 0, &bp);
if (error)
break;
dirboff = xfs_dir2_da_to_byte(geo, map.br_startoff);
for (offset = geo->data_entry_offset; offset < geo->blksize;) {
struct xfs_dir2_data_entry *dep;
struct xfs_dir2_data_unused *dup;
uint8_t filetype;
dup = bp->b_addr + offset;
dep = bp->b_addr + offset;
if (be16_to_cpu(dup->freetag) ==
XFS_DIR2_DATA_FREE_TAG) {
/* Skip unused entry */
length = be16_to_cpu(dup->length);
offset += length;
continue;
}
offset += libxfs_dir2_data_entsize(mp, dep->namelen);
filetype = libxfs_dir2_data_get_ftype(mp, dep);
dir_emit(mp, xfs_dir2_byte_to_dataptr(dirboff + offset),
(char *)dep->name, dep->namelen,
be64_to_cpu(dep->inumber), filetype);
}
dabno += XFS_DADDR_TO_FSB(mp, bp->b_length);
libxfs_buf_relse(bp);
bp = NULL;
}
if (bp)
libxfs_buf_relse(bp);
return error;
}
/* Read the directory, display contents. */
static int
listdir(
struct xfs_inode *dp)
{
struct xfs_da_args args = {
.dp = dp,
.geo = dp->i_mount->m_dir_geo,
.owner = dp->i_ino,
};
int error;
switch (libxfs_dir2_format(&args, &error)) {
case XFS_DIR2_FMT_SF:
return list_sfdir(&args);
case XFS_DIR2_FMT_BLOCK:
return list_blockdir(&args);
case XFS_DIR2_FMT_LEAF:
case XFS_DIR2_FMT_NODE:
return list_leafdir(&args);
default:
return error;
}
}
/* List the inode number of the currently selected inode. */
static int
inum_cur(void)
{
if (iocur_top->typ != &typtab[TYP_INODE])
return ENOENT;
dbprintf("%llu\n", iocur_top->ino);
return 0;
}
/* If the io cursor points to a directory, list its contents. */
static int
ls_cur(
char *tag)
{
struct xfs_inode *dp;
int error = 0;
if (iocur_top->typ != &typtab[TYP_INODE] ||
!S_ISDIR(iocur_top->mode))
return ENOTDIR;
error = -libxfs_iget(mp, NULL, iocur_top->ino, 0, &dp);
if (error)
return error;
if (!S_ISDIR(VFS_I(dp)->i_mode)) {
error = ENOTDIR;
goto rele;
}
/* List the contents of a directory. */
if (tag)
dbprintf(_("%s:\n"), tag);
error = listdir(dp);
if (error)
goto rele;
rele:
libxfs_irele(dp);
return error;
}
static void
ls_help(void)
{
dbprintf(_(
"\n"
" List the contents of the currently selected directory inode.\n"
"\n"
" Options:\n"
" -i -- Resolve the given paths to their corresponding inode numbers.\n"
" If no paths are given, display the current inode number.\n"
"\n"
" Directory contents will be listed in the format:\n"
" dir_cookie inode_number type hash name_length name\n"
));
}
static int
ls_f(
int argc,
char **argv)
{
bool inum_only = false;
int c;
int error = 0;
while ((c = getopt(argc, argv, "i")) != -1) {
switch (c) {
case 'i':
inum_only = true;
break;
default:
ls_help();
return 0;
}
}
if (optind == argc) {
if (inum_only)
error = inum_cur();
else
error = ls_cur(NULL);
if (error) {
dbprintf("%s\n", strerror(error));
exitcode = 1;
}
return 0;
}
for (c = optind; c < argc; c++) {
push_cur();
error = path_walk(argv[c]);
if (error)
goto err_cur;
if (inum_only)
error = inum_cur();
else
error = ls_cur(argv[c]);
if (error)
goto err_cur;
pop_cur();
}
return 0;
err_cur:
pop_cur();
if (error) {
dbprintf("%s: %s\n", argv[c], strerror(error));
exitcode = 1;
}
return 0;
}
static struct cmdinfo ls_cmd = {
.name = "ls",
.altname = "l",
.cfunc = ls_f,
.argmin = 0,
.argmax = -1,
.canpush = 0,
.args = "[-i] [paths...]",
.help = ls_help,
};
static void
pptr_emit(
struct xfs_inode *ip,
unsigned int attr_flags,
const uint8_t *name,
unsigned int namelen,
const void *value,
unsigned int valuelen)
{
struct xfs_mount *mp = ip->i_mount;
xfs_ino_t parent_ino;
uint32_t parent_gen;
int error;
if (!(attr_flags & XFS_ATTR_PARENT))
return;
error = -libxfs_parent_from_attr(mp, attr_flags, name, namelen, value,
valuelen, &parent_ino, &parent_gen);
if (error)
return;
dbprintf("%18llu:0x%08x %3d %.*s\n", parent_ino, parent_gen, namelen,
namelen, name);
}
static int
list_sf_pptrs(
struct xfs_inode *ip)
{
struct xfs_attr_sf_hdr *hdr = ip->i_af.if_data;
struct xfs_attr_sf_entry *sfe;
unsigned int i;
sfe = libxfs_attr_sf_firstentry(hdr);
for (i = 0; i < hdr->count; i++) {
pptr_emit(ip, sfe->flags, sfe->nameval, sfe->namelen,
sfe->nameval + sfe->valuelen, sfe->valuelen);
sfe = xfs_attr_sf_nextentry(sfe);
}
return 0;
}
static void
list_leaf_pptr_entries(
struct xfs_inode *ip,
struct xfs_buf *bp)
{
struct xfs_attr3_icleaf_hdr ichdr;
struct xfs_mount *mp = ip->i_mount;
struct xfs_attr_leafblock *leaf = bp->b_addr;
struct xfs_attr_leaf_entry *entry;
unsigned int i;
libxfs_attr3_leaf_hdr_from_disk(mp->m_attr_geo, &ichdr, leaf);
entry = xfs_attr3_leaf_entryp(leaf);
for (i = 0; i < ichdr.count; entry++, i++) {
struct xfs_attr_leaf_name_local *name_loc;
/*
* Parent pointers cannot be remote values; don't bother
* decoding this xattr name.
*/
if (!(entry->flags & XFS_ATTR_LOCAL))
continue;
name_loc = xfs_attr3_leaf_name_local(leaf, i);
pptr_emit(ip, entry->flags, name_loc->nameval,
name_loc->namelen,
name_loc->nameval + name_loc->namelen,
be16_to_cpu(name_loc->valuelen));
}
}
static int
list_leaf_pptrs(
struct xfs_inode *ip)
{
struct xfs_buf *leaf_bp;
int error;
error = -libxfs_attr3_leaf_read(NULL, ip, ip->i_ino, 0, &leaf_bp);
if (error)
return error;
list_leaf_pptr_entries(ip, leaf_bp);
libxfs_trans_brelse(NULL, leaf_bp);
return 0;
}
static int
find_leftmost_attr_leaf(
struct xfs_inode *ip,
struct xfs_buf **leaf_bpp)
{
struct xfs_da3_icnode_hdr nodehdr;
struct xfs_mount *mp = ip->i_mount;
struct xfs_da_intnode *node;
struct xfs_da_node_entry *btree;
struct xfs_buf *bp;
xfs_dablk_t blkno = 0;
unsigned int expected_level = 0;
int error;
for (;;) {
uint16_t magic;
error = -libxfs_da3_node_read(NULL, ip, blkno, &bp,
XFS_ATTR_FORK);
if (error)
return error;
node = bp->b_addr;
magic = be16_to_cpu(node->hdr.info.magic);
if (magic == XFS_ATTR_LEAF_MAGIC ||
magic == XFS_ATTR3_LEAF_MAGIC)
break;
error = EFSCORRUPTED;
if (magic != XFS_DA_NODE_MAGIC &&
magic != XFS_DA3_NODE_MAGIC)
goto out_buf;
libxfs_da3_node_hdr_from_disk(mp, &nodehdr, node);
if (nodehdr.count == 0 || nodehdr.level >= XFS_DA_NODE_MAXDEPTH)
goto out_buf;
/* Check the level from the root node. */
if (blkno == 0)
expected_level = nodehdr.level - 1;
else if (expected_level != nodehdr.level)
goto out_buf;
else
expected_level--;
/* Find the next level towards the leaves of the dabtree. */
btree = nodehdr.btree;
blkno = be32_to_cpu(btree->before);
libxfs_trans_brelse(NULL, bp);
}
error = EFSCORRUPTED;
if (expected_level != 0)
goto out_buf;
*leaf_bpp = bp;
return 0;
out_buf:
libxfs_trans_brelse(NULL, bp);
return error;
}
static int
list_node_pptrs(
struct xfs_inode *ip)
{
struct xfs_attr3_icleaf_hdr leafhdr;
struct xfs_mount *mp = ip->i_mount;
struct xfs_attr_leafblock *leaf;
struct xfs_buf *leaf_bp;
int error;
error = find_leftmost_attr_leaf(ip, &leaf_bp);
if (error)
return error;
for (;;) {
list_leaf_pptr_entries(ip, leaf_bp);
/* Find the right sibling of this leaf block. */
leaf = leaf_bp->b_addr;
libxfs_attr3_leaf_hdr_from_disk(mp->m_attr_geo, &leafhdr, leaf);
if (leafhdr.forw == 0)
goto out_leaf;
libxfs_trans_brelse(NULL, leaf_bp);
error = -libxfs_attr3_leaf_read(NULL, ip, ip->i_ino,
leafhdr.forw, &leaf_bp);
if (error)
return error;
}
out_leaf:
libxfs_trans_brelse(NULL, leaf_bp);
return error;
}
static int
list_pptrs(
struct xfs_inode *ip)
{
int error;
if (!libxfs_inode_hasattr(ip))
return 0;
if (ip->i_af.if_format == XFS_DINODE_FMT_LOCAL)
return list_sf_pptrs(ip);
/* attr functions require that the attr fork is loaded */
error = -libxfs_iread_extents(NULL, ip, XFS_ATTR_FORK);
if (error)
return error;
if (libxfs_attr_is_leaf(ip))
return list_leaf_pptrs(ip);
return list_node_pptrs(ip);
}
/* If the io cursor points to a file, list its parents. */
static int
parent_cur(
char *tag)
{
struct xfs_inode *ip;
int error = 0;
if (!xfs_has_parent(mp))
return 0;
if (iocur_top->typ != &typtab[TYP_INODE])
return ENOTDIR;
error = -libxfs_iget(mp, NULL, iocur_top->ino, 0, &ip);
if (error)
return error;
/* List the parents of a file. */
if (tag)
dbprintf(_("%s:\n"), tag);
error = list_pptrs(ip);
if (error)
goto rele;
rele:
libxfs_irele(ip);
return error;
}
static void
parent_help(void)
{
dbprintf(_(
"\n"
" List the parents of the currently selected file.\n"
"\n"
" Parent pointers will be listed in the format:\n"
" inode_number:inode_gen ondisk_namehash:namehash name_length name\n"
));
}
static int
parent_f(
int argc,
char **argv)
{
int c;
int error = 0;
while ((c = getopt(argc, argv, "")) != -1) {
switch (c) {
default:
ls_help();
return 0;
}
}
if (optind == argc) {
error = parent_cur(NULL);
if (error) {
dbprintf("%s\n", strerror(error));
exitcode = 1;
}
return 0;
}
for (c = optind; c < argc; c++) {
push_cur();
error = path_walk(argv[c]);
if (error)
goto err_cur;
error = parent_cur(argv[c]);
if (error)
goto err_cur;
pop_cur();
}
return 0;
err_cur:
pop_cur();
if (error) {
dbprintf("%s: %s\n", argv[c], strerror(error));
exitcode = 1;
}
return 0;
}
static struct cmdinfo parent_cmd = {
.name = "parent",
.altname = "pptr",
.cfunc = parent_f,
.argmin = 0,
.argmax = -1,
.canpush = 0,
.args = "[paths...]",
.help = parent_help,
};
static void
link_help(void)
{
dbprintf(_(
"\n"
" Create a directory entry in the current directory that points to the\n"
" specified file.\n"
"\n"
" Options:\n"
" -i -- Point to this specific inode number.\n"
" -p -- Point to the inode given by this path.\n"
" -t -- Set the file type to this value.\n"
" name -- Create this directory entry with this name.\n"
));
}
static int
create_child(
struct xfs_mount *mp,
xfs_ino_t parent_ino,
const char *name,
unsigned int ftype,
xfs_ino_t child_ino)
{
struct xfs_name xname = {
.name = (const unsigned char *)name,
.len = strlen(name),
.type = ftype,
};
struct xfs_parent_args *ppargs = NULL;
struct xfs_trans *tp;
struct xfs_inode *dp, *ip;
unsigned int resblks;
bool isdir;
int error;
error = -libxfs_iget(mp, NULL, parent_ino, 0, &dp);
if (error)
return error;
if (!S_ISDIR(VFS_I(dp)->i_mode)) {
error = -ENOTDIR;
goto out_dp;
}
error = -libxfs_iget(mp, NULL, child_ino, 0, &ip);
if (error)
goto out_dp;
isdir = S_ISDIR(VFS_I(ip)->i_mode);
if (xname.type == XFS_DIR3_FT_UNKNOWN)
xname.type = libxfs_mode_to_ftype(VFS_I(ip)->i_mode);
error = -libxfs_parent_start(mp, &ppargs);
if (error)
goto out_ip;
resblks = libxfs_link_space_res(mp, MAXNAMELEN);
error = -libxfs_trans_alloc(mp, &M_RES(mp)->tr_link, resblks, 0, 0,
&tp);
if (error)
goto out_parent;
libxfs_trans_ijoin(tp, dp, 0);
libxfs_trans_ijoin(tp, ip, 0);
error = -libxfs_dir_createname(tp, dp, &xname, ip->i_ino, resblks);
if (error)
goto out_trans;
/* bump dp's link to ip */
libxfs_bumplink(tp, ip);
/* bump ip's dotdot link to dp */
if (isdir)
libxfs_bumplink(tp, dp);
/* Replace the dotdot entry in the child directory. */
if (isdir) {
error = -libxfs_dir_replace(tp, ip, &xfs_name_dotdot,
dp->i_ino, resblks);
if (error)
goto out_trans;
}
if (ppargs) {
error = -libxfs_parent_addname(tp, ppargs, dp, &xname, ip);
if (error)
goto out_trans;
}
error = -libxfs_trans_commit(tp);
goto out_parent;
out_trans:
libxfs_trans_cancel(tp);
out_parent:
libxfs_parent_finish(mp, ppargs);
out_ip:
libxfs_irele(ip);
out_dp:
libxfs_irele(dp);
return error;
}
static const char *ftype_map[] = {
[XFS_DIR3_FT_REG_FILE] = "reg",
[XFS_DIR3_FT_DIR] = "dir",
[XFS_DIR3_FT_CHRDEV] = "cdev",
[XFS_DIR3_FT_BLKDEV] = "bdev",
[XFS_DIR3_FT_FIFO] = "fifo",
[XFS_DIR3_FT_SOCK] = "sock",
[XFS_DIR3_FT_SYMLINK] = "symlink",
[XFS_DIR3_FT_WHT] = "whiteout",
};
static int
link_f(
int argc,
char **argv)
{
xfs_ino_t child_ino = NULLFSINO;
int ftype = XFS_DIR3_FT_UNKNOWN;
unsigned int i;
int c;
int error = 0;
while ((c = getopt(argc, argv, "i:p:t:")) != -1) {
switch (c) {
case 'i':
errno = 0;
child_ino = strtoull(optarg, NULL, 0);
if (errno == ERANGE) {
printf("%s: unknown inode number\n", optarg);
exitcode = 1;
return 0;
}
break;
case 'p':
push_cur();
error = path_walk(optarg);
if (error) {
printf("%s: %s\n", optarg, strerror(error));
exitcode = 1;
return 0;
} else if (iocur_top->typ != &typtab[TYP_INODE]) {
printf("%s: does not point to an inode\n",
optarg);
exitcode = 1;
return 0;
} else {
child_ino = iocur_top->ino;
}
pop_cur();
break;
case 't':
for (i = 0; i < ARRAY_SIZE(ftype_map); i++) {
if (ftype_map[i] &&
!strcmp(ftype_map[i], optarg)) {
ftype = i;
break;
}
}
if (i == ARRAY_SIZE(ftype_map)) {
printf("%s: unknown file type\n", optarg);
exitcode = 1;
return 0;
}
break;
default:
link_help();
return 0;
}
}
if (child_ino == NULLFSINO) {
printf("link: need to specify child via -i or -p\n");
exitcode = 1;
return 0;
}
if (iocur_top->typ != &typtab[TYP_INODE]) {
printf("io cursor does not point to an inode.\n");
exitcode = 1;
return 0;
}
if (optind + 1 != argc) {
printf("link: need directory entry name");
exitcode = 1;
return 0;
}
error = create_child(mp, iocur_top->ino, argv[optind], ftype,
child_ino);
if (error) {
printf("link failed: %s\n", strerror(error));
exitcode = 1;
return 0;
}
return 0;
}
static struct cmdinfo link_cmd = {
.name = "link",
.cfunc = link_f,
.argmin = 0,
.argmax = -1,
.canpush = 0,
.args = "[-i ino] [-p path] [-t ftype] name",
.help = link_help,
};
static void
unlink_help(void)
{
dbprintf(_(
"\n"
" Remove a directory entry from the current directory.\n"
"\n"
" Options:\n"
" name -- Remove the directory entry with this name.\n"
));
}
static void
droplink(
struct xfs_trans *tp,
struct xfs_inode *ip)
{
struct inode *inode = VFS_I(ip);
libxfs_trans_ichgtime(tp, ip, XFS_ICHGTIME_CHG);
if (inode->i_nlink != XFS_NLINK_PINNED)
drop_nlink(VFS_I(ip));
libxfs_trans_log_inode(tp, ip, XFS_ILOG_CORE);
}
static int
remove_child(
struct xfs_mount *mp,
xfs_ino_t parent_ino,
const char *name)
{
struct xfs_name xname = {
.name = (const unsigned char *)name,
.len = strlen(name),
};
struct xfs_parent_args *ppargs;
struct xfs_trans *tp;
struct xfs_inode *dp, *ip;
xfs_ino_t child_ino;
unsigned int resblks;
int error;
error = -libxfs_iget(mp, NULL, parent_ino, 0, &dp);
if (error)
return error;
if (!S_ISDIR(VFS_I(dp)->i_mode)) {
error = -ENOTDIR;
goto out_dp;
}
error = -libxfs_dir_lookup(NULL, dp, &xname, &child_ino, NULL);
if (error)
goto out_dp;
error = -libxfs_iget(mp, NULL, child_ino, 0, &ip);
if (error)
goto out_dp;
error = -libxfs_parent_start(mp, &ppargs);
if (error)
goto out_ip;
resblks = libxfs_remove_space_res(mp, MAXNAMELEN);
error = -libxfs_trans_alloc(mp, &M_RES(mp)->tr_remove, resblks, 0, 0,
&tp);
if (error)
goto out_parent;
libxfs_trans_ijoin(tp, dp, 0);
libxfs_trans_ijoin(tp, ip, 0);
if (S_ISDIR(VFS_I(ip)->i_mode)) {
/* drop ip's dotdot link to dp */
droplink(tp, dp);
} else {
libxfs_trans_log_inode(tp, dp, XFS_ILOG_CORE);
}
/* drop dp's link to ip */
droplink(tp, ip);
error = -libxfs_dir_removename(tp, dp, &xname, ip->i_ino, resblks);
if (error)
goto out_trans;
if (ppargs) {
error = -libxfs_parent_removename(tp, ppargs, dp, &xname, ip);
if (error)
goto out_trans;
}
error = -libxfs_trans_commit(tp);
goto out_parent;
out_trans:
libxfs_trans_cancel(tp);
out_parent:
libxfs_parent_finish(mp, ppargs);
out_ip:
libxfs_irele(ip);
out_dp:
libxfs_irele(dp);
return error;
}
static int
unlink_f(
int argc,
char **argv)
{
int c;
int error = 0;
while ((c = getopt(argc, argv, "")) != -1) {
switch (c) {
default:
unlink_help();
return 0;
}
}
if (iocur_top->typ != &typtab[TYP_INODE]) {
printf("io cursor does not point to an inode.\n");
exitcode = 1;
return 0;
}
if (optind + 1 != argc) {
printf("unlink: need directory entry name");
exitcode = 1;
return 0;
}
error = remove_child(mp, iocur_top->ino, argv[optind]);
if (error) {
printf("unlink failed: %s\n", strerror(error));
exitcode = 1;
return 0;
}
return 0;
}
static struct cmdinfo unlink_cmd = {
.name = "unlink",
.cfunc = unlink_f,
.argmin = 0,
.argmax = -1,
.canpush = 0,
.args = "name",
.help = unlink_help,
};
void
namei_init(void)
{
path_cmd.oneline = _("navigate to an inode by path");
add_command(&path_cmd);
ls_cmd.oneline = _("list directory contents");
add_command(&ls_cmd);
parent_cmd.oneline = _("list parent pointers");
add_command(&parent_cmd);
if (expert_mode) {
link_cmd.oneline = _("create directory link");
add_command(&link_cmd);
unlink_cmd.oneline = _("remove directory link");
add_command(&unlink_cmd);
}
}