+/* This must be longer than the longest string below */
+#define SYNC_STATES_MAXLEN 16
+static char *sync_on_cancel_states[] = {"never",
+ "blocking",
+ "always" };
+
+/**
+ * Show policy for handling dirty data under a lock being cancelled.
+ *
+ * \param[in] kobj sysfs kobject
+ * \param[in] attr sysfs attribute
+ * \param[in] buf buffer for data
+ *
+ * \retval 0 and buffer filled with data on success
+ * \retval negative value on error
+ */
+static ssize_t sync_lock_cancel_show(struct kobject *kobj,
+ struct attribute *attr, char *buf)
+{
+ struct obd_device *obd = container_of(kobj, struct obd_device,
+ obd_kset.kobj);
+ struct lu_target *tgt = obd->u.obt.obt_lut;
+
+ return sprintf(buf, "%s\n",
+ sync_on_cancel_states[tgt->lut_sync_lock_cancel]);
+}
+
+/**
+ * Change policy for handling dirty data under a lock being cancelled.
+ *
+ * This variable defines what action target takes upon lock cancel
+ * There are three possible modes:
+ * 1) never - never do sync upon lock cancel. This can lead to data
+ * inconsistencies if both the OST and client crash while writing a file
+ * that is also concurrently being read by another client. In these cases,
+ * this may allow the file data to "rewind" to an earlier state.
+ * 2) blocking - do sync only if there is blocking lock, e.g. if another
+ * client is trying to access this same object
+ * 3) always - do sync always
+ *
+ * \param[in] kobj kobject
+ * \param[in] attr attribute to show
+ * \param[in] buf buffer for data
+ * \param[in] count buffer size
+ *
+ * \retval \a count on success
+ * \retval negative value on error
+ */
+static ssize_t sync_lock_cancel_store(struct kobject *kobj,
+ struct attribute *attr,
+ const char *buffer, size_t count)
+{
+ struct obd_device *obd = container_of(kobj, struct obd_device,
+ obd_kset.kobj);
+ struct lu_target *tgt = obd->u.obt.obt_lut;
+ int val = -1;
+ int i;
+
+ if (count == 0 || count >= SYNC_STATES_MAXLEN)
+ return -EINVAL;
+
+ for (i = 0 ; i < NUM_SYNC_ON_CANCEL_STATES; i++) {
+ if (strcmp(buffer, sync_on_cancel_states[i]) == 0) {
+ val = i;
+ break;
+ }
+ }
+
+ /* Legacy numeric codes */
+ if (val == -1) {
+ int rc = kstrtoint(buffer, 0, &val);
+ if (rc)
+ return rc;
+ }
+
+ if (val < 0 || val > 2)
+ return -EINVAL;
+
+ spin_lock(&tgt->lut_flags_lock);
+ tgt->lut_sync_lock_cancel = val;
+ spin_unlock(&tgt->lut_flags_lock);
+ return count;
+}
+LUSTRE_RW_ATTR(sync_lock_cancel);
+
+/**
+ * Show maximum number of Filter Modification Data (FMD) maintained.
+ *
+ * \param[in] kobj kobject
+ * \param[in] attr attribute to show
+ * \param[in] buf buffer for data
+ *
+ * \retval 0 and buffer filled with data on success
+ * \retval negative value on error
+ */
+ssize_t tgt_fmd_count_show(struct kobject *kobj, struct attribute *attr,
+ char *buf)
+{
+ struct obd_device *obd = container_of(kobj, struct obd_device,
+ obd_kset.kobj);
+ struct lu_target *lut = obd->u.obt.obt_lut;
+
+ return sprintf(buf, "%u\n", lut->lut_fmd_max_num);
+}
+
+/**
+ * Change number of FMDs maintained by target.
+ *
+ * This defines how large the list of FMDs can be.
+ *
+ * \param[in] kobj kobject
+ * \param[in] attr attribute to show
+ * \param[in] buf buffer for data
+ * \param[in] count buffer size
+ *
+ * \retval \a count on success
+ * \retval negative value on error
+ */
+ssize_t tgt_fmd_count_store(struct kobject *kobj, struct attribute *attr,
+ const char *buffer, size_t count)
+{
+ struct obd_device *obd = container_of(kobj, struct obd_device,
+ obd_kset.kobj);
+ struct lu_target *lut = obd->u.obt.obt_lut;
+ int val, rc;
+
+ rc = kstrtoint(buffer, 0, &val);
+ if (rc)
+ return rc;
+
+ if (val < 1 || val > 65536)
+ return -EINVAL;
+
+ lut->lut_fmd_max_num = val;
+
+ return count;
+}
+LUSTRE_RW_ATTR(tgt_fmd_count);
+
+/**
+ * Show the maximum age of FMD data in seconds.
+ *
+ * \param[in] kobj kobject
+ * \param[in] attr attribute to show
+ * \param[in] buf buffer for data
+ *
+ * \retval 0 and buffer filled with data on success
+ * \retval negative value on error
+ */
+ssize_t tgt_fmd_seconds_show(struct kobject *kobj, struct attribute *attr,
+ char *buf)
+{
+ struct obd_device *obd = container_of(kobj, struct obd_device,
+ obd_kset.kobj);
+ struct lu_target *lut = obd->u.obt.obt_lut;
+
+ return sprintf(buf, "%lld\n", lut->lut_fmd_max_age);
+}
+
+/**
+ * Set the maximum age of FMD data in seconds.
+ *
+ * This defines how long FMD data stays in the FMD list.
+ *
+ * \param[in] kobj kobject
+ * \param[in] attr attribute to show
+ * \param[in] buf buffer for data
+ * \param[in] count buffer size
+ *
+ * \retval \a count on success
+ * \retval negative number on error
+ */
+ssize_t tgt_fmd_seconds_store(struct kobject *kobj, struct attribute *attr,
+ const char *buffer, size_t count)
+{
+ struct obd_device *obd = container_of(kobj, struct obd_device,
+ obd_kset.kobj);
+ struct lu_target *lut = obd->u.obt.obt_lut;
+ time64_t val;
+ int rc;
+
+ rc = kstrtoll(buffer, 0, &val);
+ if (rc)
+ return rc;
+
+ if (val < 1 || val > 65536) /* ~ 18 hour max */
+ return -EINVAL;
+
+ lut->lut_fmd_max_age = val;
+
+ return count;
+}
+LUSTRE_RW_ATTR(tgt_fmd_seconds);
+
+/* These two aliases are old names and kept for compatibility, they were
+ * changed to 'tgt_fmd_count' and 'tgt_fmd_seconds'.
+ * This change was made in Lustre 2.13, so these aliases can be removed
+ * when back compatibility is not needed with any Lustre version prior 2.13
+ */
+static struct lustre_attr tgt_fmd_count_compat = __ATTR(client_cache_count,
+ 0644, tgt_fmd_count_show, tgt_fmd_count_store);
+static struct lustre_attr tgt_fmd_seconds_compat = __ATTR(client_cache_seconds,
+ 0644, tgt_fmd_seconds_show, tgt_fmd_seconds_store);
+
+static const struct attribute *tgt_attrs[] = {
+ &lustre_attr_sync_lock_cancel.attr,
+ &lustre_attr_tgt_fmd_count.attr,
+ &lustre_attr_tgt_fmd_seconds.attr,
+ &tgt_fmd_count_compat.attr,
+ &tgt_fmd_seconds_compat.attr,
+ NULL,
+};
+
+int tgt_tunables_init(struct lu_target *lut)
+{
+ int rc;
+
+ rc = sysfs_create_files(&lut->lut_obd->obd_kset.kobj, tgt_attrs);
+ if (!rc)
+ lut->lut_attrs = tgt_attrs;
+ return rc;
+}
+EXPORT_SYMBOL(tgt_tunables_init);
+
+void tgt_tunables_fini(struct lu_target *lut)
+{
+ if (lut->lut_attrs) {
+ sysfs_remove_files(&lut->lut_obd->obd_kset.kobj,
+ lut->lut_attrs);
+ lut->lut_attrs = NULL;
+ }
+}
+EXPORT_SYMBOL(tgt_tunables_fini);
+
+/*
+ * Save cross-MDT lock in lut_slc_locks.
+ *
+ * Lock R/W count is not saved, but released in unlock (not canceled remotely),
+ * instead only a refcount is taken, so that the remote MDT where the object
+ * resides can detect conflict with this lock there.
+ *
+ * \param lut target
+ * \param lock cross-MDT lock to save
+ * \param transno when the transaction with this transno is committed, this lock
+ * can be canceled.
+ */
+void tgt_save_slc_lock(struct lu_target *lut, struct ldlm_lock *lock,
+ __u64 transno)
+{
+ spin_lock(&lut->lut_slc_locks_guard);
+ lock_res_and_lock(lock);
+ if (ldlm_is_cbpending(lock)) {
+ /* if it was canceld by server, don't save, because remote MDT
+ * will do Sync-on-Cancel. */
+ LDLM_LOCK_PUT(lock);
+ } else {
+ lock->l_transno = transno;
+ /* if this lock is in the list already, there are two operations
+ * both use this lock, and save it after use, so for the second
+ * one, just put the refcount. */
+ if (list_empty(&lock->l_slc_link))
+ list_add_tail(&lock->l_slc_link, &lut->lut_slc_locks);
+ else
+ LDLM_LOCK_PUT(lock);
+ }
+ unlock_res_and_lock(lock);
+ spin_unlock(&lut->lut_slc_locks_guard);
+}
+EXPORT_SYMBOL(tgt_save_slc_lock);
+
+/*
+ * Discard cross-MDT lock from lut_slc_locks.
+ *
+ * This is called upon BAST, just remove lock from lut_slc_locks and put lock
+ * refcount. The BAST will cancel this lock.
+ *
+ * \param lut target
+ * \param lock cross-MDT lock to discard
+ */
+void tgt_discard_slc_lock(struct lu_target *lut, struct ldlm_lock *lock)
+{
+ spin_lock(&lut->lut_slc_locks_guard);
+ lock_res_and_lock(lock);
+ /* may race with tgt_cancel_slc_locks() */
+ if (lock->l_transno != 0) {
+ LASSERT(!list_empty(&lock->l_slc_link));
+ LASSERT(ldlm_is_cbpending(lock));
+ list_del_init(&lock->l_slc_link);
+ lock->l_transno = 0;
+ LDLM_LOCK_PUT(lock);
+ }
+ unlock_res_and_lock(lock);
+ spin_unlock(&lut->lut_slc_locks_guard);
+}
+EXPORT_SYMBOL(tgt_discard_slc_lock);
+
+/*
+ * Cancel cross-MDT locks upon transaction commit.
+ *
+ * Remove cross-MDT locks from lut_slc_locks, cancel them and put lock refcount.
+ *
+ * \param lut target
+ * \param transno transaction with this number was committed.
+ */
+void tgt_cancel_slc_locks(struct lu_target *lut, __u64 transno)
+{
+ struct ldlm_lock *lock, *next;
+ LIST_HEAD(list);
+ struct lustre_handle lockh;
+
+ spin_lock(&lut->lut_slc_locks_guard);
+ list_for_each_entry_safe(lock, next, &lut->lut_slc_locks,
+ l_slc_link) {
+ lock_res_and_lock(lock);
+ LASSERT(lock->l_transno != 0);
+ if (lock->l_transno > transno) {
+ unlock_res_and_lock(lock);
+ continue;
+ }
+ /* ouch, another operation is using it after it's saved */
+ if (lock->l_readers != 0 || lock->l_writers != 0) {
+ unlock_res_and_lock(lock);
+ continue;
+ }
+ /* set CBPENDING so that this lock won't be used again */
+ ldlm_set_cbpending(lock);
+ lock->l_transno = 0;
+ list_move(&lock->l_slc_link, &list);
+ unlock_res_and_lock(lock);
+ }
+ spin_unlock(&lut->lut_slc_locks_guard);
+
+ list_for_each_entry_safe(lock, next, &list, l_slc_link) {
+ list_del_init(&lock->l_slc_link);
+ ldlm_lock2handle(lock, &lockh);
+ ldlm_cli_cancel(&lockh, LCF_ASYNC);
+ LDLM_LOCK_PUT(lock);
+ }
+}
+