Merge branch 'akpm' (fixes from Andrew Morton)
[firefly-linux-kernel-4.4.55.git] / fs / ocfs2 / quota_global.c
index d7b5108789e2e7d8a26049b7dc6a01f8a0ff2d6f..b990a62cff50c4b6d3bd18f6a5ecede3fe14d440 100644 (file)
@@ -10,6 +10,7 @@
 #include <linux/jiffies.h>
 #include <linux/writeback.h>
 #include <linux/workqueue.h>
+#include <linux/llist.h>
 
 #include <cluster/masklog.h>
 
@@ -679,6 +680,27 @@ static int ocfs2_calc_qdel_credits(struct super_block *sb, int type)
               OCFS2_INODE_UPDATE_CREDITS;
 }
 
+void ocfs2_drop_dquot_refs(struct work_struct *work)
+{
+       struct ocfs2_super *osb = container_of(work, struct ocfs2_super,
+                                              dquot_drop_work);
+       struct llist_node *list;
+       struct ocfs2_dquot *odquot, *next_odquot;
+
+       list = llist_del_all(&osb->dquot_drop_list);
+       llist_for_each_entry_safe(odquot, next_odquot, list, list) {
+               /* Drop the reference we acquired in ocfs2_dquot_release() */
+               dqput(&odquot->dq_dquot);
+       }
+}
+
+/*
+ * Called when the last reference to dquot is dropped. If we are called from
+ * downconvert thread, we cannot do all the handling here because grabbing
+ * quota lock could deadlock (the node holding the quota lock could need some
+ * other cluster lock to proceed but with blocked downconvert thread we cannot
+ * release any lock).
+ */
 static int ocfs2_release_dquot(struct dquot *dquot)
 {
        handle_t *handle;
@@ -694,6 +716,19 @@ static int ocfs2_release_dquot(struct dquot *dquot)
        /* Check whether we are not racing with some other dqget() */
        if (atomic_read(&dquot->dq_count) > 1)
                goto out;
+       /* Running from downconvert thread? Postpone quota processing to wq */
+       if (current == osb->dc_task) {
+               /*
+                * Grab our own reference to dquot and queue it for delayed
+                * dropping.  Quota code rechecks after calling
+                * ->release_dquot() and won't free dquot structure.
+                */
+               dqgrab(dquot);
+               /* First entry on list -> queue work */
+               if (llist_add(&OCFS2_DQUOT(dquot)->list, &osb->dquot_drop_list))
+                       queue_work(ocfs2_wq, &osb->dquot_drop_work);
+               goto out;
+       }
        status = ocfs2_lock_global_qf(oinfo, 1);
        if (status < 0)
                goto out;