Viewing: llsom_sync.c
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2017, DDN Storage Corporation.
*/
/*
* lustre/utils/llsom_sync.c
*
* Tool for sync the LSOM xattr.
*
* Author: Qian Yingjin <qian@ddn.com>
*/
#include <stdlib.h>
#include <errno.h>
#include <getopt.h>
#include <unistd.h>
#include <fcntl.h>
#include <poll.h>
#include <assert.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <linux/unistd.h>
#include <linux/kernel.h>
#include <sys/sysinfo.h>
#include <linux/lustre/lustre_user.h>
#include <lustre/lustreapi.h>
#include <linux/lustre/lustre_idl.h>
#include <linux/lustre/lustre_fid.h>
#include <libcfs/util/hash.h>
#include <libcfs/util/list.h>
#define container_of(ptr, type, member) ({ \
const typeof(((type *) 0)->member) * __mptr = (ptr); \
(type *) ((char *) __mptr - offsetof(type, member)); })
#define CHLG_POLL_INTV 60
#define REC_MIN_AGE 600
#define DEF_CACHE_SIZE (256 * 1048576) /* 256MB */
#define ONE_MB 0x100000
struct options {
const char *o_chlg_user;
const char *o_mdtname;
const char *o_mntpt;
bool o_daemonize;
bool o_data_sync;
int o_verbose;
int o_intv;
int o_min_age;
unsigned long o_cached_fid_hiwm; /* high watermark */
unsigned long o_batch_sync_cnt;
};
struct options opt;
struct fid_rec {
struct hlist_node fr_node;
struct list_head fr_link;
lustre_fid fr_fid;
__u64 fr_time;
__u64 fr_index;
};
static const int fid_hash_shift = 6;
#define FID_HASH_ENTRIES (1 << fid_hash_shift)
#define FID_ON_HASH(f) (!hlist_unhashed(&(f)->fr_node))
struct lsom_head {
struct hlist_head *lh_hash;
struct list_head lh_list; /* ordered list by record index */
unsigned long lh_cached_count;
} head;
static void usage(char *prog)
{
printf("\nUsage: %s [options] -u <userid> -m <mdtdev> <mntpt>\n"
"options:\n"
"\t-d, --daemonize\n"
"\t-i, --interval, poll interval in second\n"
"\t-a, --min-age, min age before a record is processed.\n"
"\t-c, --max-cache, percentage of the memroy used for cache.\n"
"\t-s, --sync, data sync when update LSOM xattr\n"
"\t-v, --verbose, produce more verbose ouput\n",
prog);
exit(0);
}
static inline bool fid_eq(const lustre_fid *f1, const lustre_fid *f2)
{
return f1->f_seq == f2->f_seq && f1->f_oid == f2->f_oid &&
f1->f_ver == f2->f_ver;
}
static void fid_hash_del(struct fid_rec *f)
{
if (FID_ON_HASH(f))
hlist_del_init(&f->fr_node);
}
static void fid_hash_add(struct fid_rec *f)
{
assert(!FID_ON_HASH(f));
hlist_add_head(&f->fr_node,
&head.lh_hash[llapi_fid_hash(&f->fr_fid,
fid_hash_shift)]);
}
static struct fid_rec *fid_hash_find(const lustre_fid *fid)
{
struct hlist_head *hash_list;
struct hlist_node *entry, *next;
struct fid_rec *f;
hash_list = &head.lh_hash[llapi_fid_hash(fid, fid_hash_shift)];
hlist_for_each_entry_safe(f, entry, next, hash_list, fr_node) {
assert(FID_ON_HASH(f));
if (fid_eq(fid, &f->fr_fid))
return f;
}
return NULL;
}
static int lsom_setup(void)
{
int i;
/* set llapi message level */
llapi_msg_set_level(opt.o_verbose);
memset(&head, 0, sizeof(head));
head.lh_hash = malloc(sizeof(struct hlist_head) * FID_HASH_ENTRIES);
if (head.lh_hash == NULL) {
llapi_err_noerrno(LLAPI_MSG_ERROR,
"failed to alloc memory for hash (%zu).",
sizeof(struct hlist_head) * FID_HASH_ENTRIES);
return -ENOMEM;
}
for (i = 0; i < FID_HASH_ENTRIES; i++)
INIT_HLIST_HEAD(&head.lh_hash[i]);
INIT_LIST_HEAD(&head.lh_list);
return 0;
}
static void lsom_cleanup(void)
{
free(head.lh_hash);
}
static int lsom_update_one(struct fid_rec *f)
{
struct stat st;
int fd;
int rc = 0;
fd = llapi_open_by_fid(opt.o_mntpt, &f->fr_fid,
O_RDONLY | O_NOATIME);
if (fd < 0) {
rc = -errno;
/* The file may be deleted, clean the corresponding
* changelog record and ignore this error.
*/
if (rc == -ENOENT)
goto clean_up;
llapi_error(LLAPI_MSG_ERROR, rc,
"llapi_open_by_fid for " DFID " failed",
PFID(&f->fr_fid));
return rc;
}
if (opt.o_data_sync) {
__u64 dv;
/* Flush dirty pages from clients */
rc = llapi_get_data_version(fd, &dv, LL_DV_RD_FLUSH);
if (rc < 0)
llapi_error(LLAPI_MSG_ERROR, errno,
"failed to sync data for " DFID,
PFID(&f->fr_fid));
/* ignore this error, continue to sync lsom data */
}
rc = fstat(fd, &st);
if (rc < 0) {
llapi_error(LLAPI_MSG_ERROR, rc, "failed to stat FID: " DFID,
PFID(&f->fr_fid));
return rc;
}
/* After call fstat(), it already gets OST attrs to the client,
* when close the file, MDS will update the LSOM data itself
* according the size and blocks information from the client.
*/
close(fd);
llapi_printf(LLAPI_MSG_DEBUG,
"record %llu:%llu, updated LSOM for fid " DFID
" size:%lu blocks:%lu\n",
(unsigned long long)f->fr_time,
(unsigned long long)f->fr_index,
PFID(&f->fr_fid), st.st_size, st.st_blocks);
clean_up:
rc = llapi_changelog_clear(opt.o_mdtname,
opt.o_chlg_user, f->fr_index);
if (rc)
llapi_error(LLAPI_MSG_ERROR, rc,
"failed to clear changelog record: %s:%llu",
opt.o_chlg_user, (unsigned long long)f->fr_index);
return rc;
}
static int lsom_start_update(int count)
{
int rc = 0;
int i = 0;
llapi_printf(LLAPI_MSG_INFO, "Start to sync %d records.\n", count);
while (i < count) {
struct fid_rec *f;
f = list_first_entry(&head.lh_list, struct fid_rec, fr_link);
rc = lsom_update_one(f);
if (rc == 0) {
list_del_init(&f->fr_link);
fid_hash_del(f);
free(f);
head.lh_cached_count--;
i++;
} else {
goto out;
}
}
out:
return rc;
}
static int lsom_check_sync(void)
{
int rc = 0;
int count;
repeated:
count = 0;
if (list_empty(&head.lh_list))
return 0;
if (head.lh_cached_count > opt.o_cached_fid_hiwm)
count = opt.o_batch_sync_cnt;
else {
struct fid_rec *f;
time_t now;
/* When the first record in the list was not being
* processed for a long time (more than o_min_age),
* pop the record, start to handle it immediately.
*/
now = time(NULL);
f = list_first_entry(&head.lh_list, struct fid_rec, fr_link);
if (now > ((f->fr_time >> 30) + opt.o_min_age))
count = 1;
}
if (count > 0)
rc = lsom_start_update(count);
if (rc == 0 && count == 1)
goto repeated;
return rc;
}
static void lsom_sort_record_list(struct fid_rec *f)
{
struct list_head *pos;
bool need_move = false;
for (pos = f->fr_link.next; pos != &head.lh_list; pos = pos->next) {
struct fid_rec *rec = list_entry(pos, struct fid_rec, fr_link);
if (f->fr_index > rec->fr_index) {
need_move = true;
continue;
} else {
break;
}
}
if (need_move)
list_move_tail(&f->fr_link, pos);
}
static int process_record(struct changelog_rec *rec)
{
__u64 index = rec->cr_index;
int rc = 0;
if (rec->cr_type == CL_CLOSE || rec->cr_type == CL_TRUNC ||
rec->cr_type == CL_SETATTR) {
struct fid_rec *f;
f = fid_hash_find(&rec->cr_tfid);
if (f == NULL) {
f = malloc(sizeof(struct fid_rec));
if (f == NULL) {
rc = -ENOMEM;
llapi_error(LLAPI_MSG_ERROR, rc,
"failed to alloc memory for fid_rec");
return rc;
}
f->fr_fid = rec->cr_tfid;
f->fr_index = index;
f->fr_time = rec->cr_time;
INIT_HLIST_NODE(&f->fr_node);
fid_hash_add(f);
/*
* The newly changelog record index is processed in the
* ascending order, so it is safe to put the record at
* the tail of the ordered list.
*/
list_add_tail(&f->fr_link, &head.lh_list);
head.lh_cached_count++;
} else {
f->fr_index = index;
lsom_sort_record_list(f);
}
}
llapi_printf(LLAPI_MSG_DEBUG,
"Processed changelog record index:%llu type:%s(0x%x) FID:"DFID"\n",
(unsigned long long)index,
changelog_type2str(__le32_to_cpu(rec->cr_type)),
__le32_to_cpu(rec->cr_type), PFID(&rec->cr_tfid));
return rc;
}
static unsigned long get_fid_cache_size(int pct)
{
struct sysinfo sinfo;
unsigned long cache_size;
int rc;
rc = sysinfo(&sinfo);
if (rc) {
llapi_error(LLAPI_MSG_ERROR, rc, "failed to get sysinfo");
/* ignore this error, just pick some reasonable static
* limit for the cache size (e.g. 256MB, default value).
*/
cache_size = DEF_CACHE_SIZE;
} else {
/* maximum cached fid size is tunned according to total
* memory size, e.g. 5% of the memroy.
*/
cache_size = sinfo.totalram * pct / 100;
}
return cache_size;
}
int main(int argc, char **argv)
{
int c;
int rc;
void *chglog_hdlr;
struct changelog_rec *rec;
bool stop = 0;
int ret = 0;
unsigned long long cache_size = DEF_CACHE_SIZE;
char fsname[MAX_OBD_NAME + 1];
unsigned long long unit;
static struct option options[] = {
{ "mdt", required_argument, NULL, 'm' },
{ "user", required_argument, 0, 'u'},
{ "daemonize", no_argument, NULL, 'd'},
{ "interval", required_argument, NULL, 'i'},
{ "min-age", required_argument, NULL, 'a'},
{ "max-cache", required_argument, NULL, 'c'},
{ "verbose", no_argument, NULL, 'v'},
{ "sync", no_argument, NULL, 's'},
{ "help", no_argument, NULL, 'h' },
{ NULL }
};
memset(&opt, 0, sizeof(opt));
opt.o_data_sync = false;
opt.o_verbose = LLAPI_MSG_INFO;
opt.o_intv = CHLG_POLL_INTV;
opt.o_min_age = REC_MIN_AGE;
while ((c = getopt_long(argc, argv, "u:hm:dsi:a:c:v", options, NULL))
!= EOF) {
switch (c) {
default:
rc = -EINVAL;
llapi_error(LLAPI_MSG_ERROR, rc,
"%s: unknown option '%c'",
argv[0], optopt);
return rc;
case 'u':
opt.o_chlg_user = optarg;
break;
case 'h':
usage(argv[0]);
break;
case 'm':
opt.o_mdtname = optarg;
break;
case 'd':
opt.o_daemonize = true;
break;
case 'i':
opt.o_intv = atoi(optarg);
if (opt.o_intv < 0) {
rc = -EINVAL;
llapi_error(LLAPI_MSG_ERROR, rc,
"bad value for -i %s", optarg);
return rc;
}
break;
case 'a':
opt.o_min_age = atoi(optarg);
if (opt.o_min_age < 0) {
rc = -EINVAL;
llapi_error(LLAPI_MSG_ERROR, rc,
"bad value for -a %s", optarg);
return rc;
}
break;
case 'c':
unit = ONE_MB;
rc = llapi_parse_size(optarg, &cache_size, &unit, 0);
if (rc < 0) {
rc = -EINVAL;
llapi_error(LLAPI_MSG_ERROR, rc,
"bad valud for -c '%s'", optarg);
return rc;
}
/* For value < 100, it is taken as the percentage of
* total memory instead.
*/
if (cache_size < 100)
cache_size = get_fid_cache_size(cache_size);
llapi_printf(LLAPI_MSG_INFO, "Cache size: %llu\n",
cache_size);
break;
case 'v':
opt.o_verbose++;
break;
case 's':
opt.o_data_sync = true;
break;
}
}
if (argc != optind + 1) {
llapi_err_noerrno(LLAPI_MSG_ERROR,
"%s: no mount point specified\n", argv[0]);
usage(argv[0]);
}
opt.o_mntpt = argv[optind];
rc = llapi_search_fsname(opt.o_mntpt, fsname);
if (rc < 0) {
llapi_error(LLAPI_MSG_ERROR, rc,
"cannot find a Lustre file system mounted at '%s'",
opt.o_mntpt);
return rc;
}
if (!opt.o_mdtname)
usage(argv[0]);
if (!opt.o_chlg_user)
usage(argv[0]);
if (opt.o_daemonize) {
rc = daemon(1, 1);
if (rc < 0) {
rc = -errno;
llapi_error(LLAPI_MSG_ERROR, rc, "cannot daemonize");
return rc;
}
setbuf(stdout, NULL);
}
opt.o_cached_fid_hiwm = cache_size / sizeof(struct fid_rec);
opt.o_batch_sync_cnt = opt.o_cached_fid_hiwm / 2;
rc = lsom_setup();
if (rc < 0)
return rc;
while (!stop) {
bool eof = false;
llapi_printf(LLAPI_MSG_DEBUG, "Start receiving records\n");
rc = llapi_changelog_start(&chglog_hdlr,
CHANGELOG_FLAG_BLOCK |
CHANGELOG_FLAG_JOBID |
CHANGELOG_FLAG_EXTRA_FLAGS,
opt.o_mdtname, 0);
if (rc) {
llapi_error(LLAPI_MSG_ERROR, rc,
"unable to open changelog of MDT '%s'",
opt.o_mdtname);
return rc;
}
while (!eof && !stop) {
rc = llapi_changelog_recv(chglog_hdlr, &rec);
switch (rc) {
case 0:
rc = process_record(rec);
if (rc) {
llapi_error(LLAPI_MSG_ERROR, rc,
"failed to process record");
ret = rc;
}
llapi_changelog_free(&rec);
rc = lsom_check_sync();
if (rc) {
stop = true;
ret = rc;
}
break;
case 1: /* EOF */
llapi_printf(LLAPI_MSG_DEBUG,
"finished reading [%s]\n",
opt.o_mdtname);
eof = true;
break;
case -EINVAL: /* FS unmounted */
case -EPROTO: /* error in KUC channel */
default:
stop = true;
llapi_error(LLAPI_MSG_ERROR, rc,
"failed to get changelog record");
ret = rc;
break;
}
}
/* reach EOF of changelog */
rc = llapi_changelog_fini(&chglog_hdlr);
if (rc) {
llapi_error(LLAPI_MSG_ERROR, rc,
"unable to close changelog of MDT '%s'",
opt.o_mdtname);
ret = rc;
return rc;
}
if (opt.o_daemonize) {
sleep(opt.o_intv);
rc = lsom_check_sync();
if (rc) {
stop = true;
ret = rc;
}
} else {
lsom_start_update(head.lh_cached_count);
stop = true;
}
}
lsom_cleanup();
return ret;
}