Viewing: llog_cat.c

// SPDX-License-Identifier: GPL-2.0

/*
 * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
 * Use is subject to license terms.
 *
 * Copyright (c) 2012, 2017, Intel Corporation.
 */

/*
 * This file is part of Lustre, http://www.lustre.org/
 *
 * OST<->MDS recovery logging infrastructure.
 *
 * Invariants in implementation:
 * - we do not share logs among different OST<->MDS connections, so that
 *   if an OST or MDS fails it need only look at log(s) relevant to itself
 *
 * Author: Andreas Dilger <adilger@clusterfs.com>
 * Author: Alexey Zhuravlev <alexey.zhuravlev@intel.com>
 * Author: Mikhail Pershin <mike.pershin@intel.com>
 */

#define DEBUG_SUBSYSTEM S_LOG

#include <obd_class.h>

#include "llog_internal.h"

/* Open an existent log handle and add it to the open list.
 * This log handle will be closed when all of the records in it are removed.
 *
 * Assumes caller has already pushed us into the kernel context and is locking.
 * We return a lock on the handle to ensure nobody yanks it from us.
 *
 * This takes extra reference on llog_handle via llog_handle_get() and require
 * this reference to be put by caller using llog_handle_put()
 */
SERVER_ONLY int
llog_cat_id2handle(const struct lu_env *env, struct llog_handle *cathandle,
		   struct llog_handle **res, struct llog_logid *logid)
{
	struct llog_handle	*loghandle;
	enum llog_flag		 fmt;
	int			 rc = 0;

	ENTRY;

	if (cathandle == NULL)
		RETURN(-EBADF);

	fmt = cathandle->lgh_hdr->llh_flags & LLOG_F_EXT_MASK;
	down_read(&cathandle->lgh_lock);
	list_for_each_entry(loghandle, &cathandle->u.chd.chd_head,
			    u.phd.phd_entry) {
		struct llog_logid *cgl = &loghandle->lgh_id;

		if (ostid_id(&cgl->lgl_oi) == ostid_id(&logid->lgl_oi) &&
		    ostid_seq(&cgl->lgl_oi) == ostid_seq(&logid->lgl_oi)) {
			*res = llog_handle_get(loghandle);
			if (!*res) {
				CERROR("%s: log "DFID" refcount is zero!\n",
				       loghandle2name(loghandle),
				       PLOGID(logid));
				continue;
			}
			loghandle->u.phd.phd_cat_handle = cathandle;
			up_read(&cathandle->lgh_lock);
			RETURN(rc);
		}
	}
	up_read(&cathandle->lgh_lock);

	rc = llog_open(env, cathandle->lgh_ctxt, &loghandle, logid, NULL,
		       LLOG_OPEN_EXISTS);
	if (rc < 0) {
		CERROR("%s: error opening log id "DFID": rc = %d\n",
		       loghandle2name(cathandle), PLOGID(logid), rc);
		RETURN(rc);
	}

	rc = llog_init_handle(env, loghandle, LLOG_F_IS_PLAIN |
			      LLOG_F_ZAP_WHEN_EMPTY | fmt, NULL);
	if (rc < 0) {
		llog_close(env, loghandle);
		*res = NULL;
		RETURN(rc);
	}

	*res = llog_handle_get(loghandle);
	LASSERT(*res);
	down_write(&cathandle->lgh_lock);
	list_add(&loghandle->u.phd.phd_entry, &cathandle->u.chd.chd_head);
	up_write(&cathandle->lgh_lock);

	loghandle->u.phd.phd_cat_handle = cathandle;
	loghandle->u.phd.phd_cookie.lgc_lgl = cathandle->lgh_id;
	loghandle->u.phd.phd_cookie.lgc_index =
				loghandle->lgh_hdr->llh_cat_idx;
	RETURN(0);
}

