Viewing: tgt_fmd.c
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2008 Sun Microsystems, Inc. All rights reserved
* Use is subject to license terms.
*
* Copyright (c) 2012, 2014, Intel Corporation.
*
* Copyright (c) 2019, DDN Storage Corporation.
*/
/*
* This file is part of Lustre, http://www.lustre.org/
*
* This file provides functions to handle Filter Modification Data (FMD).
* The FMD is responsible for file attributes to be applied in
* Transaction ID (XID) order, so older requests can't re-write newer
* attributes.
*
* FMD is organized as per-client list and identified by FID of object. Each
* FMD stores FID of object and the highest received XID of modification
* request for this object.
*
* FMD can expire if there are no updates for a long time to keep the list
* reasonably small.
*
* Author: Andreas Dilger <adilger@whamcloud.com>
* Author: Mike Pershin <mpershin@whamcloud.com>
*/
#define DEBUG_SUBSYSTEM S_CLASS
#include <obd.h>
#include <obd_class.h>
#include "tgt_internal.h"
/**
* tgt_fmd_put_nolock() - Drop FMD reference and free it if reference drops to
* zero.
* @exp: OBD export
* @fmd: FMD to put
*
* Must be called with ted_fmd_lock held.
*/
static inline void tgt_fmd_put_nolock(struct obd_export *exp,
struct tgt_fmd_data *fmd)
{
struct tg_export_data *ted = &exp->exp_target_data;
assert_spin_locked(&ted->ted_fmd_lock);
if (--fmd->fmd_refcount == 0) {
ted->ted_fmd_count--;
list_del(&fmd->fmd_list);
OBD_SLAB_FREE_PTR(fmd, tgt_fmd_kmem);
}
}
/**
* tgt_fmd_put() - Wrapper to drop FMD reference with ted_fmd_lock held.
* @exp: OBD export
* @fmd: FMD to put
*/
static void tgt_fmd_put(struct obd_export *exp, struct tgt_fmd_data *fmd)
{
struct tg_export_data *ted = &exp->exp_target_data;
spin_lock(&ted->ted_fmd_lock);
tgt_fmd_put_nolock(exp, fmd); /* caller reference */
spin_unlock(&ted->ted_fmd_lock);
}
/**
* tgt_fmd_expire_nolock() - Expire FMD entries.
* @exp: OBD export
* @keep: FMD to keep always
*
* Expire entries from the FMD list if there are too many
* of them or they are too old.
*
* This function must be called with ted_fmd_lock held.
*
* The \a keep FMD is not to be expired in any case. This parameter is used
* by ofd_fmd_find_nolock() to prohibit a FMD that was just found from
* expiring.
*/
static void tgt_fmd_expire_nolock(struct obd_export *exp,
struct tgt_fmd_data *keep)
{
struct tg_export_data *ted = &exp->exp_target_data;
struct lu_target *lut = obd2obt(exp->exp_obd)->obt_lut;
time64_t now = ktime_get_seconds();
struct tgt_fmd_data *fmd, *tmp;
list_for_each_entry_safe(fmd, tmp, &ted->ted_fmd_list, fmd_list) {
if (fmd == keep)
break;
if (now < fmd->fmd_expire &&
ted->ted_fmd_count < lut->lut_fmd_max_num)
break;
list_del_init(&fmd->fmd_list);
tgt_fmd_put_nolock(exp, fmd); /* list reference */
}
}
/**
* tgt_fmd_expire() - Expire FMD entries.
* @exp: OBD export
*
* This is a wrapper to call ofd_fmd_expire_nolock() with the required lock.
*/
void tgt_fmd_expire(struct obd_export *exp)
{
struct tg_export_data *ted = &exp->exp_target_data;
spin_lock(&ted->ted_fmd_lock);
tgt_fmd_expire_nolock(exp, NULL);
spin_unlock(&ted->ted_fmd_lock);
}
/**
* tgt_fmd_find_nolock() - Find FMD by specified FID.
* @exp: OBD export
* @fid: FID of FMD to find
*
* Function finds FMD entry by FID in the tg_export_data::ted_fmd_list.
*
* Caller must hold tg_export_data::ted_fmd_lock and take FMD reference.
*
* Return:
* * struct tgt_fmd_data found by FID
* * %NULL is FMD is not found
*/
static struct tgt_fmd_data *tgt_fmd_find_nolock(struct obd_export *exp,
const struct lu_fid *fid)
{
struct tg_export_data *ted = &exp->exp_target_data;
struct tgt_fmd_data *found = NULL, *fmd;
struct lu_target *lut = obd2obt(exp->exp_obd)->obt_lut;
time64_t now = ktime_get_seconds();
assert_spin_locked(&ted->ted_fmd_lock);
list_for_each_entry_reverse(fmd, &ted->ted_fmd_list, fmd_list) {
if (lu_fid_eq(&fmd->fmd_fid, fid)) {
found = fmd;
list_move_tail(&fmd->fmd_list, &ted->ted_fmd_list);
fmd->fmd_expire = now + lut->lut_fmd_max_age;
break;
}
}
tgt_fmd_expire_nolock(exp, found);
return found;
}
/**
* tgt_fmd_find() - Find FMD by specified FID with locking.
* @exp: OBD export
* @fid: FID of FMD to find
*
* Wrapper to the ofd_fmd_find_nolock() with correct locks.
*
* Return:
* * struct tgt_fmd_data found by FID
* * %NULL indicates FMD is not found
*/
static struct tgt_fmd_data *tgt_fmd_find(struct obd_export *exp,
const struct lu_fid *fid)
{
struct tg_export_data *ted = &exp->exp_target_data;
struct tgt_fmd_data *fmd;
spin_lock(&ted->ted_fmd_lock);
fmd = tgt_fmd_find_nolock(exp, fid);
if (fmd)
fmd->fmd_refcount++; /* caller reference */
spin_unlock(&ted->ted_fmd_lock);
return fmd;
}
/**
* tgt_fmd_get() - Find FMD by FID or create a new one if none is found.
* @exp: OBD export
* @fid: FID of FMD to find
*
* It is possible for this function to return NULL under memory pressure,
* or if the passed FID is zero (which will only cause old entries to expire).
* Currently this is not fatal because any FMD state is transient and
* may also be freed when it gets sufficiently old.
*
* Return:
* * struct tgt_fmd_data found by FID
* * %NULL indicates FMD is not found
*/
static struct tgt_fmd_data *tgt_fmd_get(struct obd_export *exp,
const struct lu_fid *fid)
{
struct tg_export_data *ted = &exp->exp_target_data;
struct tgt_fmd_data *found = NULL, *fmd_new = NULL;
OBD_SLAB_ALLOC_PTR(fmd_new, tgt_fmd_kmem);
spin_lock(&ted->ted_fmd_lock);
found = tgt_fmd_find_nolock(exp, fid);
if (fmd_new) {
if (!found) {
list_add_tail(&fmd_new->fmd_list, &ted->ted_fmd_list);
fmd_new->fmd_fid = *fid;
fmd_new->fmd_refcount++; /* list reference */
found = fmd_new;
ted->ted_fmd_count++;
} else {
OBD_SLAB_FREE_PTR(fmd_new, tgt_fmd_kmem);
}
}
if (found) {
found->fmd_refcount++; /* caller reference */
found->fmd_expire = ktime_get_seconds() +
class_exp2tgt(exp)->lut_fmd_max_age;
} else {
LCONSOLE_WARN("%s: cannot allocate FMD for "DFID
", timestamps may be out of sync\n",
exp->exp_obd->obd_name, PFID(fid));
}
spin_unlock(&ted->ted_fmd_lock);
return found;
}
#ifdef DO_FMD_DROP
/**
* tgt_fmd_drop() - Drop FMD list reference
* @exp: OBD export
* @fid: FID of FMD to drop
*
* Drop FMD list reference so it will disappear when last reference is dropped
* to zero.
*
* This function is called from ofd_destroy() and may only affect
* the one client that is doing the unlink and at worst we have an stale entry
* referencing an object that should never be used again.
*
* NB: this function is used only if DO_FMD_DROP is defined. It is not
* currently defined, so FMD drop doesn't happen and FMD are dropped only
* when expired.
*/
void tgt_fmd_drop(struct obd_export *exp, const struct lu_fid *fid)
{
struct tg_export_data *ted = &exp->exp_target_data;
struct tgt_fmd_data *fmd = NULL;
spin_lock(&ted->ted_fmd_lock);
fmd = tgt_fmd_find_nolock(exp, fid);
if (fmd) {
list_del_init(&fmd->fmd_list);
tgt_fmd_put_nolock(exp, fmd);
}
spin_unlock(&ted->ted_fmd_lock);
}
EXPORT_SYMBOL(tgt_fmd_drop);
#endif
/**
* tgt_fmd_cleanup() - Remove all entries from FMD list.
* @exp: OBD export
*
* Cleanup function to free all FMD enries on the given export.
*/
void tgt_fmd_cleanup(struct obd_export *exp)
{
struct tg_export_data *ted = &exp->exp_target_data;
struct tgt_fmd_data *fmd = NULL, *tmp;
spin_lock(&ted->ted_fmd_lock);
list_for_each_entry_safe(fmd, tmp, &ted->ted_fmd_list, fmd_list) {
list_del_init(&fmd->fmd_list);
if (fmd->fmd_refcount > 1) {
CDEBUG(D_INFO,
"fmd %p still referenced (refcount = %d)\n",
fmd, fmd->fmd_refcount);
}
tgt_fmd_put_nolock(exp, fmd);
}
spin_unlock(&ted->ted_fmd_lock);
LASSERT(list_empty(&exp->exp_target_data.ted_fmd_list));
}
/**
* tgt_fmd_update() - Update FMD with the latest request XID.
* @exp: OBD export
* @fid: FID of FMD to find
* @xid: request XID
*
* Save a new setattr/punch XID in FMD if exists.
*/
void tgt_fmd_update(struct obd_export *exp, const struct lu_fid *fid, __u64 xid)
{
struct tgt_fmd_data *fmd;
fmd = tgt_fmd_get(exp, fid);
if (fmd) {
if (fmd->fmd_mactime_xid < xid)
fmd->fmd_mactime_xid = xid;
tgt_fmd_put(exp, fmd);
}
}
EXPORT_SYMBOL(tgt_fmd_update);
/**
* tgt_fmd_check() - Chech time can be updated by the request with given XID.
*
* Check FMD XID if exists to be less than supplied XID
*
* @exp: OBD export
* @fid: FID of FMD to find
* @xid: request XID
*
* Returns true if FMD has no greater XID, so time attr can be updated
*/
bool tgt_fmd_check(struct obd_export *exp, const struct lu_fid *fid, __u64 xid)
{
struct tgt_fmd_data *fmd;
bool can_update = true;
fmd = tgt_fmd_find(exp, fid);
if (fmd) {
can_update = fmd->fmd_mactime_xid < xid;
tgt_fmd_put(exp, fmd);
}
return can_update;
}
EXPORT_SYMBOL(tgt_fmd_check);