--- /dev/null
+.TH ll_decode_linkea 1 "May 25, 2016" Lustre "utilities"
+.SH NAME
+ll_decode_linkea \- display parent FIDs and name of a Lustre file
+.SH SYNOPSIS
+.B ll_decode_linkea
+.I file
+.RI [ "file ..." ]
+.br
+.SH DESCRIPTION
+.B ll_decode_linkea
+decodes and prints the Lustre parent FIDs and name of a Lustre file, which
+are stored in the "trusted.link" attribute on MDT. This is accessible to
+.B ll_decode_linkea
+not only through Lustre client, but also when the MDT filesystem is mounted
+locally as type ldiskfs for maintenance.
+.PP
+The "trusted.link" extended attribute is saved when file/directory is created,
+and modified when renaming or hard link happens.
+.PP
+The parent FID is useful in case of MDT corruption. The fsck of ldiskfs can
+recover most of the ldiskfs hierarchy, but it might leave some files or
+directories under lost+found. The parent FIDs can be used to determine the
+right Lustre paths to move them to.
+.SH EXAMPLE
+.fi
+root@mdt1# cd /mnt/mdt/lost+found
+.fi
+root@mdt1# ll_decode_linkea \#123451 \#123452
+.fi
+#123451: count 2
+.fi
+ 0: [0x200034021:0x2:0x0], name "file1"
+.fi
+ 1: [0x200034021:0x1:0x0], name "file1_link"
+.fi
+#123452: count 1
+.fi
+ 0: [0x200000007:0x1:0x0], name "file2"
+.PP
+This shows that the 2 files in lost+found. ll_decode_linkea prints all of the
+parent FIDs of these files. file1 has two hard links, that is why it has two
+parent directories. Command
+.B lfs fid2path
+could be used to extract the paths of the parents.
+.PP
+Since "trusted.link" xattr is accessible on Lustre client too,
+.B ll_decode_linkea
+could also be used on Lustre client.
+.PP
+.SH SEE ALSO
+.BR lfs (1),
+.BR lustre (7),
+.BR ll_recover_lost_found_objs (8)
}
run_test 154A "lfs path2fid and fid2path basic checks"
+test_154B() {
+ [[ $(lustre_version_code $SINGLEMDS) -lt $(version_code 2.4.1) ]] &&
+ skip "Need MDS version at least 2.4.1" && return
+
+ mkdir -p $DIR/$tdir || error "mkdir $tdir failed"
+ touch $DIR/$tdir/$tfile || error "touch $DIR/$tdir/$tfile failed"
+ local linkea=$($LL_DECODE_LINKEA $DIR/$tdir/$tfile | grep 'pfid')
+ [ -z "$linkea" ] && error "decode linkea $DIR/$tdir/$tfile failed"
+
+ local name=$(echo $linkea | awk '/pfid/ {print $5}' | sed -e "s/'//g")
+ local PFID=$(echo $linkea | awk '/pfid/ {print $3}' | sed -e "s/,//g")
+
+ # check that we get the same pathname
+ echo "PFID: $PFID, name: $name"
+ local FOUND=$($LFS fid2path $MOUNT "$PFID")
+ [ -z "$FOUND" ] && error "fid2path unable to get $PFID path"
+ [ "$FOUND/$name" != "$DIR/$tdir/$tfile" ] &&
+ error "ll_decode_linkea has $FOUND/$name != $DIR/$tdir/$tfile"
+
+ rm -rf $DIR/$tdir || error "Can not delete directory $DIR/$tdir"
+}
+run_test 154B "verify the ll_decode_linkea tool"
+
test_154a() {
[ $PARALLEL == "yes" ] && skip "skip parallel run" && return
[[ $(lustre_version_code $SINGLEMDS) -ge $(version_code 2.2.51) ]] ||
--- /dev/null
+/*
+ * GPL HEADER START
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 only,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will 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 version 2 for more details (a copy is included
+ * in the LICENSE file that accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, see
+ * http://www.gnu.org/licenses/gpl-2.0.html
+ *
+ * GPL HEADER END
+ */
+/*
+ * Copyright (c) 2016, DDN Storage Corporation.
+ */
+/*
+ * lustre/utils/ll_decode_linkea.c
+ *
+ * Tool for printing the MDT link_ea structure on the objects
+ * in human readable form.
+ *
+ * Author: Li Xi <lixi@ddn.com>
+ */
+
+
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <sys/xattr.h>
+#include <lustre/lustre_idl.h>
+#include <lustre/lustre_user.h>
+
+#define BUFFER_SIZE 65536
+
+int decode_linkea(const char *fname)
+{
+ char buf[BUFFER_SIZE];
+ struct link_ea_header *leh;
+ ssize_t size;
+ struct link_ea_entry *lee;
+ int i;
+ __u64 length;
+ int reclen;
+ struct lu_fid pfid;
+
+ size = getxattr(fname, "trusted.link", buf, BUFFER_SIZE);
+ if (size < 0) {
+ if (errno == ERANGE) {
+ fprintf(stderr, "%s: failed to read trusted.link "
+ "xattr, the buffer size %u might be too "
+ "small\n", fname, BUFFER_SIZE);
+ } else {
+ fprintf(stderr,
+ "%s: failed to read trusted.link xattr: %s\n",
+ fname, strerror(errno));
+ }
+ return -1;
+ }
+
+ leh = (struct link_ea_header *)buf;
+ if (leh->leh_magic == __swab32(LINK_EA_MAGIC)) {
+ leh->leh_magic = LINK_EA_MAGIC;
+ leh->leh_reccount = __swab32(leh->leh_reccount);
+ leh->leh_len = __swab64(leh->leh_len);
+ }
+ if (leh->leh_magic != LINK_EA_MAGIC) {
+ fprintf(stderr,
+ "%s: magic mismatch, expected 0x%lx, got 0x%x\n",
+ fname, LINK_EA_MAGIC, leh->leh_magic);
+ return -1;
+ }
+ if (leh->leh_reccount == 0) {
+ fprintf(stderr, "%s: empty record count\n", fname);
+ return -1;
+ }
+ if (leh->leh_len > size) {
+ fprintf(stderr,
+ "%s: invalid length %llu, should smaller than %zd\n",
+ fname, leh->leh_len, size);
+ return -1;
+ }
+
+ length = sizeof(struct link_ea_header);
+ lee = (struct link_ea_entry *)(leh + 1);
+ printf("%s: count %u\n", fname, leh->leh_reccount);
+ for (i = 0; i < leh->leh_reccount; i++) {
+ reclen = (lee->lee_reclen[0] << 8) | lee->lee_reclen[1];
+ length += reclen;
+ if (length > leh->leh_len) {
+ fprintf(stderr,
+ "%s: length exceeded, expected %lld, got %lld\n",
+ fname, leh->leh_len, length);
+ return -1;
+ }
+ memcpy(&pfid, &lee->lee_parent_fid, sizeof(pfid));
+ fid_be_to_cpu(&pfid, &pfid);
+
+ printf(" %d: pfid "DFID", name '%s'\n", i, PFID(&pfid),
+ lee->lee_name);
+ lee = (struct link_ea_entry *)((char *)lee + reclen);
+ }
+
+ if (length != leh->leh_len) {
+ fprintf(stderr,
+ "%s: length mismatch, expected %lld, got %lld\n",
+ fname, leh->leh_len, length);
+ return -1;
+ }
+
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ int rc = 0;
+ int rc2;
+ int i;
+
+ for (i = 1; i < argc; i++) {
+ rc2 = decode_linkea(argv[i]);
+ if (rc2 != 0)
+ rc = rc2;
+ }
+
+ return rc;
+}