int llog_cat_close(const struct lu_env *env, struct llog_handle *cathandle)
{
	struct llog_handle *loghandle, *n;
	int rc = 0;

	ENTRY;
	list_for_each_entry_safe(loghandle, n, &cathandle->u.chd.chd_head,
				 u.phd.phd_entry) {
#ifdef CONFIG_LUSTRE_FS_SERVER
		struct llog_log_hdr *llh = loghandle->lgh_hdr;
		int index;
#endif
		/* unlink open-not-created llogs */
		list_del_init(&loghandle->u.phd.phd_entry);
#ifdef CONFIG_LUSTRE_FS_SERVER
		if (loghandle->lgh_obj != NULL && llh != NULL &&
		    (llh->llh_flags & LLOG_F_ZAP_WHEN_EMPTY) &&
		    (llh->llh_count == 1)) {
			rc = llog_destroy(env, loghandle);
			if (rc)
				CERROR("%s: failure destroying log during "
				       "cleanup: rc = %d\n",
				       loghandle2name(loghandle), rc);

			index = loghandle->u.phd.phd_cookie.lgc_index;
			llog_cat_cleanup(env, cathandle, NULL, index);
		}
#endif
		llog_close(env, loghandle);
	}
#ifdef CONFIG_LUSTRE_FS_SERVER
	/* if handle was stored in ctxt, remove it too */
	if (cathandle->lgh_ctxt->loc_handle == cathandle)
		cathandle->lgh_ctxt->loc_handle = NULL;
#endif
	rc = llog_close(env, cathandle);

	RETURN(rc);
}
EXPORT_SYMBOL(llog_cat_close);

SERVER_ONLY int
llog_cat_process_common(const struct lu_env *env, struct llog_handle *cat_llh,
			struct llog_rec_hdr *rec, struct llog_handle **llhp)
{
	struct llog_logid_rec *lir = container_of(rec, typeof(*lir), lid_hdr);
#ifdef CONFIG_LUSTRE_FS_SERVER
	struct llog_log_hdr *hdr;
#endif
	int rc;

	ENTRY;
	if (rec->lrh_type != le32_to_cpu(LLOG_LOGID_MAGIC)) {
		rc = -EINVAL;
		CWARN("%s: invalid record in catalog "DFID": rc = %d\n",
		      loghandle2name(cat_llh), PLOGID(&cat_llh->lgh_id), rc);
		RETURN(rc);
	}
	CDEBUG(D_HA, "processing log "DFID" at index %u of catalog "DFID"\n",
	       PLOGID(&lir->lid_id), le32_to_cpu(rec->lrh_index),
	       PLOGID(&cat_llh->lgh_id));

	rc = llog_cat_id2handle(env, cat_llh, llhp, &lir->lid_id);
	if (rc) {
#ifdef CONFIG_LUSTRE_FS_SERVER
		/* After a server crash, a stub of index record in catlog could
		 * be kept, because plain log destroy + catlog index record
		 * deletion are not atomic. So we end up with an index but no
		 * actual record. Destroy the index and move on.
		 */
		if (rc == -ENOENT || rc == -ESTALE)
			rc = LLOG_DEL_RECORD;
		else if (rc)
			CWARN("%s: can't find llog handle "DFID": rc = %d\n",
			      loghandle2name(cat_llh), PLOGID(&lir->lid_id),
			      rc);
#endif
		RETURN(rc);
	}
#ifdef CONFIG_LUSTRE_FS_SERVER
	/* clean old empty llogs, do not consider current llog in use */
	/* ignore remote (lgh_obj == NULL) llogs */
	hdr = (*llhp)->lgh_hdr;
	if ((hdr->llh_flags & LLOG_F_ZAP_WHEN_EMPTY) &&
	    hdr->llh_count == 1 && cat_llh->lgh_obj != NULL &&
	    *llhp != cat_llh->u.chd.chd_current_log &&
	    *llhp != cat_llh->u.chd.chd_next_log) {
		rc = llog_destroy(env, *llhp);
		if (rc)
			CWARN("%s: can't destroy empty log "DFID": rc = %d\n",
			      loghandle2name((*llhp)), PLOGID(&lir->lid_id),
			      rc);
		rc = LLOG_DEL_PLAIN;
	}
#endif /* CONFIG_LUSTRE_FS_SERVER */

	RETURN(rc);
}

