Viewing: tgt_lastrcvd.c
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
* Use is subject to license terms.
*
* Copyright (c) 2011, 2017, Intel Corporation.
*/
/*
* This file is part of Lustre, http://www.lustre.org/
*
* Lustre Unified Target
* These are common function to work with last_received file
*
* Author: Mikhail Pershin <mike.pershin@intel.com>
*/
#include <obd.h>
#include <obd_class.h>
#include <lustre_fid.h>
#include "tgt_internal.h"
/** version recovery epoch */
#define LR_EPOCH_BITS 32
/**
* Update maximum client count tracking
*
* Update the maximum number of clients ever connected to this target
* if it is larger than previously seen.
*
* \param[in] lut target to update
*/
static void tgt_update_max_clients(struct lu_target *lut)
{
unsigned int current_clients;
unsigned int max_clients;
int rc;
if (unlikely(!lut || !lut->lut_obd))
return;
current_clients = atomic_read(&lut->lut_num_clients);
max_clients = atomic_read(&lut->lut_max_clients);
if (current_clients > max_clients) {
rc = class_expected_clients_update(current_clients);
if (rc != 0) {
CDEBUG(D_INFO,
"%s: error setting expected_clients=%u: rc = %d\n",
lut->lut_obd->obd_name, current_clients, rc);
return;
}
atomic_set(&lut->lut_max_clients, current_clients);
lut->lut_lsd.lsd_max_clients = current_clients;
CDEBUG(D_INFO, "%s: new maximum client count %u\n",
lut->lut_obd->obd_name, current_clients);
}
}
/* Allocate a bitmap for a chunk of reply data slots */
static int tgt_bitmap_chunk_alloc(struct lu_target *lut, int chunk)
{
unsigned long *bm;
OBD_ALLOC_LARGE(bm, BITS_TO_LONGS(LUT_REPLY_SLOTS_PER_CHUNK) *
sizeof(long));
if (bm == NULL)
return -ENOMEM;
spin_lock(&lut->lut_client_bitmap_lock);
if (lut->lut_reply_bitmap[chunk] != NULL) {
/* someone else already allocated the bitmap for this chunk */
spin_unlock(&lut->lut_client_bitmap_lock);
OBD_FREE_LARGE(bm, BITS_TO_LONGS(LUT_REPLY_SLOTS_PER_CHUNK) *
sizeof(long));
return 0;
}
lut->lut_reply_bitmap[chunk] = bm;
spin_unlock(&lut->lut_client_bitmap_lock);
return 0;
}
/* Look for an available reply data slot in the bitmap
* of the target @lut
* Allocate bitmap chunk when first used
* XXX algo could be improved if this routine limits performance
*/
static int tgt_find_free_reply_slot(struct lu_target *lut)
{
unsigned long *bmp;
int chunk = 0;
int rc;
int b;
for (chunk = 0; chunk < LUT_REPLY_SLOTS_MAX_CHUNKS; chunk++) {
/* allocate the bitmap chunk if necessary */
if (unlikely(lut->lut_reply_bitmap[chunk] == NULL)) {
rc = tgt_bitmap_chunk_alloc(lut, chunk);
if (rc != 0)
return rc;
}
bmp = lut->lut_reply_bitmap[chunk];
/* look for an available slot in this chunk */
do {
b = find_first_zero_bit(bmp, LUT_REPLY_SLOTS_PER_CHUNK);
if (b >= LUT_REPLY_SLOTS_PER_CHUNK)
break;
/* found one */
if (test_and_set_bit(b, bmp) == 0)
return chunk * LUT_REPLY_SLOTS_PER_CHUNK + b;
} while (true);
}
return -ENOSPC;
}
/* Mark the reply data slot @idx 'used' in the corresponding bitmap chunk
* of the target @lut
* Allocate the bitmap chunk if necessary
*/
static int tgt_set_reply_slot(struct lu_target *lut, int idx)
{
int chunk;
int b;
int rc;
chunk = idx / LUT_REPLY_SLOTS_PER_CHUNK;
b = idx % LUT_REPLY_SLOTS_PER_CHUNK;
LASSERT(chunk < LUT_REPLY_SLOTS_MAX_CHUNKS);
LASSERT(b < LUT_REPLY_SLOTS_PER_CHUNK);
/* allocate the bitmap chunk if necessary */
if (unlikely(lut->lut_reply_bitmap[chunk] == NULL)) {
rc = tgt_bitmap_chunk_alloc(lut, chunk);
if (rc != 0)
return rc;
}
/* mark the slot 'used' in this chunk */
if (test_and_set_bit(b, lut->lut_reply_bitmap[chunk]) != 0) {
CERROR("%s: slot %d already set in bitmap\n",
tgt_name(lut), idx);
return -EALREADY;
}
return 0;
}
/* Mark the reply data slot @idx 'unused' in the corresponding bitmap chunk
* of the target @lut
*/
static int tgt_clear_reply_slot(struct lu_target *lut, int idx)
{
int chunk;
int b;
if (test_bit(OBDF_STOPPING, lut->lut_obd->obd_flags))
/*
* in case of failover keep the bit set in order to
* avoid overwriting slots in reply_data which might
* be required by resent rpcs
*/
return 0;
chunk = idx / LUT_REPLY_SLOTS_PER_CHUNK;
b = idx % LUT_REPLY_SLOTS_PER_CHUNK;
LASSERT(chunk < LUT_REPLY_SLOTS_MAX_CHUNKS);
LASSERT(b < LUT_REPLY_SLOTS_PER_CHUNK);
if (lut->lut_reply_bitmap[chunk] == NULL) {
CERROR("%s: slot %d not allocated\n",
tgt_name(lut), idx);
return -ENOENT;
}
if (test_and_clear_bit(b, lut->lut_reply_bitmap[chunk]) == 0) {
CERROR("%s: slot %d already clear in bitmap\n",
tgt_name(lut), idx);
return -EALREADY;
}
return 0;
}
/* Read header of reply_data file of target @tgt into structure @lrh */
static int tgt_reply_header_read(const struct lu_env *env,
struct lu_target *tgt,
struct lsd_reply_header *lrh)
{
int rc;
struct lsd_reply_header buf;
struct tgt_thread_info *tti = tgt_th_info(env);
tti->tti_off = 0;
tti->tti_buf.lb_buf = &buf;
tti->tti_buf.lb_len = sizeof(buf);
rc = dt_record_read(env, tgt->lut_reply_data, &tti->tti_buf,
&tti->tti_off);
if (rc != 0)
return rc;
lrh->lrh_magic = le32_to_cpu(buf.lrh_magic);
lrh->lrh_header_size = le32_to_cpu(buf.lrh_header_size);
lrh->lrh_reply_size = le32_to_cpu(buf.lrh_reply_size);
CDEBUG(D_HA, "%s: read %s header. magic=0x%08x header_size=%d reply_size=%d\n",
tgt->lut_obd->obd_name, REPLY_DATA,
lrh->lrh_magic, lrh->lrh_header_size, lrh->lrh_reply_size);
return 0;
}
/* Write header into replay_data file of target @tgt from structure @lrh */
static int tgt_reply_header_write(const struct lu_env *env,
struct lu_target *tgt,
struct lsd_reply_header *lrh)
{
int rc;
struct lsd_reply_header buf;
struct tgt_thread_info *tti = tgt_th_info(env);
struct thandle *th;
struct dt_object *dto;
CDEBUG(D_HA, "%s: write %s header. magic=0x%08x header_size=%d reply_size=%d\n",
tgt->lut_obd->obd_name, REPLY_DATA,
lrh->lrh_magic, lrh->lrh_header_size, lrh->lrh_reply_size);
if (tgt->lut_bottom->dd_rdonly)
RETURN(0);
buf.lrh_magic = cpu_to_le32(lrh->lrh_magic);
buf.lrh_header_size = cpu_to_le32(lrh->lrh_header_size);
buf.lrh_reply_size = cpu_to_le32(lrh->lrh_reply_size);
th = dt_trans_create(env, tgt->lut_bottom);
if (IS_ERR(th))
return PTR_ERR(th);
th->th_sync = 1;
tti->tti_off = 0;
tti->tti_buf.lb_buf = &buf;
tti->tti_buf.lb_len = sizeof(buf);
rc = dt_declare_record_write(env, tgt->lut_reply_data,
&tti->tti_buf, tti->tti_off, th);
if (rc)
GOTO(out, rc);
rc = dt_trans_start(env, tgt->lut_bottom, th);
if (rc)
GOTO(out, rc);
dto = dt_object_locate(tgt->lut_reply_data, th->th_dev);
rc = dt_record_write(env, dto, &tti->tti_buf, &tti->tti_off, th);
out:
dt_trans_stop(env, tgt->lut_bottom, th);
return rc;
}
/* Write the reply data @lrd into reply_data file of target @tgt
* at offset @off
*/
static int tgt_reply_data_write(const struct lu_env *env, struct lu_target *tgt,
struct lsd_reply_data *lrd, loff_t off,
struct thandle *th)
{
struct tgt_thread_info *tti = tgt_th_info(env);
struct lsd_reply_data *buf = &tti->tti_lrd;
struct lsd_reply_header *lrh = &tgt->lut_reply_header;
struct dt_object *dto;
lrd->lrd_result = ptlrpc_status_hton(lrd->lrd_result);
buf->lrd_transno = cpu_to_le64(lrd->lrd_transno);
buf->lrd_xid = cpu_to_le64(lrd->lrd_xid);
buf->lrd_data = cpu_to_le64(lrd->lrd_data);
buf->lrd_result = cpu_to_le32(lrd->lrd_result);
buf->lrd_client_gen = cpu_to_le32(lrd->lrd_client_gen);
lrd->lrd_result = ptlrpc_status_ntoh(lrd->lrd_result);
if (lrh->lrh_magic > LRH_MAGIC_V1)
buf->lrd_batch_idx = cpu_to_le32(lrd->lrd_batch_idx);
tti->tti_off = off;
tti->tti_buf.lb_buf = buf;
tti->tti_buf.lb_len = lrh->lrh_reply_size;
dto = dt_object_locate(tgt->lut_reply_data, th->th_dev);
return dt_record_write(env, dto, &tti->tti_buf, &tti->tti_off, th);
}
/* Read the reply data from reply_data file of target @tgt at offset @off
* into structure @lrd
*/
static int tgt_reply_data_read(const struct lu_env *env, struct lu_target *tgt,
struct lsd_reply_data *lrd, loff_t off,
struct lsd_reply_header *lrh)
{
struct tgt_thread_info *tti = tgt_th_info(env);
struct lsd_reply_data *buf = &tti->tti_lrd;
int rc;
tti->tti_off = off;
tti->tti_buf.lb_buf = buf;
tti->tti_buf.lb_len = lrh->lrh_reply_size;
rc = dt_record_read(env, tgt->lut_reply_data, &tti->tti_buf,
&tti->tti_off);
if (rc != 0)
return rc;
lrd->lrd_transno = le64_to_cpu(buf->lrd_transno);
lrd->lrd_xid = le64_to_cpu(buf->lrd_xid);
lrd->lrd_data = le64_to_cpu(buf->lrd_data);
lrd->lrd_result = le32_to_cpu(buf->lrd_result);
lrd->lrd_client_gen = le32_to_cpu(buf->lrd_client_gen);
if (lrh->lrh_magic > LRH_MAGIC_V1)
lrd->lrd_batch_idx = le32_to_cpu(buf->lrd_batch_idx);
else
lrd->lrd_batch_idx = 0;
return 0;
}
/* Free the in-memory reply data structure @trd and release
* the corresponding slot in the reply_data file of target @lut
* Called with ted_lcd_lock held
*/
static void tgt_free_reply_data(struct lu_target *lut,
struct tg_export_data *ted,
struct tg_reply_data *trd)
{
CDEBUG(D_TRACE, "%s: free reply data %p: xid %llu, transno %llu, client gen %u, slot idx %d\n",
lut == NULL ? "" : tgt_name(lut), trd, trd->trd_reply.lrd_xid,
trd->trd_reply.lrd_transno, trd->trd_reply.lrd_client_gen,
trd->trd_index);
LASSERT(mutex_is_locked(&ted->ted_lcd_lock));
list_del(&trd->trd_list);
ted->ted_reply_cnt--;
if (lut != NULL && trd->trd_index != TRD_INDEX_MEMORY)
tgt_clear_reply_slot(lut, trd->trd_index);
OBD_FREE_PTR(trd);
}
/* Release the reply data @trd from target @lut
* The reply data with the highest transno for this export
* is retained to ensure correctness of target recovery
* Called with ted_lcd_lock held
*/
static void tgt_release_reply_data(struct lu_target *lut,
struct tg_export_data *ted,
struct tg_reply_data *trd)
{
CDEBUG(D_TRACE, "%s: release reply data %p: xid %llu, transno %llu, client gen %u, slot idx %d\n",
lut == NULL ? "" : tgt_name(lut), trd, trd->trd_reply.lrd_xid,
trd->trd_reply.lrd_transno, trd->trd_reply.lrd_client_gen,
trd->trd_index);
LASSERT(mutex_is_locked(&ted->ted_lcd_lock));
/* Do not free the reply data corresponding to the
* highest transno of this export.
* This ensures on-disk reply data is kept and
* last committed transno can be restored from disk in case
* of target recovery
*/
if (trd->trd_reply.lrd_transno == ted->ted_lcd->lcd_last_transno) {
/* free previous retained reply */
if (ted->ted_reply_last != NULL)
tgt_free_reply_data(lut, ted, ted->ted_reply_last);
/* retain the reply */
list_del_init(&trd->trd_list);
ted->ted_reply_last = trd;
} else {
tgt_free_reply_data(lut, ted, trd);
}
}
static inline struct lu_buf *tti_buf_lsd(struct tgt_thread_info *tti)
{
tti->tti_buf.lb_buf = &tti->tti_lsd;
tti->tti_buf.lb_len = sizeof(tti->tti_lsd);
return &tti->tti_buf;
}
static inline struct lu_buf *tti_buf_lcd(struct tgt_thread_info *tti)
{
tti->tti_buf.lb_buf = &tti->tti_lcd;
tti->tti_buf.lb_len = sizeof(tti->tti_lcd);
return &tti->tti_buf;
}
static inline bool tgt_is_multimodrpcs_record(struct lu_target *tgt,
struct lsd_client_data *lcd)
{
return tgt->lut_lsd.lsd_feature_incompat & OBD_INCOMPAT_MULTI_RPCS &&
lcd->lcd_generation != 0;
}
/**
* Allocate in-memory data for client slot related to export.
*/
int tgt_client_alloc(struct obd_export *exp)
{
ENTRY;
LASSERT(exp != exp->exp_obd->obd_self_export);
spin_lock_init(&exp->exp_target_data.ted_nodemap_lock);
INIT_LIST_HEAD(&exp->exp_target_data.ted_nodemap_member);
spin_lock_init(&exp->exp_target_data.ted_fmd_lock);
INIT_LIST_HEAD(&exp->exp_target_data.ted_fmd_list);
OBD_ALLOC_PTR(exp->exp_target_data.ted_lcd);
if (exp->exp_target_data.ted_lcd == NULL)
RETURN(-ENOMEM);
/* Mark that slot is not yet valid, 0 doesn't work here */
exp->exp_target_data.ted_lr_idx = -1;
INIT_LIST_HEAD(&exp->exp_target_data.ted_reply_list);
mutex_init(&exp->exp_target_data.ted_lcd_lock);
RETURN(0);
}
EXPORT_SYMBOL(tgt_client_alloc);
/**
* Free in-memory data for client slot related to export.
*/
void tgt_client_free(struct obd_export *exp)
{
struct tg_export_data *ted = &exp->exp_target_data;
struct lu_target *lut = class_exp2tgt(exp);
struct tg_reply_data *trd, *tmp;
LASSERT(exp != exp->exp_obd->obd_self_export);
tgt_fmd_cleanup(exp);
/* free reply data */
mutex_lock(&ted->ted_lcd_lock);
list_for_each_entry_safe(trd, tmp, &ted->ted_reply_list, trd_list) {
tgt_release_reply_data(lut, ted, trd);
}
if (ted->ted_reply_last != NULL) {
tgt_free_reply_data(lut, ted, ted->ted_reply_last);
ted->ted_reply_last = NULL;
}
mutex_unlock(&ted->ted_lcd_lock);
if (!hlist_unhashed(&exp->exp_gen_hash))
cfs_hash_del(exp->exp_obd->obd_gen_hash,
&ted->ted_lcd->lcd_generation,
&exp->exp_gen_hash);
OBD_FREE_PTR(ted->ted_lcd);
ted->ted_lcd = NULL;
/* Target may have been freed (see LU-7430)
* Slot may be not yet assigned */
if (((struct obd_device_target *)(&exp->exp_obd->u))->obt_magic !=
OBT_MAGIC ||
ted->ted_lr_idx < 0)
return;
/* Clear bit when lcd is freed */
LASSERT(lut && lut->lut_client_bitmap);
LASSERTF(test_and_clear_bit(ted->ted_lr_idx, lut->lut_client_bitmap),
"%s: client %u bit already clear in bitmap\n",
exp->exp_obd->obd_name, ted->ted_lr_idx);
}
EXPORT_SYMBOL(tgt_client_free);
static inline void tgt_check_lcd(const char *obd_name, int index,
struct lsd_client_data *lcd)
{
size_t uuid_size = sizeof(lcd->lcd_uuid);
if (strnlen((char *)lcd->lcd_uuid, uuid_size) == uuid_size) {
lcd->lcd_uuid[uuid_size - 1] = '\0';
LCONSOLE_ERROR("the client UUID (%s) on %s for exports stored in last_rcvd(index = %d) is bad!\n",
lcd->lcd_uuid, obd_name, index);
}
}
static int tgt_client_data_read(const struct lu_env *env, struct lu_target *tgt,
struct lsd_client_data *lcd,
loff_t *off, int index)
{
struct tgt_thread_info *tti = tgt_th_info(env);
int rc;
tti_buf_lcd(tti);
rc = dt_record_read(env, tgt->lut_last_rcvd, &tti->tti_buf, off);
if (rc == 0) {
tgt_check_lcd(tgt->lut_obd->obd_name, index, &tti->tti_lcd);
lcd_le_to_cpu(&tti->tti_lcd, lcd);
lcd->lcd_last_result = ptlrpc_status_ntoh(lcd->lcd_last_result);
lcd->lcd_last_close_result =
ptlrpc_status_ntoh(lcd->lcd_last_close_result);
}
CDEBUG(D_INFO, "%s: read lcd @%lld uuid = %s, last_transno = %llu, last_xid = %llu, last_result = %u, last_data = %u, "
"last_close_transno = %llu, last_close_xid = %llu, last_close_result = %u, rc = %d\n",
tgt->lut_obd->obd_name,
*off, lcd->lcd_uuid, lcd->lcd_last_transno, lcd->lcd_last_xid,
lcd->lcd_last_result, lcd->lcd_last_data,
lcd->lcd_last_close_transno, lcd->lcd_last_close_xid,
lcd->lcd_last_close_result, rc);
return rc;
}
static int tgt_client_data_write(const struct lu_env *env,
struct lu_target *tgt,
struct lsd_client_data *lcd,
loff_t *off, struct thandle *th)
{
struct tgt_thread_info *tti = tgt_th_info(env);
struct dt_object *dto;
lcd->lcd_last_result = ptlrpc_status_hton(lcd->lcd_last_result);
lcd->lcd_last_close_result =
ptlrpc_status_hton(lcd->lcd_last_close_result);
lcd_cpu_to_le(lcd, &tti->tti_lcd);
tti_buf_lcd(tti);
dto = dt_object_locate(tgt->lut_last_rcvd, th->th_dev);
return dt_record_write(env, dto, &tti->tti_buf, off, th);
}
struct tgt_new_client_callback {
struct dt_txn_commit_cb lncc_cb;
struct obd_export *lncc_exp;
};
static void tgt_cb_new_client(struct lu_env *env, struct thandle *th,
struct dt_txn_commit_cb *cb, int err)
{
struct tgt_new_client_callback *ccb;
ccb = container_of(cb, struct tgt_new_client_callback, lncc_cb);
LASSERT(ccb->lncc_exp->exp_obd);
CDEBUG(D_RPCTRACE, "%s: committing for initial connect of %s\n",
ccb->lncc_exp->exp_obd->obd_name,
ccb->lncc_exp->exp_client_uuid.uuid);
spin_lock(&ccb->lncc_exp->exp_lock);
ccb->lncc_exp->exp_need_sync = 0;
spin_unlock(&ccb->lncc_exp->exp_lock);
class_export_cb_put(ccb->lncc_exp);
OBD_FREE_PTR(ccb);
}
static int tgt_new_client_cb_add(struct thandle *th, struct obd_export *exp)
{
struct tgt_new_client_callback *ccb;
struct dt_txn_commit_cb *dcb;
int rc;
OBD_ALLOC_PTR(ccb);
if (ccb == NULL)
return -ENOMEM;
ccb->lncc_exp = class_export_cb_get(exp);
dcb = &ccb->lncc_cb;
dcb->dcb_func = tgt_cb_new_client;
INIT_LIST_HEAD(&dcb->dcb_linkage);
strscpy(dcb->dcb_name, "tgt_cb_new_client", sizeof(dcb->dcb_name));
rc = dt_trans_cb_add(th, dcb);
if (rc) {
class_export_cb_put(exp);
OBD_FREE_PTR(ccb);
}
return rc;
}
/**
* Update client data in last_rcvd
*/
static int tgt_client_data_update(const struct lu_env *env,
struct obd_export *exp)
{
struct tg_export_data *ted = &exp->exp_target_data;
struct lu_target *tgt = class_exp2tgt(exp);
struct tgt_thread_info *tti = tgt_th_info(env);
struct thandle *th;
int rc = 0;
ENTRY;
if (unlikely(tgt == NULL)) {
CDEBUG(D_ERROR, "%s: No target for connected export\n",
class_exp2obd(exp)->obd_name);
RETURN(-EINVAL);
}
if (tgt->lut_bottom->dd_rdonly)
RETURN(0);
th = dt_trans_create(env, tgt->lut_bottom);
if (IS_ERR(th))
RETURN(PTR_ERR(th));
tti_buf_lcd(tti);
rc = dt_declare_record_write(env, tgt->lut_last_rcvd,
&tti->tti_buf,
ted->ted_lr_off, th);
if (rc)
GOTO(out, rc);
rc = dt_trans_start_local(env, tgt->lut_bottom, th);
if (rc)
GOTO(out, rc);
mutex_lock(&ted->ted_lcd_lock);
/*
* Until this operations will be committed the sync is needed
* for this export. This should be done _after_ starting the
* transaction so that many connecting clients will not bring
* server down with lots of sync writes.
*/
rc = tgt_new_client_cb_add(th, exp);
if (rc) {
/* can't add callback, do sync now */
th->th_sync = 1;
} else {
spin_lock(&exp->exp_lock);
exp->exp_need_sync = 1;
spin_unlock(&exp->exp_lock);
}
tti->tti_off = ted->ted_lr_off;
rc = tgt_client_data_write(env, tgt, ted->ted_lcd, &tti->tti_off, th);
mutex_unlock(&ted->ted_lcd_lock);
EXIT;
out:
dt_trans_stop(env, tgt->lut_bottom, th);
CDEBUG(D_INFO, "%s: update last_rcvd client data for UUID = %s, last_transno = %llu: rc = %d\n",
tgt->lut_obd->obd_name,
tgt->lut_lsd.lsd_uuid, tgt->lut_lsd.lsd_last_transno, rc);
return rc;
}
static int tgt_server_data_read(const struct lu_env *env, struct lu_target *tgt)
{
struct tgt_thread_info *tti = tgt_th_info(env);
int rc;
tti->tti_off = 0;
tti_buf_lsd(tti);
rc = dt_record_read(env, tgt->lut_last_rcvd, &tti->tti_buf,
&tti->tti_off);
if (rc == 0)
lsd_le_to_cpu(&tti->tti_lsd, &tgt->lut_lsd);
CDEBUG(D_INFO, "%s: read last_rcvd server data for UUID = %s, last_transno = %llu: rc = %d\n",
tgt->lut_obd->obd_name,
tgt->lut_lsd.lsd_uuid, tgt->lut_lsd.lsd_last_transno, rc);
return rc;
}
static int tgt_server_data_write(const struct lu_env *env,
struct lu_target *tgt, struct thandle *th)
{
struct tgt_thread_info *tti = tgt_th_info(env);
struct dt_object *dto;
int rc;
ENTRY;
tti->tti_off = 0;
tti_buf_lsd(tti);
lsd_cpu_to_le(&tgt->lut_lsd, &tti->tti_lsd);
dto = dt_object_locate(tgt->lut_last_rcvd, th->th_dev);
rc = dt_record_write(env, dto, &tti->tti_buf, &tti->tti_off, th);
CDEBUG(D_INFO, "%s: write last_rcvd server data for UUID = %s, last_transno = %llu: rc = %d\n",
tgt->lut_obd->obd_name,
tgt->lut_lsd.lsd_uuid, tgt->lut_lsd.lsd_last_transno, rc);
RETURN(rc);
}
/**
* Update server data in last_rcvd
*/
int tgt_server_data_update(const struct lu_env *env, struct lu_target *tgt,
int sync)
{
struct tgt_thread_info *tti = tgt_th_info(env);
struct thandle *th;
int rc = 0;
ENTRY;
CDEBUG(D_SUPER,
"%s: mount_count is %llu, last_transno is %llu\n",
tgt->lut_lsd.lsd_uuid, obd2obt(tgt->lut_obd)->obt_mount_count,
tgt->lut_last_transno);
/* Always save latest transno to keep it fresh */
spin_lock(&tgt->lut_translock);
tgt->lut_lsd.lsd_last_transno = tgt->lut_last_transno;
spin_unlock(&tgt->lut_translock);
tgt_update_max_clients(tgt);
if (tgt->lut_bottom->dd_rdonly)
RETURN(0);
th = dt_trans_create(env, tgt->lut_bottom);
if (IS_ERR(th))
RETURN(PTR_ERR(th));
th->th_sync = sync;
tti_buf_lsd(tti);
rc = dt_declare_record_write(env, tgt->lut_last_rcvd,
&tti->tti_buf, tti->tti_off, th);
if (rc)
GOTO(out, rc);
rc = dt_trans_start(env, tgt->lut_bottom, th);
if (rc)
GOTO(out, rc);
rc = tgt_server_data_write(env, tgt, th);
out:
dt_trans_stop(env, tgt->lut_bottom, th);
CDEBUG(D_INFO, "%s: update last_rcvd server data for UUID = %s, last_transno = %llu: rc = %d\n",
tgt->lut_obd->obd_name,
tgt->lut_lsd.lsd_uuid, tgt->lut_lsd.lsd_last_transno, rc);
RETURN(rc);
}
EXPORT_SYMBOL(tgt_server_data_update);
static int tgt_truncate_object(const struct lu_env *env, struct lu_target *tgt,
struct dt_object *dt, loff_t size)
{
struct thandle *th;
struct lu_attr attr;
int rc;
ENTRY;
if (tgt->lut_bottom->dd_rdonly)
RETURN(0);
attr.la_size = size;
attr.la_valid = LA_SIZE;
th = dt_trans_create(env, tgt->lut_bottom);
if (IS_ERR(th))
RETURN(PTR_ERR(th));
rc = dt_declare_punch(env, dt, size, OBD_OBJECT_EOF, th);
if (rc)
GOTO(cleanup, rc);
rc = dt_declare_attr_set(env, dt, &attr, th);
if (rc)
GOTO(cleanup, rc);
rc = dt_trans_start_local(env, tgt->lut_bottom, th);
if (rc)
GOTO(cleanup, rc);
rc = dt_punch(env, dt, size, OBD_OBJECT_EOF, th);
if (rc == 0)
rc = dt_attr_set(env, dt, &attr, th);
cleanup:
dt_trans_stop(env, tgt->lut_bottom, th);
RETURN(rc);
}
static void tgt_client_epoch_update(const struct lu_env *env,
struct obd_export *exp)
{
struct lsd_client_data *lcd = exp->exp_target_data.ted_lcd;
struct lu_target *tgt = class_exp2tgt(exp);
LASSERT(tgt && tgt->lut_bottom);
/** VBR: set client last_epoch to current epoch */
if (lcd->lcd_last_epoch >= tgt->lut_lsd.lsd_start_epoch)
return;
lcd->lcd_last_epoch = tgt->lut_lsd.lsd_start_epoch;
tgt_client_data_update(env, exp);
}
static int tgt_reply_data_upgrade_check(const struct lu_env *env,
struct lu_target *tgt)
{
struct lsd_reply_header *lrh = &tgt->lut_reply_header;
int rc;
/*
* Reply data is supported by MDT targets only for now.
* When reply data object @lut_reply_data is NULL, it indicates the
* target type is OST and it should skip the upgrade check.
*/
if (tgt->lut_reply_data == NULL)
RETURN(0);
rc = tgt_reply_header_read(env, tgt, lrh);
if (rc) {
CERROR("%s: failed to read %s: rc = %d\n",
tgt_name(tgt), REPLY_DATA, rc);
RETURN(rc);
}
if (lrh->lrh_magic == LRH_MAGIC)
RETURN(0);
rc = tgt_truncate_object(env, tgt, tgt->lut_reply_data, 0);
if (rc) {
CERROR("%s: failed to truncate %s: rc = %d\n",
tgt_name(tgt), REPLY_DATA, rc);
RETURN(rc);
}
lrh->lrh_magic = LRH_MAGIC;
lrh->lrh_header_size = sizeof(struct lsd_reply_header);
if (lrh->lrh_magic == LRH_MAGIC_V1)
lrh->lrh_reply_size = sizeof(struct lsd_reply_data_v1);
else
lrh->lrh_reply_size = sizeof(struct lsd_reply_data_v2);
rc = tgt_reply_header_write(env, tgt, lrh);
if (rc)
CERROR("%s: failed to write header for %s: rc = %d\n",
tgt_name(tgt), REPLY_DATA, rc);
RETURN(rc);
}
/**
* Update boot epoch when recovery ends
*/
void tgt_boot_epoch_update(struct lu_target *tgt)
{
struct lu_env env;
struct ptlrpc_request *req;
__u32 start_epoch;
LIST_HEAD(client_list);
int rc;
if (test_bit(OBDF_STOPPING, tgt->lut_obd->obd_flags))
return;
rc = lu_env_init(&env, LCT_DT_THREAD);
if (rc) {
CERROR("%s: can't initialize environment: rc = %d\n",
tgt->lut_obd->obd_name, rc);
return;
}
spin_lock(&tgt->lut_translock);
start_epoch = (tgt->lut_last_transno >> LR_EPOCH_BITS) + 1;
tgt->lut_last_transno = (__u64)start_epoch << LR_EPOCH_BITS;
tgt->lut_lsd.lsd_start_epoch = start_epoch;
spin_unlock(&tgt->lut_translock);
/**
* The recovery is not yet finished and final queue can still be updated
* with resend requests. Move final list to separate one for processing
*/
spin_lock(&tgt->lut_obd->obd_recovery_task_lock);
list_splice_init(&tgt->lut_obd->obd_final_req_queue, &client_list);
spin_unlock(&tgt->lut_obd->obd_recovery_task_lock);
/**
* go through list of exports participated in recovery and
* set new epoch for them
*/
list_for_each_entry(req, &client_list, rq_list) {
LASSERT(!req->rq_export->exp_delayed);
if (!req->rq_export->exp_vbr_failed)
tgt_client_epoch_update(&env, req->rq_export);
}
/** return list back at once */
spin_lock(&tgt->lut_obd->obd_recovery_task_lock);
list_splice_init(&client_list, &tgt->lut_obd->obd_final_req_queue);
spin_unlock(&tgt->lut_obd->obd_recovery_task_lock);
/**
* Clear MULTI RPCS incompatibility flag if there is no multi-rpcs
* client in last_rcvd file
*/
if (atomic_read(&tgt->lut_num_clients) == 0)
tgt->lut_lsd.lsd_feature_incompat &= ~OBD_INCOMPAT_MULTI_RPCS;
/** update server epoch */
tgt_server_data_update(&env, tgt, 1);
tgt_reply_data_upgrade_check(&env, tgt);
lu_env_fini(&env);
}
/**
* commit callback, need to update last_committed value
*/
struct tgt_last_committed_callback {
struct dt_txn_commit_cb llcc_cb;
struct lu_target *llcc_tgt;
struct obd_export *llcc_exp;
__u64 llcc_transno;
};
static void tgt_cb_last_committed(struct lu_env *env, struct thandle *th,
struct dt_txn_commit_cb *cb, int err)
{
struct tgt_last_committed_callback *ccb;
ccb = container_of(cb, struct tgt_last_committed_callback, llcc_cb);
LASSERT(ccb->llcc_exp);
LASSERT(ccb->llcc_tgt != NULL);
LASSERT(ccb->llcc_exp->exp_obd == ccb->llcc_tgt->lut_obd);
if (th->th_reserved_quota.lqi_space > 0) {
CDEBUG(D_QUOTA, "free quota %llu %llu\n",
th->th_reserved_quota.lqi_id.qid_gid,
th->th_reserved_quota.lqi_space);
/* env can be NULL for freeing reserved quota */
th->th_reserved_quota.lqi_space *= -1;
dt_reserve_or_free_quota(NULL, th->th_dev,
&th->th_reserved_quota);
}
/* error hit, don't update last committed to provide chance to
* replay data after fail */
if (err != 0)
goto out;
/* Fast path w/o spinlock, if exp_last_committed was updated
* with higher transno, no need to take spinlock and check,
* also no need to update obd_last_committed. */
if (ccb->llcc_transno <= ccb->llcc_exp->exp_last_committed)
goto out;
spin_lock(&ccb->llcc_tgt->lut_translock);
if (ccb->llcc_transno > ccb->llcc_tgt->lut_obd->obd_last_committed)
ccb->llcc_tgt->lut_obd->obd_last_committed = ccb->llcc_transno;
if (ccb->llcc_transno > ccb->llcc_exp->exp_last_committed) {
ccb->llcc_exp->exp_last_committed = ccb->llcc_transno;
spin_unlock(&ccb->llcc_tgt->lut_translock);
ptlrpc_commit_replies(ccb->llcc_exp);
tgt_cancel_slc_locks(ccb->llcc_tgt, ccb->llcc_transno);
} else {
spin_unlock(&ccb->llcc_tgt->lut_translock);
}
CDEBUG(D_HA, "%s: transno %lld is committed\n",
ccb->llcc_tgt->lut_obd->obd_name, ccb->llcc_transno);
out:
class_export_cb_put(ccb->llcc_exp);
OBD_FREE_PTR(ccb);
}
/**
* Add commit callback function, it returns a non-zero value to inform
* caller to use sync transaction if necessary.
*/
static int tgt_last_commit_cb_add(struct thandle *th, struct lu_target *tgt,
struct obd_export *exp, __u64 transno)
{
struct tgt_last_committed_callback *ccb;
struct dt_txn_commit_cb *dcb;
int rc;
OBD_ALLOC_PTR(ccb);
if (ccb == NULL)
return -ENOMEM;
ccb->llcc_tgt = tgt;
ccb->llcc_exp = class_export_cb_get(exp);
ccb->llcc_transno = transno;
dcb = &ccb->llcc_cb;
dcb->dcb_func = tgt_cb_last_committed;
INIT_LIST_HEAD(&dcb->dcb_linkage);
strscpy(dcb->dcb_name, "tgt_cb_last_committed", sizeof(dcb->dcb_name));
rc = dt_trans_cb_add(th, dcb);
if (rc) {
class_export_cb_put(exp);
OBD_FREE_PTR(ccb);
}
if (exp_connect_flags(exp) & OBD_CONNECT_LIGHTWEIGHT)
/* report failure to force synchronous operation */
return -EPERM;
/* if exp_need_sync is set, return non-zero value to force
* a sync transaction. */
return rc ? rc : exp->exp_need_sync;
}
static int tgt_is_local_client(const struct lu_env *env,
struct obd_export *exp)
{
struct lu_target *tgt = class_exp2tgt(exp);
struct tgt_session_info *tsi = tgt_ses_info(env);
struct ptlrpc_request *req = tgt_ses_req(tsi);
if (exp_connect_flags(exp) & OBD_CONNECT_MDS ||
exp_connect_flags(exp) & OBD_CONNECT_MDS_MDS)
return 0;
if (tgt->lut_local_recovery)
return 0;
if (!req)
return 0;
if (!LNetIsPeerLocal(&req->rq_peer.nid))
return 0;
return 1;
}
/**
* Add new client to the last_rcvd upon new connection.
*
* We use a bitmap to locate a free space in the last_rcvd file and initialize
* tg_export_data.
*/
int tgt_client_new(const struct lu_env *env, struct obd_export *exp)
{
struct tg_export_data *ted = &exp->exp_target_data;
struct lu_target *tgt = class_exp2tgt(exp);
int rc = 0, idx;
ENTRY;
LASSERT(tgt && tgt->lut_client_bitmap != NULL);
if (!strcmp(ted->ted_lcd->lcd_uuid, tgt->lut_obd->obd_uuid.uuid))
RETURN(0);
if (exp_connect_flags(exp) & OBD_CONNECT_LIGHTWEIGHT)
RETURN(0);
if (tgt_is_local_client(env, exp)) {
LCONSOLE_WARN("%s: local client %s w/o recovery\n",
exp->exp_obd->obd_name, ted->ted_lcd->lcd_uuid);
exp->exp_no_recovery = 1;
RETURN(0);
}
/* the bitmap operations can handle cl_idx > sizeof(long) * 8, so
* there's no need for extra complication here
*/
idx = find_first_zero_bit(tgt->lut_client_bitmap, LR_MAX_CLIENTS);
repeat:
if (idx >= LR_MAX_CLIENTS ||
CFS_FAIL_CHECK(OBD_FAIL_MDS_CLIENT_ADD)) {
CERROR("%s: no room for %u clients - fix LR_MAX_CLIENTS\n",
tgt->lut_obd->obd_name, idx);
RETURN(-EOVERFLOW);
}
if (test_and_set_bit(idx, tgt->lut_client_bitmap)) {
idx = find_next_zero_bit(tgt->lut_client_bitmap,
LR_MAX_CLIENTS, idx);
goto repeat;
}
ted->ted_lr_idx = idx;
ted->ted_lr_off = tgt->lut_lsd.lsd_client_start +
idx * tgt->lut_lsd.lsd_client_size;
LASSERTF(ted->ted_lr_off > 0, "ted_lr_off = %llu\n", ted->ted_lr_off);
if (tgt_is_multimodrpcs_client(exp)) {
/* Set MULTI RPCS incompatibility flag to prevent previous
* Lustre versions to mount a target with reply_data file */
if (!(tgt->lut_lsd.lsd_feature_incompat &
OBD_INCOMPAT_MULTI_RPCS)) {
tgt->lut_lsd.lsd_feature_incompat |=
OBD_INCOMPAT_MULTI_RPCS;
rc = tgt_server_data_update(env, tgt, 1);
if (rc < 0) {
CERROR("%s: unable to set MULTI RPCS incompatibility flag\n",
exp->exp_obd->obd_name);
RETURN(rc);
}
}
/* assign client slot generation */
ted->ted_lcd->lcd_generation =
atomic_inc_return(&tgt->lut_client_generation);
} else {
ted->ted_lcd->lcd_generation = 0;
}
CDEBUG(D_INFO, "%s: new client at index %d (%llu) with UUID '%s' generation %d\n",
tgt->lut_obd->obd_name, ted->ted_lr_idx, ted->ted_lr_off,
ted->ted_lcd->lcd_uuid, ted->ted_lcd->lcd_generation);
if (CFS_FAIL_CHECK(OBD_FAIL_TGT_CLIENT_ADD))
RETURN(-ENOSPC);
rc = tgt_client_data_update(env, exp);
if (rc) {
CERROR("%s: Failed to write client lcd at idx %d, rc %d\n",
tgt->lut_obd->obd_name, idx, rc);
RETURN(rc);
}
if (tgt_is_multimodrpcs_client(exp)) {
atomic_inc(&tgt->lut_num_clients);
tgt_update_max_clients(tgt);
}
RETURN(0);
}
EXPORT_SYMBOL(tgt_client_new);
/* Add an existing client to the MDS in-memory state based on
* a client that was previously found in the last_rcvd file and
* already has an assigned slot (idx >= 0).
*
* It should not be possible to fail adding an existing client - otherwise
* mdt_init_server_data() callsite needs to be fixed.
*/
int tgt_client_add(const struct lu_env *env, struct obd_export *exp, int idx)
{
struct tg_export_data *ted = &exp->exp_target_data;
struct lu_target *tgt = class_exp2tgt(exp);
ENTRY;
LASSERT(tgt && tgt->lut_client_bitmap != NULL);
LASSERTF(idx >= 0, "%d\n", idx);
if (!strcmp(ted->ted_lcd->lcd_uuid, tgt->lut_obd->obd_uuid.uuid) ||
exp_connect_flags(exp) & OBD_CONNECT_LIGHTWEIGHT)
RETURN(0);
LASSERTF(!test_and_set_bit(idx, tgt->lut_client_bitmap),
"%s: client %d: bit already set in bitmap!!\n",
tgt->lut_obd->obd_name, idx);
CDEBUG(D_INFO, "%s: client at idx %d with UUID '%s' added, generation %d\n",
tgt->lut_obd->obd_name, idx, ted->ted_lcd->lcd_uuid,
ted->ted_lcd->lcd_generation);
ted->ted_lr_idx = idx;
ted->ted_lr_off = tgt->lut_lsd.lsd_client_start +
idx * tgt->lut_lsd.lsd_client_size;
mutex_init(&ted->ted_lcd_lock);
LASSERTF(ted->ted_lr_off > 0, "ted_lr_off = %llu\n", ted->ted_lr_off);
RETURN(0);
}
int tgt_client_del(const struct lu_env *env, struct obd_export *exp)
{
struct tg_export_data *ted = &exp->exp_target_data;
struct lu_target *tgt = class_exp2tgt(exp);
int rc;
ENTRY;
LASSERT(ted->ted_lcd);
if (unlikely(tgt == NULL)) {
CDEBUG(D_ERROR, "%s: No target for connected export\n",
class_exp2obd(exp)->obd_name);
RETURN(-EINVAL);
}
/* XXX if lcd_uuid were a real obd_uuid, I could use obd_uuid_equals */
if (!strcmp((char *)ted->ted_lcd->lcd_uuid,
(char *)tgt->lut_obd->obd_uuid.uuid) ||
exp_connect_flags(exp) & OBD_CONNECT_LIGHTWEIGHT ||
exp->exp_no_recovery)
RETURN(0);
/* Slot may be not yet assigned, use case is race between Client
* reconnect and forced eviction */
if (ted->ted_lr_idx < 0) {
CWARN("%s: client with UUID '%s' not in bitmap\n",
tgt->lut_obd->obd_name, ted->ted_lcd->lcd_uuid);
RETURN(0);
}
CDEBUG(D_INFO, "%s: del client at idx %u, off %lld, UUID '%s'\n",
tgt->lut_obd->obd_name, ted->ted_lr_idx, ted->ted_lr_off,
ted->ted_lcd->lcd_uuid);
/* Clear the bit _after_ zeroing out the client so we don't
race with filter_client_add and zero out new clients.*/
LASSERTF(test_bit(ted->ted_lr_idx, tgt->lut_client_bitmap),
"%s: client %u: bit already clear in bitmap!!\n",
tgt->lut_obd->obd_name, ted->ted_lr_idx);
/* Do not erase record for recoverable client. */
if (exp->exp_flags & OBD_OPT_FAILOVER)
RETURN(0);
if (CFS_FAIL_CHECK(OBD_FAIL_TGT_CLIENT_DEL))
RETURN(0);
/* Make sure the server's last_transno is up to date.
* This should be done before zeroing client slot so last_transno will
* be in server data or in client data in case of failure */
rc = tgt_server_data_update(env, tgt, 0);
if (rc != 0) {
CERROR("%s: failed to update server data, skip client %s zeroing, rc %d\n",
tgt->lut_obd->obd_name,
ted->ted_lcd->lcd_uuid, rc);
RETURN(rc);
}
/* Race between an eviction and a disconnection ?*/
mutex_lock(&ted->ted_lcd_lock);
if (ted->ted_lcd->lcd_uuid[0] == '\0') {
mutex_unlock(&ted->ted_lcd_lock);
RETURN(rc);
}
memset(ted->ted_lcd->lcd_uuid, 0, sizeof(ted->ted_lcd->lcd_uuid));
mutex_unlock(&ted->ted_lcd_lock);
rc = tgt_client_data_update(env, exp);
if (!rc && tgt_is_multimodrpcs_record(tgt, ted->ted_lcd))
atomic_dec(&tgt->lut_num_clients);
CDEBUG(rc == 0 ? D_INFO : D_ERROR,
"%s: zeroing out client %s at idx %u (%llu), rc %d\n",
tgt->lut_obd->obd_name, ted->ted_lcd->lcd_uuid,
ted->ted_lr_idx, ted->ted_lr_off, rc);
RETURN(rc);
}
EXPORT_SYMBOL(tgt_client_del);
static void tgt_clean_by_tag(struct obd_export *exp, __u64 xid, __u16 tag)
{
struct tg_export_data *ted = &exp->exp_target_data;
struct lu_target *lut = class_exp2tgt(exp);
struct tg_reply_data *trd, *tmp;
if (tag == 0)
return;
list_for_each_entry_safe(trd, tmp, &ted->ted_reply_list, trd_list) {
if (trd->trd_tag != tag)
continue;
LASSERT(ergo(tgt_is_increasing_xid_client(exp),
trd->trd_reply.lrd_xid <= xid));
ted->ted_release_tag++;
tgt_release_reply_data(lut, ted, trd);
}
}
static int tgt_add_reply_data(const struct lu_env *env, struct lu_target *tgt,
struct tg_export_data *ted, struct tg_reply_data *trd,
struct ptlrpc_request *req,
struct thandle *th, bool update_lrd_file)
{
struct tgt_session_info *tsi = NULL;
struct lsd_reply_data *lrd;
int i = -1;
int rc;
lrd = &trd->trd_reply;
/* update export last transno */
mutex_lock(&ted->ted_lcd_lock);
if (lrd->lrd_transno > ted->ted_lcd->lcd_last_transno)
ted->ted_lcd->lcd_last_transno = lrd->lrd_transno;
mutex_unlock(&ted->ted_lcd_lock);
if (!tgt) {
trd->trd_index = TRD_INDEX_MEMORY;
GOTO(add_reply_data, rc = 0);
}
if (env) {
tsi = tgt_ses_info(env);
if (tsi->tsi_batch_trd) {
LASSERT(tsi->tsi_batch_env);
trd = tsi->tsi_batch_trd;
i = trd->trd_index;
}
}
if (i == -1) {
/* find a empty slot */
i = tgt_find_free_reply_slot(tgt);
if (unlikely(i < 0)) {
CERROR("%s: couldn't find a slot for reply data: rc = %d\n",
tgt_name(tgt), i);
RETURN(i);
}
trd->trd_index = i;
}
if (update_lrd_file) {
struct lsd_reply_header *lrh = &tgt->lut_reply_header;
loff_t off;
/* write reply data to disk */
off = lrh->lrh_header_size + lrh->lrh_reply_size * i;
rc = tgt_reply_data_write(env, tgt, lrd, off, th);
if (unlikely(rc != 0)) {
CERROR("%s: can't update %s file: rc = %d\n",
tgt_name(tgt), REPLY_DATA, rc);
GOTO(free_slot, rc);
}
}
add_reply_data:
/* add reply data to target export's reply list */
mutex_lock(&ted->ted_lcd_lock);
if (req != NULL) {
int exclude = tgt_is_increasing_xid_client(req->rq_export) ?
MSG_REPLAY : MSG_REPLAY|MSG_RESENT;
if (req->rq_obsolete) {
CDEBUG(D_INFO,
"drop reply data update for obsolete req xid=%llu,transno=%llu, tag=%hu\n",
req->rq_xid,
lrd->lrd_transno, trd->trd_tag);
mutex_unlock(&ted->ted_lcd_lock);
GOTO(free_slot, rc = -EBADR);
}
if (!(lustre_msg_get_flags(req->rq_reqmsg) & exclude) &&
!(tsi && tsi->tsi_batch_env &&
trd->trd_reply.lrd_batch_idx > 0))
tgt_clean_by_tag(req->rq_export, req->rq_xid,
trd->trd_tag);
}
/*
* For the batched RPC, all sub requests use one common @trd for the
* reply data.
*/
if (list_empty(&trd->trd_list)) {
list_add(&trd->trd_list, &ted->ted_reply_list);
ted->ted_reply_cnt++;
if (ted->ted_reply_cnt > ted->ted_reply_max)
ted->ted_reply_max = ted->ted_reply_cnt;
}
mutex_unlock(&ted->ted_lcd_lock);
CDEBUG(D_TRACE, "add reply %p: xid %llu, transno %llu, tag %hu, client gen %u, slot idx %d\n",
trd, lrd->lrd_xid, lrd->lrd_transno,
trd->trd_tag, lrd->lrd_client_gen, trd->trd_index);
RETURN(0);
free_slot:
if (tgt != NULL)
tgt_clear_reply_slot(tgt, trd->trd_index);
return rc;
}
int tgt_mk_reply_data(const struct lu_env *env,
struct lu_target *tgt,
struct tg_export_data *ted,
struct ptlrpc_request *req,
__u64 opdata,
struct thandle *th,
bool write_update,
__u64 transno)
{
struct tg_reply_data *trd = NULL;
struct lsd_reply_data *lrd;
__u64 *pre_versions = NULL;
struct tgt_session_info *tsi = NULL;
int rc;
if (env != NULL) {
tsi = tgt_ses_info(env);
if (tsi->tsi_batch_trd) {
LASSERT(tsi->tsi_batch_env);
trd = tsi->tsi_batch_trd;
}
}
if (trd == NULL) {
OBD_ALLOC_PTR(trd);
if (unlikely(trd == NULL))
RETURN(-ENOMEM);
INIT_LIST_HEAD(&trd->trd_list);
}
/* fill reply data information */
lrd = &trd->trd_reply;
lrd->lrd_transno = transno;
if (tsi && tsi->tsi_batch_env) {
if (tsi->tsi_batch_idx == 0) {
LASSERT(req != NULL);
tsi->tsi_batch_trd = trd;
trd->trd_index = -1;
lrd->lrd_xid = req->rq_xid;
trd->trd_tag = lustre_msg_get_tag(req->rq_reqmsg);
lrd->lrd_client_gen = ted->ted_lcd->lcd_generation;
}
lrd->lrd_batch_idx = tsi->tsi_batch_idx;
} else if (req != NULL) {
lrd->lrd_xid = req->rq_xid;
trd->trd_tag = lustre_msg_get_tag(req->rq_reqmsg);
lrd->lrd_client_gen = ted->ted_lcd->lcd_generation;
if (write_update) {
pre_versions = lustre_msg_get_versions(req->rq_repmsg);
lrd->lrd_result = th->th_result;
}
} else {
LASSERT(env != NULL);
LASSERT(tsi->tsi_xid != 0);
lrd->lrd_xid = tsi->tsi_xid;
lrd->lrd_result = tsi->tsi_result;
lrd->lrd_client_gen = tsi->tsi_client_gen;
}
lrd->lrd_data = opdata;
if (pre_versions) {
trd->trd_pre_versions[0] = pre_versions[0];
trd->trd_pre_versions[1] = pre_versions[1];
trd->trd_pre_versions[2] = pre_versions[2];
trd->trd_pre_versions[3] = pre_versions[3];
}
if (tsi && tsi->tsi_open_obj)
trd->trd_object = *lu_object_fid(&tsi->tsi_open_obj->do_lu);
rc = tgt_add_reply_data(env, tgt, ted, trd, req,
th, write_update);
if (rc < 0) {
OBD_FREE_PTR(trd);
if (rc == -EBADR)
rc = 0;
}
return rc;
}
EXPORT_SYMBOL(tgt_mk_reply_data);
/*
* last_rcvd & last_committed update callbacks
*/
static int tgt_last_rcvd_update(const struct lu_env *env, struct lu_target *tgt,
struct dt_object *obj, __u64 opdata,
struct thandle *th, struct ptlrpc_request *req)
{
struct tgt_thread_info *tti = tgt_th_info(env);
struct tgt_session_info *tsi = tgt_ses_info(env);
struct obd_export *exp = tsi->tsi_exp;
struct tg_export_data *ted;
__u64 *transno_p;
bool nolcd = false;
int rc = 0;
ENTRY;
LASSERT(exp != NULL);
ted = &exp->exp_target_data;
/* Some clients don't support recovery, and they don't have last_rcvd
* client data:
* 1. lightweight clients.
* 2. local clients on MDS which doesn't enable "localrecov".
* 3. OFD connect may cause transaction before export has last_rcvd
* slot.
*/
if (ted->ted_lr_idx < 0)
nolcd = true;
if (req != NULL)
tti->tti_transno = lustre_msg_get_transno(req->rq_reqmsg);
else
/* From update replay, tti_transno should be set already */
LASSERT(tti->tti_transno != 0);
spin_lock(&tgt->lut_translock);
if (th->th_result != 0) {
if (tti->tti_transno != 0) {
CERROR("%s: replay transno %llu failed: rc = %d\n",
tgt_name(tgt), tti->tti_transno, th->th_result);
}
} else if (tti->tti_transno == 0) {
tti->tti_transno = ++tgt->lut_last_transno;
} else {
/* should be replay */
if (tti->tti_transno > tgt->lut_last_transno)
tgt->lut_last_transno = tti->tti_transno;
}
spin_unlock(&tgt->lut_translock);
/** VBR: set new versions */
if (th->th_result == 0 && obj != NULL) {
struct dt_object *dto = dt_object_locate(obj, th->th_dev);
dt_version_set(env, dto, tti->tti_transno, th);
if (unlikely(tsi->tsi_dv_update))
dt_data_version_set(env, dto, tti->tti_transno, th);
}
/* filling reply data */
CDEBUG(D_INODE, "transno = %llu, last_committed = %llu\n",
tti->tti_transno, tgt->lut_obd->obd_last_committed);
if (req != NULL) {
req->rq_transno = tti->tti_transno;
lustre_msg_set_transno(req->rq_repmsg, tti->tti_transno);
}
/* if can't add callback, do sync write */
th->th_sync |= !!tgt_last_commit_cb_add(th, tgt, exp, tti->tti_transno);
if (nolcd) {
/* store transno in the last_rcvd header */
spin_lock(&tgt->lut_translock);
if (tti->tti_transno > tgt->lut_lsd.lsd_last_transno) {
tgt->lut_lsd.lsd_last_transno = tti->tti_transno;
spin_unlock(&tgt->lut_translock);
/* Although current connection doesn't have slot
* in the last_rcvd, we still want to maintain
* the in-memory lsd_client_data structure in order to
* properly handle reply reconstruction. */
rc = tgt_server_data_write(env, tgt, th);
} else {
spin_unlock(&tgt->lut_translock);
}
} else if (ted->ted_lr_off == 0) {
CERROR("%s: client idx %d has offset %lld\n",
tgt_name(tgt), ted->ted_lr_idx, ted->ted_lr_off);
RETURN(-EINVAL);
}
/* Target that supports multiple reply data */
if (tgt_is_multimodrpcs_client(exp)) {
return tgt_mk_reply_data(env, tgt, ted, req, opdata, th,
!!(req != NULL), tti->tti_transno);
}
/* Enough for update replay, let's return */
if (req == NULL)
RETURN(rc);
mutex_lock(&ted->ted_lcd_lock);
LASSERT(ergo(tti->tti_transno == 0, th->th_result != 0));
if (lustre_msg_get_opc(req->rq_reqmsg) == MDS_CLOSE) {
transno_p = &ted->ted_lcd->lcd_last_close_transno;
ted->ted_lcd->lcd_last_close_xid = req->rq_xid;
ted->ted_lcd->lcd_last_close_result = th->th_result;
} else {
/* VBR: save versions in last_rcvd for reconstruct. */
__u64 *pre_versions = lustre_msg_get_versions(req->rq_repmsg);
if (pre_versions) {
ted->ted_lcd->lcd_pre_versions[0] = pre_versions[0];
ted->ted_lcd->lcd_pre_versions[1] = pre_versions[1];
ted->ted_lcd->lcd_pre_versions[2] = pre_versions[2];
ted->ted_lcd->lcd_pre_versions[3] = pre_versions[3];
}
transno_p = &ted->ted_lcd->lcd_last_transno;
ted->ted_lcd->lcd_last_xid = req->rq_xid;
ted->ted_lcd->lcd_last_result = th->th_result;
/* XXX: lcd_last_data is __u32 but intent_dispostion is __u64,
* see struct ldlm_reply->lock_policy_res1; */
ted->ted_lcd->lcd_last_data = opdata;
}
/* Update transno in slot only if non-zero number, i.e. no errors */
if (likely(tti->tti_transno != 0)) {
/* Don't overwrite bigger transaction number with lower one.
* That is not sign of problem in all cases, but in any case
* this value should be monotonically increased only. */
if (*transno_p > tti->tti_transno) {
if (!tgt->lut_no_reconstruct) {
CERROR("%s: trying to overwrite bigger transno:on-disk: %llu, new: %llu replay: "
"%d. See LU-617.\n", tgt_name(tgt),
*transno_p, tti->tti_transno,
req_is_replay(req));
if (req_is_replay(req)) {
spin_lock(&req->rq_export->exp_lock);
req->rq_export->exp_vbr_failed = 1;
spin_unlock(&req->rq_export->exp_lock);
}
mutex_unlock(&ted->ted_lcd_lock);
RETURN(req_is_replay(req) ? -EOVERFLOW : 0);
}
} else {
*transno_p = tti->tti_transno;
}
}
if (!nolcd) {
tti->tti_off = ted->ted_lr_off;
if (CFS_FAIL_CHECK(OBD_FAIL_TGT_RCVD_EIO))
rc = -EIO;
else
rc = tgt_client_data_write(env, tgt, ted->ted_lcd,
&tti->tti_off, th);
if (rc < 0) {
mutex_unlock(&ted->ted_lcd_lock);
RETURN(rc);
}
}
mutex_unlock(&ted->ted_lcd_lock);
RETURN(rc);
}
/*
* last_rcvd update for echo client simulation.
* It updates last_rcvd client slot and version of object in
* simple way but with all locks to simulate all drawbacks
*/
static int tgt_last_rcvd_update_echo(const struct lu_env *env,
struct lu_target *tgt,
struct dt_object *obj,
struct thandle *th,
struct obd_export *exp)
{
struct tgt_thread_info *tti = tgt_th_info(env);
struct tg_export_data *ted = &exp->exp_target_data;
int rc = 0;
ENTRY;
tti->tti_transno = 0;
spin_lock(&tgt->lut_translock);
if (th->th_result == 0)
tti->tti_transno = ++tgt->lut_last_transno;
spin_unlock(&tgt->lut_translock);
/** VBR: set new versions */
if (th->th_result == 0 && obj != NULL)
dt_version_set(env, obj, tti->tti_transno, th);
/* if can't add callback, do sync write */
th->th_sync |= !!tgt_last_commit_cb_add(th, tgt, exp,
tti->tti_transno);
LASSERT(ted->ted_lr_off > 0);
mutex_lock(&ted->ted_lcd_lock);
LASSERT(ergo(tti->tti_transno == 0, th->th_result != 0));
ted->ted_lcd->lcd_last_transno = tti->tti_transno;
ted->ted_lcd->lcd_last_result = th->th_result;
tti->tti_off = ted->ted_lr_off;
rc = tgt_client_data_write(env, tgt, ted->ted_lcd, &tti->tti_off, th);
mutex_unlock(&ted->ted_lcd_lock);
RETURN(rc);
}
static int tgt_clients_data_init(const struct lu_env *env,
struct lu_target *tgt,
unsigned long last_size)
{
struct obd_device *obd = tgt->lut_obd;
struct lr_server_data *lsd = &tgt->lut_lsd;
struct lsd_client_data *lcd = NULL;
struct tg_export_data *ted;
int cl_idx;
int rc = 0;
loff_t off = lsd->lsd_client_start;
__u32 generation = 0;
struct cfs_hash *hash = NULL;
ENTRY;
if (tgt->lut_bottom->dd_rdonly)
RETURN(0);
BUILD_BUG_ON(offsetof(struct lsd_client_data, lcd_padding) +
sizeof(lcd->lcd_padding) != LR_CLIENT_SIZE);
OBD_ALLOC_PTR(lcd);
if (lcd == NULL)
RETURN(-ENOMEM);
hash = cfs_hash_getref(tgt->lut_obd->obd_gen_hash);
if (hash == NULL)
GOTO(err_out, rc = -ENODEV);
for (cl_idx = 0; off < last_size; cl_idx++) {
struct obd_export *exp;
__u64 last_transno;
/* Don't assume off is incremented properly by
* read_record(), in case sizeof(*lcd)
* isn't the same as fsd->lsd_client_size. */
off = lsd->lsd_client_start + cl_idx * lsd->lsd_client_size;
rc = tgt_client_data_read(env, tgt, lcd, &off, cl_idx);
if (rc) {
CERROR("%s: error reading last_rcvd %s idx %d off %llu: rc = %d\n",
tgt_name(tgt), LAST_RCVD,
cl_idx, off, rc);
rc = 0;
break; /* read error shouldn't cause startup to fail */
}
if (lcd->lcd_uuid[0] == '\0') {
CDEBUG(D_INFO, "skipping zeroed client at offset %d\n",
cl_idx);
continue;
}
last_transno = lcd_last_transno(lcd);
/* These exports are cleaned up by disconnect, so they
* need to be set up like real exports as connect does.
*/
CDEBUG(D_HA, "RCVRNG CLIENT uuid: %s idx: %d lr: %llu srv lr: %llu lx: %llu gen %u\n",
lcd->lcd_uuid,
cl_idx, last_transno, lsd->lsd_last_transno,
lcd_last_xid(lcd), lcd->lcd_generation);
exp = class_new_export(obd, (struct obd_uuid *)lcd->lcd_uuid);
if (IS_ERR(exp)) {
if (PTR_ERR(exp) == -EALREADY) {
/* export already exists, zero out this one */
CERROR("%s: Duplicate export %s!\n",
tgt_name(tgt), lcd->lcd_uuid);
continue;
}
GOTO(err_out, rc = PTR_ERR(exp));
}
ted = &exp->exp_target_data;
*ted->ted_lcd = *lcd;
rc = tgt_client_add(env, exp, cl_idx);
LASSERTF(rc == 0, "rc = %d\n", rc); /* can't fail existing */
/* VBR: set export last committed version */
exp->exp_last_committed = last_transno;
spin_lock(&exp->exp_lock);
exp->exp_connecting = 0;
exp->exp_in_recovery = 0;
spin_unlock(&exp->exp_lock);
atomic_inc(&obd->obd_max_recoverable_clients);
if (tgt_is_multimodrpcs_record(tgt, lcd)) {
atomic_inc(&tgt->lut_num_clients);
tgt_update_max_clients(tgt);
/* compute the highest valid client generation */
generation = max(generation, lcd->lcd_generation);
/* fill client_generation <-> export hash table */
rc = cfs_hash_add_unique(hash, &lcd->lcd_generation,
&exp->exp_gen_hash);
if (rc != 0) {
CERROR("%s: duplicate export for client generation %u\n",
tgt_name(tgt), lcd->lcd_generation);
class_export_put(exp);
GOTO(err_out, rc);
}
}
class_export_put(exp);
/* Need to check last_rcvd even for duplicated exports. */
CDEBUG(D_OTHER, "client at idx %d has last_transno = %llu\n",
cl_idx, last_transno);
spin_lock(&tgt->lut_translock);
tgt->lut_last_transno = max(last_transno,
tgt->lut_last_transno);
spin_unlock(&tgt->lut_translock);
}
/* record highest valid client generation */
atomic_set(&tgt->lut_client_generation, generation);
err_out:
if (hash != NULL)
cfs_hash_putref(hash);
OBD_FREE_PTR(lcd);
RETURN(rc);
}
struct server_compat_data {
__u32 rocompat;
__u32 incompat;
__u32 rocinit;
__u32 incinit;
};
static struct server_compat_data tgt_scd[] = {
[LDD_F_SV_TYPE_MDT] = {
.rocompat = OBD_ROCOMPAT_LOVOBJID,
.incompat = OBD_INCOMPAT_MDT | OBD_INCOMPAT_COMMON_LR |
OBD_INCOMPAT_FID | OBD_INCOMPAT_IAM_DIR |
OBD_INCOMPAT_LMM_VER | OBD_INCOMPAT_MULTI_OI |
OBD_INCOMPAT_MULTI_RPCS,
.rocinit = OBD_ROCOMPAT_LOVOBJID,
.incinit = OBD_INCOMPAT_MDT | OBD_INCOMPAT_COMMON_LR |
OBD_INCOMPAT_MULTI_OI,
},
[LDD_F_SV_TYPE_OST] = {
.rocompat = OBD_ROCOMPAT_IDX_IN_IDIF,
.incompat = OBD_INCOMPAT_OST | OBD_INCOMPAT_COMMON_LR |
OBD_INCOMPAT_FID,
.rocinit = OBD_ROCOMPAT_IDX_IN_IDIF,
.incinit = OBD_INCOMPAT_OST | OBD_INCOMPAT_COMMON_LR,
}
};
int tgt_server_data_init(const struct lu_env *env, struct lu_target *tgt)
{
struct tgt_thread_info *tti = tgt_th_info(env);
struct lr_server_data *lsd = &tgt->lut_lsd;
unsigned long last_rcvd_size;
__u32 index;
int rc, type;
rc = dt_attr_get(env, tgt->lut_last_rcvd, &tti->tti_attr);
if (rc)
RETURN(rc);
last_rcvd_size = (unsigned long)tti->tti_attr.la_size;
/* ensure padding in the struct is the correct size */
BUILD_BUG_ON(offsetof(struct lr_server_data, lsd_padding) +
sizeof(lsd->lsd_padding) != LR_SERVER_SIZE);
rc = server_name2index(tgt_name(tgt), &index, NULL);
if (rc < 0) {
CERROR("%s: Can not get index from name: rc = %d\n",
tgt_name(tgt), rc);
RETURN(rc);
}
/* server_name2index() returns type */
type = rc;
if (type != LDD_F_SV_TYPE_MDT && type != LDD_F_SV_TYPE_OST) {
CERROR("%s: unknown target type %x\n", tgt_name(tgt), type);
RETURN(-EINVAL);
}
/* last_rcvd on OST doesn't provide reconstruct support because there
* may be up to 8 in-flight write requests per single slot in
* last_rcvd client data
*/
tgt->lut_no_reconstruct = (type == LDD_F_SV_TYPE_OST);
if (last_rcvd_size == 0) {
LCONSOLE_WARN("%s: new disk, initializing\n", tgt_name(tgt));
memcpy(lsd->lsd_uuid, tgt->lut_obd->obd_uuid.uuid,
sizeof(lsd->lsd_uuid));
lsd->lsd_last_transno = 0;
lsd->lsd_mount_count = 0;
lsd->lsd_server_size = LR_SERVER_SIZE;
lsd->lsd_client_start = LR_CLIENT_START;
lsd->lsd_client_size = LR_CLIENT_SIZE;
lsd->lsd_subdir_count = OBJ_SUBDIR_COUNT;
lsd->lsd_osd_index = index;
lsd->lsd_max_clients = 0;
lsd->lsd_feature_rocompat = tgt_scd[type].rocinit;
lsd->lsd_feature_incompat = tgt_scd[type].incinit;
} else {
rc = tgt_server_data_read(env, tgt);
if (rc) {
CERROR("%s: error reading LAST_RCVD: rc= %d\n",
tgt_name(tgt), rc);
RETURN(rc);
}
if (strcmp(lsd->lsd_uuid, tgt->lut_obd->obd_uuid.uuid)) {
if (tgt->lut_bottom->dd_rdonly) {
/* Such difference may be caused by mounting
* up snapshot with new fsname under rd_only
* mode. But even if it was NOT, it will not
* damage the system because of "rd_only". */
memcpy(lsd->lsd_uuid,
tgt->lut_obd->obd_uuid.uuid,
sizeof(lsd->lsd_uuid));
} else {
LCONSOLE_ERROR("Trying to start OBD %s using the wrong disk %s. Were the /dev/ assignments rearranged?\n",
tgt->lut_obd->obd_uuid.uuid,
lsd->lsd_uuid);
RETURN(-EINVAL);
}
}
if (lsd->lsd_osd_index != index) {
LCONSOLE_ERROR("%s: index %d in last rcvd is different with the index %d in config log, It might be disk corruption!\n",
tgt_name(tgt), lsd->lsd_osd_index,
index);
RETURN(-EINVAL);
}
}
if (lsd->lsd_feature_incompat & ~tgt_scd[type].incompat) {
CERROR("%s: unsupported incompat filesystem feature(s) %x\n",
tgt_name(tgt),
lsd->lsd_feature_incompat & ~tgt_scd[type].incompat);
RETURN(-EINVAL);
}
if (type == LDD_F_SV_TYPE_MDT)
lsd->lsd_feature_incompat |= OBD_INCOMPAT_FID;
if (lsd->lsd_feature_rocompat & ~tgt_scd[type].rocompat) {
CERROR("%s: unsupported read-only filesystem feature(s) %x\n",
tgt_name(tgt),
lsd->lsd_feature_rocompat & ~tgt_scd[type].rocompat);
RETURN(-EINVAL);
}
/** Interop: evict all clients at first boot with 1.8 last_rcvd */
if (type == LDD_F_SV_TYPE_MDT &&
!(lsd->lsd_feature_compat & OBD_COMPAT_20)) {
if (last_rcvd_size > lsd->lsd_client_start) {
LCONSOLE_WARN("%s: mounting at first time on 1.8 FS, remove all clients for interop needs\n",
tgt_name(tgt));
rc = tgt_truncate_object(env, tgt, tgt->lut_last_rcvd,
lsd->lsd_client_start);
if (rc)
RETURN(rc);
last_rcvd_size = lsd->lsd_client_start;
}
/** set 2.0 flag to upgrade/downgrade between 1.8 and 2.0 */
lsd->lsd_feature_compat |= OBD_COMPAT_20;
}
spin_lock(&tgt->lut_translock);
tgt->lut_last_transno = lsd->lsd_last_transno;
spin_unlock(&tgt->lut_translock);
lsd->lsd_mount_count++;
CDEBUG(D_INODE, "=======,=BEGIN DUMPING LAST_RCVD========\n");
CDEBUG(D_INODE, "%s: server last_transno: %llu\n",
tgt_name(tgt), tgt->lut_last_transno);
CDEBUG(D_INODE, "%s: server mount_count: %llu\n",
tgt_name(tgt), lsd->lsd_mount_count);
CDEBUG(D_INODE, "%s: server data size: %u\n",
tgt_name(tgt), lsd->lsd_server_size);
CDEBUG(D_INODE, "%s: per-client data start: %u\n",
tgt_name(tgt), lsd->lsd_client_start);
CDEBUG(D_INODE, "%s: per-client data size: %u\n",
tgt_name(tgt), lsd->lsd_client_size);
CDEBUG(D_INODE, "%s: last_rcvd size: %lu\n",
tgt_name(tgt), last_rcvd_size);
CDEBUG(D_INODE, "%s: server subdir_count: %u\n",
tgt_name(tgt), lsd->lsd_subdir_count);
CDEBUG(D_INODE, "%s: last_rcvd clients: %lu\n", tgt_name(tgt),
last_rcvd_size <= lsd->lsd_client_start ? 0 :
(last_rcvd_size - lsd->lsd_client_start) /
lsd->lsd_client_size);
CDEBUG(D_INODE, "========END DUMPING LAST_RCVD========\n");
/* Initialize maximum client count */
if (lsd->lsd_max_clients > LR_MAX_CLIENTS) {
CWARN("%s: stored max_clients %u > max allowed %lu, reset to 0\n",
tgt_name(tgt), lsd->lsd_max_clients, LR_MAX_CLIENTS);
lsd->lsd_max_clients = 0;
}
atomic_set(&tgt->lut_max_clients, (int)lsd->lsd_max_clients);
CDEBUG(D_INFO, "%s: restored maximum client count: %u\n",
tgt_name(tgt), lsd->lsd_max_clients);
if (lsd->lsd_server_size == 0 || lsd->lsd_client_start == 0 ||
lsd->lsd_client_size == 0) {
CERROR("%s: bad last_rcvd contents!\n", tgt_name(tgt));
RETURN(-EINVAL);
}
if (!test_bit(OBDF_REPLAYABLE, tgt->lut_obd->obd_flags))
CWARN("%s: recovery support OFF\n", tgt_name(tgt));
rc = tgt_clients_data_init(env, tgt, last_rcvd_size);
if (rc < 0)
GOTO(err_client, rc);
spin_lock(&tgt->lut_translock);
/* obd_last_committed is used for compatibility
* with other lustre recovery code */
tgt->lut_obd->obd_last_committed = tgt->lut_last_transno;
spin_unlock(&tgt->lut_translock);
obd2obt(tgt->lut_obd)->obt_mount_count = lsd->lsd_mount_count;
obd2obt(tgt->lut_obd)->obt_instance = (__u32)lsd->lsd_mount_count;
/* save it, so mount count and last_transno is current */
rc = tgt_server_data_update(env, tgt, 0);
if (rc < 0)
GOTO(err_client, rc);
RETURN(0);
err_client:
class_disconnect_exports(tgt->lut_obd);
return rc;
}
/* add credits for last_rcvd update */
int tgt_txn_start_cb(const struct lu_env *env, struct thandle *th,
void *cookie)
{
struct lu_target *tgt = cookie;
struct tgt_session_info *tsi;
struct tgt_thread_info *tti = tgt_th_info(env);
struct dt_object *dto;
int rc;
/* For readonly case, the caller should have got failure
* when start the transaction. If the logic comes here,
* there must be something wrong. */
if (unlikely(tgt->lut_bottom->dd_rdonly)) {
dump_stack();
LBUG();
}
/* if there is no session, then this transaction is not result of
* request processing but some local operation */
if (env->le_ses == NULL)
return 0;
if (!(env->le_ses->lc_tags & LCT_CL_INIT))
return -EFAULT;
LASSERT(tgt->lut_last_rcvd);
tsi = tgt_ses_info(env);
/* OFD may start transaction without export assigned */
if (tsi->tsi_exp == NULL)
return 0;
if (tgt_is_multimodrpcs_client(tsi->tsi_exp)) {
/*
* Use maximum possible file offset for declaration to ensure
* ZFS will reserve enough credits for a write anywhere in this
* file, since we don't know where in the file the write will be
* because a replay slot has not been assigned. This should be
* replaced by dmu_tx_hold_append() when available.
*/
tti->tti_buf.lb_buf = NULL;
tti->tti_buf.lb_len = sizeof(struct lsd_reply_data);
dto = dt_object_locate(tgt->lut_reply_data, th->th_dev);
rc = dt_declare_record_write(env, dto, &tti->tti_buf, -1, th);
if (rc)
return rc;
} else {
dto = dt_object_locate(tgt->lut_last_rcvd, th->th_dev);
tti_buf_lcd(tti);
tti->tti_off = tsi->tsi_exp->exp_target_data.ted_lr_off;
rc = dt_declare_record_write(env, dto, &tti->tti_buf,
tti->tti_off, th);
if (rc)
return rc;
}
if (tsi->tsi_vbr_obj != NULL &&
!lu_object_remote(&tsi->tsi_vbr_obj->do_lu)) {
dto = dt_object_locate(tsi->tsi_vbr_obj, th->th_dev);
rc = dt_declare_version_set(env, dto, th);
if (!rc && tsi->tsi_dv_update)
rc = dt_declare_data_version_set(env, dto, th);
}
return rc;
}
/* Update last_rcvd records with latests transaction data */
int tgt_txn_stop_cb(const struct lu_env *env, struct thandle *th,
void *cookie)
{
struct lu_target *tgt = cookie;
struct tgt_session_info *tsi;
struct tgt_thread_info *tti = tgt_th_info(env);
struct dt_object *obj = NULL;
int rc;
bool echo_client;
if (env->le_ses == NULL)
return 0;
tsi = tgt_ses_info(env);
/* OFD may start transaction without export assigned */
if (tsi->tsi_exp == NULL)
return 0;
echo_client = (tgt_ses_req(tsi) == NULL && tsi->tsi_xid == 0);
if (tsi->tsi_has_trans && !echo_client && !tsi->tsi_batch_env) {
if (!tsi->tsi_mult_trans) {
CDEBUG(D_HA, "More than one transaction %llu\n",
tti->tti_transno);
/**
* if RPC handler sees unexpected multiple last_rcvd
* updates with transno, then it is better to return
* the latest transaction number to the client.
* In that case replay may fail if part of operation
* was committed and can't be re-applied easily. But
* that is better than report the first transno, in
* which case partially committed operation would be
* considered as finished so never replayed causing
* data loss.
*/
}
/* we need new transno to be assigned */
tti->tti_transno = 0;
}
if (!th->th_result)
tsi->tsi_has_trans++;
if (tsi->tsi_vbr_obj != NULL &&
!lu_object_remote(&tsi->tsi_vbr_obj->do_lu)) {
obj = tsi->tsi_vbr_obj;
}
if (unlikely(echo_client)) /* echo client special case */
rc = tgt_last_rcvd_update_echo(env, tgt, obj, th,
tsi->tsi_exp);
else
rc = tgt_last_rcvd_update(env, tgt, obj, tsi->tsi_opdata, th,
tgt_ses_req(tsi));
return rc;
}
int tgt_reply_data_init(const struct lu_env *env, struct lu_target *tgt)
{
struct tgt_thread_info *tti = tgt_th_info(env);
struct lsd_reply_data *lrd = &tti->tti_lrd;
unsigned long reply_data_size;
int rc;
struct lsd_reply_header *lrh = &tgt->lut_reply_header;
struct tg_reply_data *trd = NULL;
int idx;
loff_t off;
struct cfs_hash *hash = NULL;
struct obd_export *exp;
struct tg_export_data *ted;
int reply_data_recovered = 0;
rc = dt_attr_get(env, tgt->lut_reply_data, &tti->tti_attr);
if (rc)
GOTO(out, rc);
reply_data_size = (unsigned long)tti->tti_attr.la_size;
if (reply_data_size == 0) {
CDEBUG(D_INFO, "%s: new reply_data file, initializing\n",
tgt_name(tgt));
lrh->lrh_magic = LRH_MAGIC;
lrh->lrh_header_size = sizeof(struct lsd_reply_header);
if (lrh->lrh_magic == LRH_MAGIC_V1)
lrh->lrh_reply_size = sizeof(struct lsd_reply_data_v1);
else
lrh->lrh_reply_size = sizeof(struct lsd_reply_data_v2);
rc = tgt_reply_header_write(env, tgt, lrh);
if (rc) {
CERROR("%s: error writing %s: rc = %d\n",
tgt_name(tgt), REPLY_DATA, rc);
GOTO(out, rc);
}
} else {
__u32 recsz = sizeof(*lrd);
const char *lrd_ver = "v2";
rc = tgt_reply_header_read(env, tgt, lrh);
if (rc) {
CERROR("%s: error reading %s: rc = %d\n",
tgt_name(tgt), REPLY_DATA, rc);
GOTO(out, rc);
}
switch (lrh->lrh_magic) {
#if LUSTRE_VERSION_CODE < OBD_OCD_VERSION(3, 5, 53, 0)
/* The old reply_data is replaced on the first mount after
* an upgrade, so no need to keep this interop code forever.
*/
case LRH_MAGIC_V1:
recsz = sizeof(struct lsd_reply_data_v1);
lrd_ver = "v1";
if (lrh->lrh_magic != LRH_MAGIC)
CWARN("%s: %s record size will be %s\n",
tgt_name(tgt), REPLY_DATA,
lrh->lrh_magic < LRH_MAGIC ?
"upgraded" : "downgraded");
fallthrough;
#endif
case LRH_MAGIC_V2:
if (lrh->lrh_header_size != sizeof(*lrh)) {
CERROR("%s: bad %s %s header size: %u != %lu\n",
tgt_name(tgt), REPLY_DATA, lrd_ver,
lrh->lrh_header_size, sizeof(*lrh));
GOTO(out, rc = -EINVAL);
}
if (lrh->lrh_reply_size != recsz) {
CERROR("%s: bad %s %s reply size: %u != %u\n",
tgt_name(tgt), REPLY_DATA, lrd_ver,
lrh->lrh_reply_size, recsz);
GOTO(out, rc = -EINVAL);
}
break;
default:
CERROR("%s: invalid %s magic: %x != %x/%x\n",
tgt_name(tgt), REPLY_DATA,
lrh->lrh_magic, LRH_MAGIC_V1, LRH_MAGIC_V2);
GOTO(out, rc = -EINVAL);
}
hash = cfs_hash_getref(tgt->lut_obd->obd_gen_hash);
if (hash == NULL)
GOTO(out, rc = -ENODEV);
OBD_ALLOC_PTR(trd);
if (trd == NULL)
GOTO(out, rc = -ENOMEM);
/* Load reply_data from disk */
for (idx = 0, off = lrh->lrh_header_size;
off < reply_data_size; idx++, off += recsz) {
rc = tgt_reply_data_read(env, tgt, lrd, off, lrh);
if (rc) {
CERROR("%s: error reading %s: rc = %d\n",
tgt_name(tgt), REPLY_DATA, rc);
GOTO(out, rc);
}
exp = cfs_hash_lookup(hash, &lrd->lrd_client_gen);
if (exp == NULL) {
/* old reply data from a disconnected client */
continue;
}
ted = &exp->exp_target_data;
mutex_lock(&ted->ted_lcd_lock);
/* create in-memory reply_data and link it to
* target export's reply list */
rc = tgt_set_reply_slot(tgt, idx);
if (rc != 0) {
mutex_unlock(&ted->ted_lcd_lock);
GOTO(out, rc);
}
trd->trd_reply = *lrd;
trd->trd_pre_versions[0] = 0;
trd->trd_pre_versions[1] = 0;
trd->trd_pre_versions[2] = 0;
trd->trd_pre_versions[3] = 0;
trd->trd_index = idx;
trd->trd_tag = 0;
fid_zero(&trd->trd_object);
list_add(&trd->trd_list, &ted->ted_reply_list);
ted->ted_reply_cnt++;
if (ted->ted_reply_cnt > ted->ted_reply_max)
ted->ted_reply_max = ted->ted_reply_cnt;
CDEBUG(D_HA, "%s: restore reply %p: xid %llu, transno %llu, client gen %u, slot idx %d\n",
tgt_name(tgt), trd, lrd->lrd_xid,
lrd->lrd_transno, lrd->lrd_client_gen,
trd->trd_index);
/* update export last committed transation */
exp->exp_last_committed = max(exp->exp_last_committed,
lrd->lrd_transno);
/* Update lcd_last_transno as well for check in
* tgt_release_reply_data() or the latest client
* transno can be lost.
*/
ted->ted_lcd->lcd_last_transno =
max(ted->ted_lcd->lcd_last_transno,
exp->exp_last_committed);
mutex_unlock(&ted->ted_lcd_lock);
class_export_put(exp);
/* update target last committed transaction */
spin_lock(&tgt->lut_translock);
tgt->lut_last_transno = max(tgt->lut_last_transno,
lrd->lrd_transno);
spin_unlock(&tgt->lut_translock);
reply_data_recovered++;
OBD_ALLOC_PTR(trd);
if (trd == NULL)
GOTO(out, rc = -ENOMEM);
}
CDEBUG(D_INFO, "%s: %d reply data have been recovered\n",
tgt_name(tgt), reply_data_recovered);
}
spin_lock(&tgt->lut_translock);
/* obd_last_committed is used for compatibility
* with other lustre recovery code */
tgt->lut_obd->obd_last_committed = tgt->lut_last_transno;
spin_unlock(&tgt->lut_translock);
rc = 0;
out:
if (hash != NULL)
cfs_hash_putref(hash);
OBD_FREE_PTR(trd);
return rc;
}
static int tgt_check_lookup_req(struct ptlrpc_request *req, int lookup,
struct tg_reply_data *trd)
{
struct tg_export_data *ted = &req->rq_export->exp_target_data;
struct lu_target *lut = class_exp2tgt(req->rq_export);
__u16 tag = lustre_msg_get_tag(req->rq_reqmsg);
int rc = 0;
struct tg_reply_data *reply;
bool check_increasing;
if (tag == 0)
return 0;
check_increasing = tgt_is_increasing_xid_client(req->rq_export) &&
!(lustre_msg_get_flags(req->rq_reqmsg) & MSG_REPLAY);
if (!lookup && !check_increasing)
return 0;
list_for_each_entry(reply, &ted->ted_reply_list, trd_list) {
if (lookup && reply->trd_reply.lrd_xid == req->rq_xid) {
rc = 1;
if (trd != NULL)
*trd = *reply;
break;
} else if (check_increasing && reply->trd_tag == tag &&
reply->trd_reply.lrd_xid > req->rq_xid) {
rc = -EPROTO;
CERROR("%s: busy tag=%u req_xid=%llu, trd=%p: xid=%llu transno=%llu client_gen=%u slot_idx=%d: rc = %d\n",
tgt_name(lut), tag, req->rq_xid, trd,
reply->trd_reply.lrd_xid,
reply->trd_reply.lrd_transno,
reply->trd_reply.lrd_client_gen,
reply->trd_index, rc);
break;
}
}
return rc;
}
/* Look for a reply data matching specified request @req
* A copy is returned in @trd if the pointer is not NULL
*/
int tgt_lookup_reply(struct ptlrpc_request *req, struct tg_reply_data *trd)
{
struct tg_export_data *ted = &req->rq_export->exp_target_data;
int found = 0;
bool not_replay = !(lustre_msg_get_flags(req->rq_reqmsg) & MSG_REPLAY);
mutex_lock(&ted->ted_lcd_lock);
if (not_replay && req->rq_xid <= req->rq_export->exp_last_xid) {
/* A check for the last_xid is needed here in case there is
* no reply data is left in the list. It may happen if another
* RPC on another slot increased the last_xid between our
* process_req_last_xid & tgt_lookup_reply calls */
found = -EPROTO;
} else {
found = tgt_check_lookup_req(req, 1, trd);
}
mutex_unlock(&ted->ted_lcd_lock);
CDEBUG(D_TRACE, "%s: lookup reply xid %llu, found %d last_xid %llu\n",
tgt_name(class_exp2tgt(req->rq_export)), req->rq_xid, found,
req->rq_export->exp_last_xid);
return found;
}
EXPORT_SYMBOL(tgt_lookup_reply);
int tgt_handle_received_xid(struct obd_export *exp, __u64 rcvd_xid)
{
struct tg_export_data *ted = &exp->exp_target_data;
struct lu_target *lut = class_exp2tgt(exp);
struct tg_reply_data *trd, *tmp;
list_for_each_entry_safe(trd, tmp, &ted->ted_reply_list, trd_list) {
if (trd->trd_reply.lrd_xid > rcvd_xid)
continue;
ted->ted_release_xid++;
tgt_release_reply_data(lut, ted, trd);
}
return 0;
}
int tgt_handle_tag(struct ptlrpc_request *req)
{
return tgt_check_lookup_req(req, 0, NULL);
}