Whamcloud - gitweb
LU-12396 utils: lfs should not output 'nul' char
[fs/lustre-release.git] / lustre / utils / lfs.c
index e229763..4f21ced 100644 (file)
@@ -127,6 +127,13 @@ static inline int lfs_mirror_verify(int argc, char **argv);
 static inline int lfs_mirror_read(int argc, char **argv);
 static inline int lfs_mirror_write(int argc, char **argv);
 static inline int lfs_mirror_copy(int argc, char **argv);
+static int lfs_pcc_attach(int argc, char **argv);
+static int lfs_pcc_attach_fid(int argc, char **argv);
+static int lfs_pcc_detach(int argc, char **argv);
+static int lfs_pcc_detach_fid(int argc, char **argv);
+static int lfs_pcc_state(int argc, char **argv);
+static int lfs_pcc(int argc, char **argv);
+static int lfs_pcc_list_commands(int argc, char **argv);
 
 enum setstripe_origin {
        SO_SETSTRIPE,
@@ -259,6 +266,7 @@ static inline int lfs_mirror_split(int argc, char **argv)
        "\tmdt_hash:  hash type of the striped directory. mdt types:\n" \
        "       fnv_1a_64 FNV-1a hash algorithm (default)\n"            \
        "       all_char  sum of characters % MDT_COUNT (not recommended)\n" \
+       "       space     create subdirectories with balanced space usage\n" \
        "\tdefault_stripe: set default dirstripe of the directory\n"    \
        "\tmode: the file access permission of the directory (octal)\n" \
        "To create dir with a foreign (free format) layout :\n" \
@@ -324,6 +332,37 @@ command_t mirror_cmdlist[] = {
        { .pc_help = NULL }
 };
 
+/**
+ * command_t pcc_cmdlist - lfs pcc commands.
+ */
+command_t pcc_cmdlist[] = {
+       { .pc_name = "attach", .pc_func = lfs_pcc_attach,
+         .pc_help = "Attach given files to the Persistent Client Cache.\n"
+               "usage: lfs pcc attach <--id|-i NUM> <file> ...\n"
+               "\t-i: archive id for RW-PCC\n" },
+       { .pc_name = "attach_fid", .pc_func = lfs_pcc_attach_fid,
+         .pc_help = "Attach given files into PCC by FID(s).\n"
+               "usage: lfs pcc attach_id <--id|-i NUM> <--mnt|-m mnt> "
+               "<fid> ...\n"
+               "\t-i: archive id for RW-PCC\n"
+               "\t-m: Lustre mount point\n" },
+       { .pc_name = "state", .pc_func = lfs_pcc_state,
+         .pc_help = "Display the PCC state for given files.\n"
+               "usage: lfs pcc state <file> ...\n" },
+       { .pc_name = "detach", .pc_func = lfs_pcc_detach,
+         .pc_help = "Detach given files from the Persistent Client Cache.\n"
+               "usage: lfs pcc detach <file> ...\n" },
+       { .pc_name = "detach_fid", .pc_func = lfs_pcc_detach_fid,
+         .pc_help = "Detach given files from PCC by FID(s).\n"
+               "usage: lfs pcc detach_fid <mntpath> <fid>...\n" },
+       { .pc_name = "list-commands", .pc_func = lfs_pcc_list_commands,
+         .pc_help = "list commands supported by lfs pcc"},
+       { .pc_name = "help", .pc_func = Parser_help, .pc_help = "help" },
+       { .pc_name = "exit", .pc_func = Parser_quit, .pc_help = "quit" },
+       { .pc_name = "quit", .pc_func = Parser_quit, .pc_help = "quit" },
+       { .pc_help = NULL }
+};
+
 /* all available commands */
 command_t cmdlist[] = {
        {"setstripe", lfs_setstripe, 0,
@@ -629,6 +668,13 @@ command_t cmdlist[] = {
         "\t--clear|-c: Clear file heat for given files\n"
         "\t--off|-o:   Turn off file heat for given files\n"
         "\t--on|-O:    Turn on file heat for given files\n"},
+       {"pcc", lfs_pcc, pcc_cmdlist,
+        "lfs commands used to interact with PCC features:\n"
+        "lfs pcc attach - attach given files to Persistent Client Cache\n"
+        "lfs pcc attach_fid - attach given files into PCC by FID(s)\n"
+        "lfs pcc state  - display the PCC state for given files\n"
+        "lfs pcc detach - detach given files from Persistent Client Cache\n"
+        "lfs pcc detach_fid - detach given files from PCC by FID(s)\n"},
        {"help", Parser_help, 0, "help"},
        {"exit", Parser_quit, 0, "quit"},
        {"quit", Parser_quit, 0, "quit"},
@@ -4883,6 +4929,27 @@ static inline int obd_statfs_ratio(const struct obd_statfs *st, bool inodes)
        return (ratio - (int)ratio) > 0 ? (int)(ratio + 1) : (int)ratio;
 }
 
+/* This is only used to reflect various problem states for lfs df, so we only
+ * translate the flags reflecting those states.
+ */
+static char obd_statfs_state_names[] = {
+       [OS_STATE_DEGRADED]     = 'D',
+       [OS_STATE_READONLY]     = 'R',
+       [OS_STATE_NOPRECREATE]  = 'N',
+       [OS_STATE_ENOSPC]       = 'S',
+       [OS_STATE_ENOINO]       = 'I',
+};
+
+static char obd_statfs_state2char(int s)
+{
+       /* Unknown name */
+       if (s > ARRAY_SIZE(obd_statfs_state_names)/sizeof(char) ||
+           obd_statfs_state_names[s] == 0)
+               return '?';
+
+       return obd_statfs_state_names[s];
+}
+
 static int showdf(char *mntdir, struct obd_statfs *stat,
                  char *uuid, enum mntdf_flags flags,
                  char *type, int index, int rc)
@@ -4956,21 +5023,16 @@ static int showdf(char *mntdir, struct obd_statfs *stat,
                        printf("[%s:%d]", type, index);
 
                if (stat->os_state) {
-                       /*
-                        * Each character represents the matching
-                        * OS_STATE_* bit.
-                        */
-                       const char state_names[] = "DRSI";
-                       __u32      state;
-                       __u32      i;
+                       uint32_t state;
+                       uint32_t i;
 
                        printf(" ");
-                       for (i = 0, state = stat->os_state;
-                            state && i < sizeof(state_names); i++) {
-                               if (!(state & (1 << i)))
+                       for (i = 0, state = stat->os_state; state != 0; i++) {
+                               uint32_t mask = 1 << i;
+                               if (!(state & mask) || mask == OS_STATE_NONROT)
                                        continue;
-                               printf("%c", state_names[i]);
-                               state ^= 1 << i;
+                               printf("%c", obd_statfs_state2char(mask));
+                               state &= ~mask;
                        }
                }
 
@@ -5180,25 +5242,23 @@ static int ll_statfs_data_comp(const void *sd1, const void *sd2)
 /* functions */
 static int lfs_setdirstripe(int argc, char **argv)
 {
-       char                    *dname;
-       int                     result;
-       struct lfs_setstripe_args        lsa = { 0 };
-       struct llapi_stripe_param       *param = NULL;
-       __u32                   mdts[LMV_MAX_STRIPE_COUNT] = { 0 };
-       char                    *end;
-       int                     c;
-       char                    *mode_opt = NULL;
-       bool                    default_stripe = false;
-       mode_t                  mode = S_IRWXU | S_IRWXG | S_IRWXO;
-       mode_t                  previous_mode = 0;
-       bool                    delete = false;
-       struct ll_statfs_buf    *lsb = NULL;
-       char                    mntdir[PATH_MAX] = "";
-       bool                    auto_distributed = false;
-       bool                    foreign_mode = false;
-       char                    *xattr = NULL;
-       __u32                   type = LU_FOREIGN_TYPE_DAOS, flags = 0;
-
+       char *dname;
+       struct lfs_setstripe_args lsa = { 0 };
+       struct llapi_stripe_param *param = NULL;
+       __u32 mdts[LMV_MAX_STRIPE_COUNT] = { 0 };
+       char *end;
+       int c;
+       char *mode_opt = NULL;
+       bool default_stripe = false;
+       bool delete = false;
+       bool auto_distributed = false;
+       bool foreign_mode = false;
+       mode_t mode = S_IRWXU | S_IRWXG | S_IRWXO;
+       mode_t previous_mode = 0;
+       struct ll_statfs_buf *lsb = NULL;
+       char mntdir[PATH_MAX] = "";
+       char *xattr = NULL;
+       __u32 type = LU_FOREIGN_TYPE_DAOS, flags = 0;
        struct option long_opts[] = {
        { .val = 'c',   .name = "count",        .has_arg = required_argument },
        { .val = 'c',   .name = "mdt-count",    .has_arg = required_argument },
@@ -5228,6 +5288,7 @@ static int lfs_setdirstripe(int argc, char **argv)
 /* setstripe { .val = 'y', .name = "yaml",     .has_arg = no_argument }, */
        { .val = 'x',   .name = "xattr",        .has_arg = required_argument },
        { .name = NULL } };
+       int result = 0;
 
        setstripe_args_init(&lsa);
 
@@ -5305,7 +5366,6 @@ static int lfs_setdirstripe(int argc, char **argv)
                                        "%s %s: warning: '--index' deprecated, use '--mdt-index' instead\n",
                                        progname, argv[0]);
 #endif
-                       lsa.lsa_pattern = LLAPI_LAYOUT_OVERSTRIPING;
                        lsa.lsa_nr_tgts = parse_targets(mdts,
                                                sizeof(mdts) / sizeof(__u32),
                                                lsa.lsa_nr_tgts, optarg, NULL);
@@ -5315,7 +5375,6 @@ static int lfs_setdirstripe(int argc, char **argv)
                                        progname, argv[0], optarg);
                                return CMD_HELP;
                        }
-                       lsa.lsa_pattern = 0;
 
                        lsa.lsa_tgts = mdts;
                        if (lsa.lsa_stripe_off == LLAPI_LAYOUT_DEFAULT)
@@ -5456,113 +5515,124 @@ static int lfs_setdirstripe(int argc, char **argv)
                memcpy(param->lsp_tgts, mdts, sizeof(*mdts) * lsa.lsa_nr_tgts);
        }
 
+       if (!default_stripe && lsa.lsa_pattern == LMV_HASH_TYPE_SPACE) {
+               fprintf(stderr, "%s %s: can only specify -H space with -D\n",
+                       progname, argv[0]);
+               free(param);
+               return CMD_HELP;
+       }
+
        dname = argv[optind];
        do {
                if (default_stripe) {
                        result = llapi_dir_set_default_lmv(dname, param);
-               } else {
-                       /* if current \a dname isn't under the same \a mntdir
-                        * as the last one, and the last one was
-                        * auto-distributed, restore \a param.
-                        */
-                       if (mntdir[0] != '\0' &&
-                           strncmp(dname, mntdir, strlen(mntdir)) &&
-                           auto_distributed) {
-                               param->lsp_is_specific = false;
-                               param->lsp_stripe_offset = -1;
-                               auto_distributed = false;
-                       }
-
-                       if (!param->lsp_is_specific &&
-                           param->lsp_stripe_offset == -1) {
-                               char path[PATH_MAX] = "";
+                       if (result)
+                               fprintf(stderr,
+                                       "%s setdirstripe: cannot set default stripe on dir '%s': %s\n",
+                                       progname, dname, strerror(-result));
+                       continue;
+               }
 
-                               if (!lsb) {
-                                       lsb = malloc(sizeof(*lsb));
-                                       if (!lsb) {
-                                               result = -ENOMEM;
-                                               break;
-                                       }
-                               }
-                               lsb->sb_count = 0;
+               /*
+                * if current \a dname isn't under the same \a mntdir as the
+                * last one, and the last one was auto-distributed, restore
+                * \a param.
+                */
+               if (mntdir[0] != '\0' &&
+                   strncmp(dname, mntdir, strlen(mntdir)) &&
+                   auto_distributed) {
+                       param->lsp_is_specific = false;
+                       param->lsp_stripe_offset = -1;
+                       auto_distributed = false;
+               }
 
-                               /* use mntdir for dirname() temporarily */
-                               strncpy(mntdir, dname, sizeof(mntdir) - 1);
-                               if (!realpath(dirname(mntdir), path)) {
-                                       result = -errno;
-                                       fprintf(stderr,
-                                               "error: invalid path '%s': %s\n",
-                                               argv[optind], strerror(errno));
-                                       break;
-                               }
-                               mntdir[0] = '\0';
+               /*
+                * TODO: when MDT can allocate object with QoS (LU-9435), below
+                * code should be removed, instead we should let LMV to allocate
+                * the starting MDT object, and then let LOD allocate other MDT
+                * objects.
+                */
+               if (!param->lsp_is_specific && param->lsp_stripe_offset == -1) {
+                       char path[PATH_MAX] = "";
 
-                               result = llapi_search_mounts(path, 0, mntdir,
-                                                            NULL);
-                               if (result < 0 || mntdir[0] == '\0') {
-                                       fprintf(stderr,
-                                               "No suitable Lustre mount found\n");
+                       if (!lsb) {
+                               lsb = malloc(sizeof(*lsb));
+                               if (!lsb) {
+                                       result = -ENOMEM;
                                        break;
                                }
+                       }
+                       lsb->sb_count = 0;
 
-                               result = mntdf(mntdir, NULL, NULL, 0,
-                                              LL_STATFS_LMV, lsb);
-                               if (result < 0)
-                                       break;
+                       /* use mntdir for dirname() temporarily */
+                       strncpy(mntdir, dname, sizeof(mntdir));
+                       if (!realpath(dirname(mntdir), path)) {
+                               result = -errno;
+                               fprintf(stderr,
+                                       "error: invalid path '%s': %s\n",
+                                       argv[optind], strerror(errno));
+                               break;
+                       }
+                       mntdir[0] = '\0';
 
-                               if (param->lsp_stripe_count > lsb->sb_count) {
-                                       fprintf(stderr,
-                                               "error: stripe count %d is too big\n",
-                                               param->lsp_stripe_count);
-                                       result = -ERANGE;
-                                       break;
-                               }
+                       result = llapi_search_mounts(path, 0, mntdir, NULL);
+                       if (result < 0 || mntdir[0] == '\0') {
+                               fprintf(stderr,
+                                       "No suitable Lustre mount found\n");
+                               break;
+                       }
 
-                               qsort(lsb->sb_buf, lsb->sb_count,
-                                     sizeof(struct ll_statfs_data),
-                                     ll_statfs_data_comp);
+                       result = mntdf(mntdir, NULL, NULL, 0, LL_STATFS_LMV,
+                                      lsb);
+                       if (result < 0)
+                               break;
 
-                               auto_distributed = true;
+                       if (param->lsp_stripe_count > lsb->sb_count) {
+                               fprintf(stderr,
+                                       "error: stripe count %d is too big\n",
+                                       param->lsp_stripe_count);
+                               result = -ERANGE;
+                               break;
                        }
 
-                       if (auto_distributed) {
-                               int r;
-                               int nr = MAX(param->lsp_stripe_count,
-                                            lsb->sb_count / 2);
+                       qsort(lsb->sb_buf, lsb->sb_count,
+                             sizeof(struct ll_statfs_data),
+                             ll_statfs_data_comp);
 
-                               /* don't use server whose usage is above 90% */
-                               while (nr != param->lsp_stripe_count &&
-                                      obd_statfs_ratio(&lsb->sb_buf[nr].sd_st,
-                                                       false) > 90)
-                                       nr = MAX(param->lsp_stripe_count,
-                                                nr / 2);
+                       auto_distributed = true;
+               }
 
-                               /* get \a r between [0, nr) */
-                               r = rand() % nr;
+               if (auto_distributed) {
+                       int r;
+                       int nr = MAX(param->lsp_stripe_count,
+                                    lsb->sb_count / 2);
 
-                               param->lsp_stripe_offset =
-                                       lsb->sb_buf[r].sd_index;
-                               if (param->lsp_stripe_count > 1) {
-                                       int i = 0;
+                       /* don't use server whose usage is above 90% */
+                       while (nr != param->lsp_stripe_count &&
+                              obd_statfs_ratio(&lsb->sb_buf[nr].sd_st, false) >
+                              90)
+                               nr = MAX(param->lsp_stripe_count, nr / 2);
 
-                                       param->lsp_is_specific = true;
-                                       for (; i < param->lsp_stripe_count; i++)
-                                               param->lsp_tgts[(i + r) % nr] =
-                                                       lsb->sb_buf[i].sd_index;
-                               }
-                       }
+                       /* get \a r between [0, nr) */
+                       r = rand() % nr;
 
-                       result = llapi_dir_create(dname, mode, param);
+                       param->lsp_stripe_offset = lsb->sb_buf[r].sd_index;
+                       if (param->lsp_stripe_count > 1) {
+                               int i = 0;
+
+                               param->lsp_is_specific = true;
+                               for (; i < param->lsp_stripe_count; i++)
+                                       param->lsp_tgts[(i + r) % nr] =
+                                               lsb->sb_buf[i].sd_index;
+                       }
                }
 
-               if (result) {
+               result = llapi_dir_create(dname, mode, param);
+               if (result)
                        fprintf(stderr,
-                               "%s setdirstripe: cannot create stripe dir '%s': %s\n",
+                               "%s setdirstripe: cannot create dir '%s': %s\n",
                                progname, dname, strerror(-result));
-                       break;
-               }
-               dname = argv[++optind];
-       } while (dname != NULL);
+       } while (!result && (dname = argv[++optind]));
 
        if (mode_opt != NULL)
                umask(previous_mode);
@@ -10302,6 +10372,343 @@ static int lfs_mirror_list_commands(int argc, char **argv)
        return 0;
 }
 
+static int lfs_pcc_attach(int argc, char **argv)
+{
+       struct option long_opts[] = {
+       { .val = 'i',   .name = "id",   .has_arg = required_argument },
+       { .name = NULL } };
+       int c;
+       int rc = 0;
+       __u32 archive_id = 0;
+       const char *path;
+       char *end;
+       char fullpath[PATH_MAX];
+       enum lu_pcc_type type = LU_PCC_READWRITE;
+
+       optind = 0;
+       while ((c = getopt_long(argc, argv, "i:",
+                               long_opts, NULL)) != -1) {
+               switch (c) {
+               case 'i':
+                       archive_id = strtoul(optarg, &end, 0);
+                       if (*end != '\0' || archive_id == 0) {
+                               fprintf(stderr, "error: %s: bad archive ID "
+                                       "'%s'\n", argv[0], optarg);
+                               return CMD_HELP;
+                       }
+                       break;
+               case '?':
+                       return CMD_HELP;
+               default:
+                       fprintf(stderr, "%s: option '%s' unrecognized\n",
+                               argv[0], argv[optind - 1]);
+                       return CMD_HELP;
+               }
+       }
+
+       if (argc <= optind) {
+               fprintf(stderr, "%s: must specify one or more file names\n",
+                       argv[0]);
+               return CMD_HELP;
+       }
+
+       while (optind < argc) {
+               int rc2;
+
+               path = argv[optind++];
+               if (realpath(path, fullpath) == NULL) {
+                       fprintf(stderr, "%s: could not find path '%s': %s\n",
+                               argv[0], path, strerror(errno));
+                       if (rc == 0)
+                               rc = -EINVAL;
+                       continue;
+               }
+
+               rc2 = llapi_pcc_attach(fullpath, archive_id, type);
+               if (rc2 < 0) {
+                       fprintf(stderr, "%s: cannot attach '%s' to PCC "
+                               "with archive ID '%u': %s\n", argv[0],
+                               path, archive_id, strerror(-rc2));
+                       if (rc == 0)
+                               rc = rc2;
+               }
+       }
+       return rc;
+}
+
+static int lfs_pcc_attach_fid(int argc, char **argv)
+{
+       struct option long_opts[] = {
+       { .val = 'i',   .name = "id",   .has_arg = required_argument },
+       { .val = 'm',   .name = "mnt",  .has_arg = required_argument },
+       { .name = NULL } };
+       char                     short_opts[] = "i:m:";
+       int                      c;
+       int                      rc = 0;
+       __u32                    archive_id = 0;
+       char                    *end;
+       const char              *mntpath = NULL;
+       const char              *fidstr;
+       enum lu_pcc_type         type = LU_PCC_READWRITE;
+
+       optind = 0;
+       while ((c = getopt_long(argc, argv, short_opts,
+                               long_opts, NULL)) != -1) {
+               switch (c) {
+               case 'i':
+                       archive_id = strtoul(optarg, &end, 0);
+                       if (*end != '\0') {
+                               fprintf(stderr, "error: %s: bad archive ID "
+                                       "'%s'\n", argv[0], optarg);
+                               return CMD_HELP;
+                       }
+                       break;
+               case 'm':
+                       mntpath = optarg;
+                       break;
+               case '?':
+                       return CMD_HELP;
+               default:
+                       fprintf(stderr, "%s: option '%s' unrecognized\n",
+                               argv[0], argv[optind - 1]);
+                       return CMD_HELP;
+               }
+       }
+
+       if (archive_id == 0) {
+               fprintf(stderr, "%s: must specify an archive ID\n", argv[0]);
+               return CMD_HELP;
+       }
+
+       if (mntpath == NULL) {
+               fprintf(stderr, "%s: must specify Lustre mount point\n",
+                       argv[0]);
+               return CMD_HELP;
+       }
+
+       if (argc <= optind) {
+               fprintf(stderr, "%s: must specify one or more fids\n", argv[0]);
+               return CMD_HELP;
+       }
+
+       while (optind < argc) {
+               int rc2;
+
+               fidstr = argv[optind++];
+
+               rc2 = llapi_pcc_attach_fid_str(mntpath, fidstr,
+                                              archive_id, type);
+               if (rc2 < 0) {
+                       fprintf(stderr, "%s: cannot attach '%s' on '%s' to PCC "
+                               "with archive ID '%u': %s\n", argv[0],
+                               fidstr, mntpath, archive_id, strerror(rc2));
+               }
+               if (rc == 0 && rc2 < 0)
+                       rc = rc2;
+       }
+       return rc;
+}
+
+static int lfs_pcc_detach(int argc, char **argv)
+{
+       struct option long_opts[] = {
+       { .val = 'k',   .name = "keep", .has_arg = no_argument },
+       { .name = NULL } };
+       char                     short_opts[] = "k";
+       int                      c;
+       int                      rc = 0;
+       const char              *path;
+       char                     fullpath[PATH_MAX];
+       __u32                    detach_opt = PCC_DETACH_OPT_UNCACHE;
+
+       optind = 0;
+       while ((c = getopt_long(argc, argv, short_opts,
+                               long_opts, NULL)) != -1) {
+               switch (c) {
+               case 'k':
+                       detach_opt = PCC_DETACH_OPT_NONE;
+                       break;
+               case '?':
+                       return CMD_HELP;
+               default:
+                       fprintf(stderr, "%s: option '%s' unrecognized\n",
+                               argv[0], argv[optind - 1]);
+                       return CMD_HELP;
+               }
+       }
+
+       while (optind < argc) {
+               int rc2;
+
+               path = argv[optind++];
+               if (realpath(path, fullpath) == NULL) {
+                       fprintf(stderr, "%s: could not find path '%s': %s\n",
+                               argv[0], path, strerror(errno));
+                       if (rc == 0)
+                               rc = -EINVAL;
+                       continue;
+               }
+
+               rc2 = llapi_pcc_detach_file(fullpath, detach_opt);
+               if (rc2 < 0) {
+                       rc2 = -errno;
+                       fprintf(stderr, "%s: cannot detach '%s' from PCC: "
+                               "%s\n", argv[0], path, strerror(errno));
+                       if (rc == 0)
+                               rc = rc2;
+               }
+       }
+       return rc;
+}
+
+static int lfs_pcc_detach_fid(int argc, char **argv)
+{
+       struct option long_opts[] = {
+       { .val = 'k',   .name = "keep", .has_arg = no_argument },
+       { .name = NULL } };
+       char             short_opts[] = "k";
+       int              c;
+       int              rc = 0;
+       const char      *fid;
+       const char      *mntpath;
+       __u32            detach_opt = PCC_DETACH_OPT_UNCACHE;
+
+       optind = 0;
+       while ((c = getopt_long(argc, argv, short_opts,
+                               long_opts, NULL)) != -1) {
+               switch (c) {
+               case 'k':
+                       detach_opt = PCC_DETACH_OPT_NONE;
+                       break;
+               case '?':
+                       return CMD_HELP;
+               default:
+                       fprintf(stderr, "%s: option '%s' unrecognized\n",
+                               argv[0], argv[optind - 1]);
+                       return CMD_HELP;
+               }
+       }
+
+       mntpath = argv[optind++];
+
+       while (optind < argc) {
+               int rc2;
+
+               fid = argv[optind++];
+
+               rc2 = llapi_pcc_detach_fid_str(mntpath, fid, detach_opt);
+               if (rc2 < 0) {
+                       fprintf(stderr, "%s: cannot detach '%s' on '%s' "
+                               "from PCC: %s\n", argv[0], fid, mntpath,
+                               strerror(-rc2));
+                       if (rc == 0)
+                               rc = rc2;
+               }
+       }
+       return rc;
+}
+
+static int lfs_pcc_state(int argc, char **argv)
+{
+       int                      rc = 0;
+       const char              *path;
+       char                     fullpath[PATH_MAX];
+       struct lu_pcc_state      state;
+
+       optind = 1;
+
+       if (argc <= 1) {
+               fprintf(stderr, "%s: must specify one or more file names\n",
+                       argv[0]);
+               return CMD_HELP;
+       }
+
+       while (optind < argc) {
+               int rc2;
+
+               path = argv[optind++];
+               if (realpath(path, fullpath) == NULL) {
+                       fprintf(stderr, "%s: could not find path '%s': %s\n",
+                               argv[0], path, strerror(errno));
+                       if (rc == 0)
+                               rc = -EINVAL;
+                       continue;
+               }
+
+               rc2 = llapi_pcc_state_get(fullpath, &state);
+               if (rc2 < 0) {
+                       if (rc == 0)
+                               rc = rc2;
+                       fprintf(stderr, "%s: cannot get PCC state of '%s': "
+                               "%s\n", argv[0], path, strerror(-rc2));
+                       continue;
+               }
+
+               printf("file: %s", path);
+               printf(", type: %s", pcc_type2string(state.pccs_type));
+               if (state.pccs_type == LU_PCC_NONE &&
+                   state.pccs_open_count == 0) {
+                       printf("\n");
+                       continue;
+               }
+
+               printf(", PCC file: %s", state.pccs_path);
+               printf(", user number: %u", state.pccs_open_count);
+               printf(", flags: %x", state.pccs_flags);
+               printf("\n");
+       }
+       return rc;
+}
+
+/**
+ * lfs_pcc_list_commands() - List lfs pcc commands.
+ * @argc: The count of command line arguments.
+ * @argv: Array of strings for command line arguments.
+ *
+ * This function lists lfs pcc commands defined in pcc_cmdlist[].
+ *
+ * Return: 0 on success.
+ */
+static int lfs_pcc_list_commands(int argc, char **argv)
+{
+       char buffer[81] = "";
+
+       Parser_list_commands(pcc_cmdlist, buffer, sizeof(buffer),
+                            NULL, 0, 4);
+
+       return 0;
+}
+
+/**
+ * lfs_pcc() - Parse and execute lfs pcc commands.
+ * @argc: The count of lfs pcc command line arguments.
+ * @argv: Array of strings for lfs pcc command line arguments.
+ *
+ * This function parses lfs pcc commands and performs the
+ * corresponding functions specified in pcc_cmdlist[].
+ *
+ * Return: 0 on success or an error code on failure.
+ */
+static int lfs_pcc(int argc, char **argv)
+{
+       char cmd[PATH_MAX];
+       int rc = 0;
+
+       setlinebuf(stdout);
+
+       Parser_init("lfs-pcc > ", pcc_cmdlist);
+
+       snprintf(cmd, sizeof(cmd), "%s %s", progname, argv[0]);
+       progname = cmd;
+       program_invocation_short_name = cmd;
+       if (argc > 1)
+               rc = Parser_execarg(argc - 1, argv + 1, pcc_cmdlist);
+       else
+               rc = Parser_commands();
+
+       return rc < 0 ? -rc : rc;
+}
+
 static int lfs_list_commands(int argc, char **argv)
 {
        char buffer[81] = ""; /* 80 printable chars + terminating NUL */