static int llog_cat_process_cb(const struct lu_env *env,
			       struct llog_handle *cat_llh,
			       struct llog_rec_hdr *rec, void *data)
{
	struct llog_process_data *d = data;
	struct llog_handle *llh = NULL;
	int rc;

	ENTRY;
	/* Skip processing of the logs until startcat */
	if (rec->lrh_index < d->lpd_startcat)
		RETURN(0);

	rc = llog_cat_process_common(env, cat_llh, rec, &llh);
	if (rc)
		GOTO(out, rc);

	if (d->lpd_startidx > 0) {
		struct llog_process_cat_data cd = {
			.lpcd_first_idx = 0,
			.lpcd_last_idx = 0,
			.lpcd_read_mode = LLOG_READ_MODE_NORMAL,
		};

		/* startidx is always associated with a catalog index */
		if (d->lpd_startcat == rec->lrh_index)
			cd.lpcd_first_idx = d->lpd_startidx;

		rc = llog_process_or_fork(env, llh, d->lpd_cb, d->lpd_data,
					  &cd, false);
		/* Continue processing the next log from idx 0 */
		d->lpd_startidx = 0;
	} else {
		rc = llog_process_or_fork(env, llh, d->lpd_cb, d->lpd_data,
					  NULL, false);
	}
#ifdef CONFIG_LUSTRE_FS_SERVER
	if (rc == -ENOENT && (cat_llh->lgh_hdr->llh_flags & LLOG_F_RM_ON_ERR)) {
		/*
		 * plain llog is reported corrupted, so better to just remove
		 * it if the caller is fine with that.
		 */
		CERROR("%s: remove corrupted/missing llog "DFID"\n",
		       loghandle2name(cat_llh), PLOGID(&llh->lgh_id));
		rc = LLOG_DEL_PLAIN;
	}
#endif
out:
#ifdef CONFIG_LUSTRE_FS_SERVER
	/* The empty plain log was destroyed while processing */
	if (rc == LLOG_DEL_PLAIN || rc == LLOG_DEL_RECORD)
		/* clear wrong catalog entry */
		rc = llog_cat_cleanup(env, cat_llh, llh, rec->lrh_index);
#endif
	if (rc == LLOG_SKIP_PLAIN)
		/* processing callback ask to skip the llog -> continue */
		rc = 0;
	if (llh)
		llog_handle_put(env, llh);

	RETURN(rc);
}

SERVER_ONLY int
llog_cat_process_or_fork(const struct lu_env *env,
			 struct llog_handle *cat_llh, llog_cb_t cat_cb,
			 llog_cb_t cb, void *data, int startcat,
			 int startidx, bool fork)
{
	struct llog_log_hdr *llh = cat_llh->lgh_hdr;
	struct llog_process_data d;
	struct llog_process_cat_data cd;
	int rc;

	ENTRY;
	LASSERT(llh->llh_flags & LLOG_F_IS_CAT);
	d.lpd_data = data;
	d.lpd_cb = cb;

	/* default: start from the oldest record */
	d.lpd_startidx = 0;
	d.lpd_startcat = llh->llh_cat_idx + 1;
	cd.lpcd_first_idx = llh->llh_cat_idx;
	cd.lpcd_last_idx = 0;
	cd.lpcd_read_mode = LLOG_READ_MODE_NORMAL;

	if (startcat > 0 && startcat <= llog_max_idx(cat_llh)) {
		/* start from a custom catalog/llog plain indexes*/
		d.lpd_startidx = startidx;
		d.lpd_startcat = startcat;
		cd.lpcd_first_idx = startcat - 1;
	} else if (startcat != 0) {
		CWARN("%s: startcat %d out of range for catlog "DFID"\n",
		      loghandle2name(cat_llh), startcat,
		      PLOGID(&cat_llh->lgh_id));
		RETURN(-EINVAL);
	}

	startcat = d.lpd_startcat;

	/* if startcat <= lgh_last_idx, we only need to process the first part
	 * of the catalog (from startcat).
	 */
	if (llog_cat_is_wrapped(cat_llh) && startcat > cat_llh->lgh_last_idx) {
		int cat_idx_origin = llh->llh_cat_idx;

		CWARN("%s: catlog "DFID" crosses index zero\n",
		      loghandle2name(cat_llh),
		      PLOGID(&cat_llh->lgh_id));

		/* processing the catalog part at the end */
		rc = llog_process_or_fork(env, cat_llh, cat_cb, &d, &cd, fork);
		if (rc)
			RETURN(rc);

		/* Reset the startcat because it has already reached catalog
		 * bottom.
		 * lgh_last_idx value could be increased during processing. So
		 * we process the remaining of catalog entries to be sure.
		 */
		d.lpd_startcat = 1;
		d.lpd_startidx = 0;
		cd.lpcd_first_idx = 0;
		cd.lpcd_last_idx = max(cat_idx_origin, cat_llh->lgh_last_idx);
	} else if (llog_cat_is_wrapped(cat_llh)) {
		/* only process 1st part -> stop before reaching 2sd part */
		cd.lpcd_last_idx = llh->llh_cat_idx;
	}

	/* processing the catalog part at the begining */
	rc = llog_process_or_fork(env, cat_llh, cat_cb, &d, &cd, fork);

	RETURN(rc);
}
SERVER_ONLY_EXPORT_SYMBOL(llog_cat_process_or_fork);

/**
 * Process catalog records with a callback
 *
 * \note
 * If "starcat = 0", this is the default processing. "startidx" argument is
 * ignored and processing begin from the oldest record.
 * If "startcat > 0", this is a custom starting point. Processing begin with
 * the llog plain defined in the catalog record at index "startcat". The first
 * llog plain record to process is at index "startidx + 1".
 *
 * \param env		Lustre environnement
 * \param cat_llh	Catalog llog handler
 * \param cb		Callback executed for each records (in llog plain files)
 * \param data		Callback data argument
 * \param startcat	Catalog index of the llog plain to start with.
 * \param startidx	Index of the llog plain to start processing. The first
 *			record to process is at startidx + 1.
 *
 * \retval 0 processing successfully completed
 * \retval LLOG_PROC_BREAK processing was stopped by the callback.
 * \retval -errno on error.
 */
int llog_cat_process(const struct lu_env *env, struct llog_handle *cat_llh,
		     llog_cb_t cb, void *data, int startcat, int startidx)
{
	return llog_cat_process_or_fork(env, cat_llh, llog_cat_process_cb,
					cb, data, startcat, startidx, false);
}
EXPORT_SYMBOL(llog_cat_process);

int llog_cat_set_first_idx(struct llog_handle *cathandle, int newidx)
{
	struct llog_log_hdr *llh = cathandle->lgh_hdr;
	int max, idx = llh->llh_cat_idx;

	ENTRY;

	max = llog_max_idx(cathandle) + 1;
	if (find_next_bit_le((void *)LLOG_HDR_BITMAP(llh), max, 1) == max)
		RETURN(0);
	/*
	 * The llh_cat_idx equals to the first used index minus 1.
	 * We scan from llh_cat_idx + 1 disregard which index
	 * was canceled to avoid llh_cat_idx cannot go forward in
	 * abnormal case.
	 */
	do {
		idx = (idx + 1) % (llog_max_idx(cathandle) + 1);
		if (newidx == idx || !test_bit_le(idx, LLOG_HDR_BITMAP(llh))) {
			/* update llh_cat_idx for each unset bit,
			 * expecting the next one is set
			 */
			llh->llh_cat_idx = idx;
		} else if (idx == 0) {
			/* skip header bit */
			llh->llh_cat_idx = 0;
			continue;
		} else {
			/* the first index is found */
			break;
		}
	} while (idx != cathandle->lgh_last_idx);

	CDEBUG(D_HA, "catlog "DFID" first idx %u, last_idx %u\n",
	       PLOGID(&cathandle->lgh_id), llh->llh_cat_idx,
	       cathandle->lgh_last_idx);

	RETURN(0);
}
EXPORT_SYMBOL(llog_cat_set_first_idx);