diff mbox series

[v3] fs/locks: print full locks information

Message ID 685386c2840b76c49b060bf7dcea1fefacf18176.1614322182.git.luolongjun@huawei.com (mailing list archive)
State New, archived
Headers show
Series [v3] fs/locks: print full locks information | expand

Commit Message

Luo Longjun Feb. 26, 2021, 3:58 a.m. UTC
Commit fd7732e033e3 ("fs/locks: create a tree of dependent requests.")
has put blocked locks into a tree.

So, with a for loop, we can't check all locks information.

To solve this problem, we should traverse the tree.

Signed-off-by: Luo Longjun <luolongjun@huawei.com>
---
 fs/locks.c | 65 ++++++++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 56 insertions(+), 9 deletions(-)

Comments

Jeff Layton March 9, 2021, 1:37 p.m. UTC | #1
On Thu, 2021-02-25 at 22:58 -0500, Luo Longjun wrote:
> Commit fd7732e033e3 ("fs/locks: create a tree of dependent requests.")
> has put blocked locks into a tree.
> 
> So, with a for loop, we can't check all locks information.
> 
> To solve this problem, we should traverse the tree.
> 
> Signed-off-by: Luo Longjun <luolongjun@huawei.com>
> ---
>  fs/locks.c | 65 ++++++++++++++++++++++++++++++++++++++++++++++--------
>  1 file changed, 56 insertions(+), 9 deletions(-)
> 
> diff --git a/fs/locks.c b/fs/locks.c
> index 99ca97e81b7a..ecaecd1f1b58 100644
> --- a/fs/locks.c
> +++ b/fs/locks.c
> @@ -2828,7 +2828,7 @@ struct locks_iterator {
>  };
>  
> 
> 
> 
>  static void lock_get_status(struct seq_file *f, struct file_lock *fl,
> -			    loff_t id, char *pfx)
> +			    loff_t id, char *pfx, int repeat)
>  {
>  	struct inode *inode = NULL;
>  	unsigned int fl_pid;
> @@ -2844,7 +2844,11 @@ static void lock_get_status(struct seq_file *f, struct file_lock *fl,
>  	if (fl->fl_file != NULL)
>  		inode = locks_inode(fl->fl_file);
>  
> 
> 
> 
> -	seq_printf(f, "%lld:%s ", id, pfx);
> +	seq_printf(f, "%lld: ", id);
> +
> +	if (repeat)
> +		seq_printf(f, "%*s", repeat - 1 + (int)strlen(pfx), pfx);

Shouldn't that be "%.*s" ?

Also, isn't this likely to end up walking past the end of "pfx" (or even
ending up at an address before the buffer)? You have this below:

    lock_get_status(f, fl, *id, "", 0);

...so the "length" value you're passing into the format there is going
to be -1. It also seems like if you get a large "level" value in
locks_show, then you'll end up with a length that is much longer than
the actual string.

> +
>  	if (IS_POSIX(fl)) {
>  		if (fl->fl_flags & FL_ACCESS)
>  			seq_puts(f, "ACCESS");
> @@ -2906,21 +2910,64 @@ static void lock_get_status(struct seq_file *f, struct file_lock *fl,
>  	}
>  }
>  
> 
> 
> 
> +static struct file_lock *get_next_blocked_member(struct file_lock *node)
> +{
> +	struct file_lock *tmp;
> +
> +	/* NULL node or root node */
> +	if (node == NULL || node->fl_blocker == NULL)
> +		return NULL;
> +
> +	/* Next member in the linked list could be itself */
> +	tmp = list_next_entry(node, fl_blocked_member);
> +	if (list_entry_is_head(tmp, &node->fl_blocker->fl_blocked_requests, fl_blocked_member)
> +		|| tmp == node) {
> +		return NULL;
> +	}
> +
> +	return tmp;
> +}
> +
>  static int locks_show(struct seq_file *f, void *v)
>  {
>  	struct locks_iterator *iter = f->private;
> -	struct file_lock *fl, *bfl;
> +	struct file_lock *cur, *tmp;
>  	struct pid_namespace *proc_pidns = proc_pid_ns(file_inode(f->file)->i_sb);
> +	int level = 0;
>  
> 
> 
> 
> -	fl = hlist_entry(v, struct file_lock, fl_link);
> +	cur = hlist_entry(v, struct file_lock, fl_link);
>  
> 
> 
> 
> -	if (locks_translate_pid(fl, proc_pidns) == 0)
> +	if (locks_translate_pid(cur, proc_pidns) == 0)
>  		return 0;
>  
> 
> 
> 
> -	lock_get_status(f, fl, iter->li_pos, "");
> +	/* View this crossed linked list as a binary tree, the first member of fl_blocked_requests
> +	 * is the left child of current node, the next silibing in fl_blocked_member is the
> +	 * right child, we can alse get the parent of current node from fl_blocker, so this
> +	 * question becomes traversal of a binary tree
> +	 */
> +	while (cur != NULL) {
> +		if (level)
> +			lock_get_status(f, cur, iter->li_pos, "-> ", level);
> +		else
> +			lock_get_status(f, cur, iter->li_pos, "", level);
>  
> 
> 
> 
> -	list_for_each_entry(bfl, &fl->fl_blocked_requests, fl_blocked_member)
> -		lock_get_status(f, bfl, iter->li_pos, " ->");
> +		if (!list_empty(&cur->fl_blocked_requests)) {
> +			/* Turn left */
> +			cur = list_first_entry_or_null(&cur->fl_blocked_requests,
> +				struct file_lock, fl_blocked_member);
> +			level++;
> +		} else {
> +			/* Turn right */
> +			tmp = get_next_blocked_member(cur);
> +			/* Fall back to parent node */
> +			while (tmp == NULL && cur->fl_blocker != NULL) {
> +				cur = cur->fl_blocker;
> +				level--;
> +				tmp = get_next_blocked_member(cur);
> +			}
> +			cur = tmp;
> +		}
> +	}
>  
> 
> 
> 
>  	return 0;
>  }
> @@ -2941,7 +2988,7 @@ static void __show_fd_locks(struct seq_file *f,
>  
> 
> 
> 
>  		(*id)++;
>  		seq_puts(f, "lock:\t");
> -		lock_get_status(f, fl, *id, "");
> +		lock_get_status(f, fl, *id, "", 0);
>  	}
>  }
>  
> 
> 
>
Luo Longjun March 11, 2021, 3:45 a.m. UTC | #2
在 2021/3/9 21:37, Jeff Layton 写道:
> On Thu, 2021-02-25 at 22:58 -0500, Luo Longjun wrote:
>> Commit fd7732e033e3 ("fs/locks: create a tree of dependent requests.")
>> has put blocked locks into a tree.
>>
>> So, with a for loop, we can't check all locks information.
>>
>> To solve this problem, we should traverse the tree.
>>
>> Signed-off-by: Luo Longjun <luolongjun@huawei.com>
>> ---
>>   fs/locks.c | 65 ++++++++++++++++++++++++++++++++++++++++++++++--------
>>   1 file changed, 56 insertions(+), 9 deletions(-)
>>
>> diff --git a/fs/locks.c b/fs/locks.c
>> index 99ca97e81b7a..ecaecd1f1b58 100644
>> --- a/fs/locks.c
>> +++ b/fs/locks.c
>> @@ -2828,7 +2828,7 @@ struct locks_iterator {
>>   };
>>   
>>
>>
>>
>>   static void lock_get_status(struct seq_file *f, struct file_lock *fl,
>> -			    loff_t id, char *pfx)
>> +			    loff_t id, char *pfx, int repeat)
>>   {
>>   	struct inode *inode = NULL;
>>   	unsigned int fl_pid;
>> @@ -2844,7 +2844,11 @@ static void lock_get_status(struct seq_file *f, struct file_lock *fl,
>>   	if (fl->fl_file != NULL)
>>   		inode = locks_inode(fl->fl_file);
>>   
>>
>>
>>
>> -	seq_printf(f, "%lld:%s ", id, pfx);
>> +	seq_printf(f, "%lld: ", id);
>> +
>> +	if (repeat)
>> +		seq_printf(f, "%*s", repeat - 1 + (int)strlen(pfx), pfx);
> Shouldn't that be "%.*s" ?
>
> Also, isn't this likely to end up walking past the end of "pfx" (or even
> ending up at an address before the buffer)? You have this below:
>
>      lock_get_status(f, fl, *id, "", 0);
>
> ...so the "length" value you're passing into the format there is going
> to be -1. It also seems like if you get a large "level" value in
> locks_show, then you'll end up with a length that is much longer than
> the actual string.

In my understanding, the difference of "%*s" and "%.*s" is that, "%*s" 
specifies the minimal filed width while "%.*s" specifies the precision 
of the string.

Here, I use "%*s", because I want to print locks information in the 
follwing format:

2: FLOCK  ADVISORY  WRITE 110 00:02:493 0 EOF
2: -> FLOCK  ADVISORY  WRITE 111 00:02:493 0 EOF
2:  -> FLOCK  ADVISORY  WRITE 112 00:02:493 0 EOF
2:   -> FLOCK  ADVISORY  WRITE 113 00:02:493 0 EOF
2:    -> FLOCK  ADVISORY  WRITE 114 00:02:493 0 EOF

And also, there is another way to show there information, in the format 
like:

60: FLOCK  ADVISORY  WRITE 23350 08:02:4456514 0 EOF
60: -> FLOCK  ADVISORY  WRITE 23356 08:02:4456514 0 EOF
60: -> FLOCK  ADVISORY  WRITE 24217 08:02:4456514 0 EOF
60: -> FLOCK  ADVISORY  WRITE 24239 08:02:4456514 0 EOF

I think both formats are acceptable, but the first format shows 
competition relationships between these locks.

In the following code:

> lock_get_status(f, fl, *id, "", 0);

repeat is 0, and in the function:

+ if (repeat)
+		seq_printf(f, "%*s", repeat - 1 + (int)strlen(pfx), pfx);

The if branch will not take effect, so it could not be -1.

>> +
>>   	if (IS_POSIX(fl)) {
>>   		if (fl->fl_flags & FL_ACCESS)
>>   			seq_puts(f, "ACCESS");
>> @@ -2906,21 +2910,64 @@ static void lock_get_status(struct seq_file *f, struct file_lock *fl,
>>   	}
>>   }
>>   
>>
>>
>>
>> +static struct file_lock *get_next_blocked_member(struct file_lock *node)
>> +{
>> +	struct file_lock *tmp;
>> +
>> +	/* NULL node or root node */
>> +	if (node == NULL || node->fl_blocker == NULL)
>> +		return NULL;
>> +
>> +	/* Next member in the linked list could be itself */
>> +	tmp = list_next_entry(node, fl_blocked_member);
>> +	if (list_entry_is_head(tmp, &node->fl_blocker->fl_blocked_requests, fl_blocked_member)
>> +		|| tmp == node) {
>> +		return NULL;
>> +	}
>> +
>> +	return tmp;
>> +}
>> +
>>   static int locks_show(struct seq_file *f, void *v)
>>   {
>>   	struct locks_iterator *iter = f->private;
>> -	struct file_lock *fl, *bfl;
>> +	struct file_lock *cur, *tmp;
>>   	struct pid_namespace *proc_pidns = proc_pid_ns(file_inode(f->file)->i_sb);
>> +	int level = 0;
>>   
>>
>>
>>
>> -	fl = hlist_entry(v, struct file_lock, fl_link);
>> +	cur = hlist_entry(v, struct file_lock, fl_link);
>>   
>>
>>
>>
>> -	if (locks_translate_pid(fl, proc_pidns) == 0)
>> +	if (locks_translate_pid(cur, proc_pidns) == 0)
>>   		return 0;
>>   
>>
>>
>>
>> -	lock_get_status(f, fl, iter->li_pos, "");
>> +	/* View this crossed linked list as a binary tree, the first member of fl_blocked_requests
>> +	 * is the left child of current node, the next silibing in fl_blocked_member is the
>> +	 * right child, we can alse get the parent of current node from fl_blocker, so this
>> +	 * question becomes traversal of a binary tree
>> +	 */
>> +	while (cur != NULL) {
>> +		if (level)
>> +			lock_get_status(f, cur, iter->li_pos, "-> ", level);
>> +		else
>> +			lock_get_status(f, cur, iter->li_pos, "", level);
>>   
>>
>>
>>
>> -	list_for_each_entry(bfl, &fl->fl_blocked_requests, fl_blocked_member)
>> -		lock_get_status(f, bfl, iter->li_pos, " ->");
>> +		if (!list_empty(&cur->fl_blocked_requests)) {
>> +			/* Turn left */
>> +			cur = list_first_entry_or_null(&cur->fl_blocked_requests,
>> +				struct file_lock, fl_blocked_member);
>> +			level++;
>> +		} else {
>> +			/* Turn right */
>> +			tmp = get_next_blocked_member(cur);
>> +			/* Fall back to parent node */
>> +			while (tmp == NULL && cur->fl_blocker != NULL) {
>> +				cur = cur->fl_blocker;
>> +				level--;
>> +				tmp = get_next_blocked_member(cur);
>> +			}
>> +			cur = tmp;
>> +		}
>> +	}
>>   
>>
>>
>>
>>   	return 0;
>>   }
>> @@ -2941,7 +2988,7 @@ static void __show_fd_locks(struct seq_file *f,
>>   
>>
>>
>>
>>   		(*id)++;
>>   		seq_puts(f, "lock:\t");
>> -		lock_get_status(f, fl, *id, "");
>> +		lock_get_status(f, fl, *id, "", 0);
>>   	}
>>   }
>>   
>>
>>
>>
Jeff Layton March 11, 2021, 1:52 p.m. UTC | #3
On Thu, 2021-03-11 at 11:45 +0800, Luo Longjun wrote:
> 在 2021/3/9 21:37, Jeff Layton 写道:
> > On Thu, 2021-02-25 at 22:58 -0500, Luo Longjun wrote:
> > > Commit fd7732e033e3 ("fs/locks: create a tree of dependent requests.")
> > > has put blocked locks into a tree.
> > > 
> > > So, with a for loop, we can't check all locks information.
> > > 
> > > To solve this problem, we should traverse the tree.
> > > 
> > > Signed-off-by: Luo Longjun <luolongjun@huawei.com>
> > > ---
> > >   fs/locks.c | 65 ++++++++++++++++++++++++++++++++++++++++++++++--------
> > >   1 file changed, 56 insertions(+), 9 deletions(-)
> > > 
> > > diff --git a/fs/locks.c b/fs/locks.c
> > > index 99ca97e81b7a..ecaecd1f1b58 100644
> > > --- a/fs/locks.c
> > > +++ b/fs/locks.c
> > > @@ -2828,7 +2828,7 @@ struct locks_iterator {
> > >   };
> > >   
> > > 
> > > 
> > > 
> > > 
> > > 
> > > 
> > >   static void lock_get_status(struct seq_file *f, struct file_lock *fl,
> > > -			    loff_t id, char *pfx)
> > > +			    loff_t id, char *pfx, int repeat)
> > >   {
> > >   	struct inode *inode = NULL;
> > >   	unsigned int fl_pid;
> > > @@ -2844,7 +2844,11 @@ static void lock_get_status(struct seq_file *f, struct file_lock *fl,
> > >   	if (fl->fl_file != NULL)
> > >   		inode = locks_inode(fl->fl_file);
> > >   
> > > 
> > > 
> > > 
> > > 
> > > 
> > > 
> > > -	seq_printf(f, "%lld:%s ", id, pfx);
> > > +	seq_printf(f, "%lld: ", id);
> > > +
> > > +	if (repeat)
> > > +		seq_printf(f, "%*s", repeat - 1 + (int)strlen(pfx), pfx);
> > Shouldn't that be "%.*s" ?
> > 
> > Also, isn't this likely to end up walking past the end of "pfx" (or even
> > ending up at an address before the buffer)? You have this below:
> > 
> >      lock_get_status(f, fl, *id, "", 0);
> > 
> > ...so the "length" value you're passing into the format there is going
> > to be -1. It also seems like if you get a large "level" value in
> > locks_show, then you'll end up with a length that is much longer than
> > the actual string.
> 
> In my understanding, the difference of "%*s" and "%.*s" is that, "%*s" 
> specifies the minimal filed width while "%.*s" specifies the precision 
> of the string.
> 

Oh, right. I always forget about the first usage.

> Here, I use "%*s", because I want to print locks information in the 
> follwing format:
> 
> 2: FLOCK  ADVISORY  WRITE 110 00:02:493 0 EOF
> 2: -> FLOCK  ADVISORY  WRITE 111 00:02:493 0 EOF
> 2:  -> FLOCK  ADVISORY  WRITE 112 00:02:493 0 EOF
> 2:   -> FLOCK  ADVISORY  WRITE 113 00:02:493 0 EOF
> 2:    -> FLOCK  ADVISORY  WRITE 114 00:02:493 0 EOF
> 
> And also, there is another way to show there information, in the format 
> like:
> 
> 60: FLOCK  ADVISORY  WRITE 23350 08:02:4456514 0 EOF
> 60: -> FLOCK  ADVISORY  WRITE 23356 08:02:4456514 0 EOF
> 60: -> FLOCK  ADVISORY  WRITE 24217 08:02:4456514 0 EOF
> 60: -> FLOCK  ADVISORY  WRITE 24239 08:02:4456514 0 EOF
> 
> I think both formats are acceptable, but the first format shows 
> competition relationships between these locks.
> 

We might as well go with the one this patch implements. I like seeing
the chain of waiters as well, and it doesn't seem to break lslocks
(which is, to my knowledge, the only real programmatic consumer of this
file).

> In the following code:
> 
> > lock_get_status(f, fl, *id, "", 0);
> 
> repeat is 0, and in the function:
> 
> + if (repeat)
> +		seq_printf(f, "%*s", repeat - 1 + (int)strlen(pfx), pfx);
> 
> The if branch will not take effect, so it could not be -1.
> 


Good point.

Ok, I'll go ahead and put this one in linux-next for now. Assuming there
are no problems, it should make v5.13.

Thanks!
diff mbox series

Patch

diff --git a/fs/locks.c b/fs/locks.c
index 99ca97e81b7a..ecaecd1f1b58 100644
--- a/fs/locks.c
+++ b/fs/locks.c
@@ -2828,7 +2828,7 @@  struct locks_iterator {
 };
 
 static void lock_get_status(struct seq_file *f, struct file_lock *fl,
-			    loff_t id, char *pfx)
+			    loff_t id, char *pfx, int repeat)
 {
 	struct inode *inode = NULL;
 	unsigned int fl_pid;
@@ -2844,7 +2844,11 @@  static void lock_get_status(struct seq_file *f, struct file_lock *fl,
 	if (fl->fl_file != NULL)
 		inode = locks_inode(fl->fl_file);
 
-	seq_printf(f, "%lld:%s ", id, pfx);
+	seq_printf(f, "%lld: ", id);
+
+	if (repeat)
+		seq_printf(f, "%*s", repeat - 1 + (int)strlen(pfx), pfx);
+
 	if (IS_POSIX(fl)) {
 		if (fl->fl_flags & FL_ACCESS)
 			seq_puts(f, "ACCESS");
@@ -2906,21 +2910,64 @@  static void lock_get_status(struct seq_file *f, struct file_lock *fl,
 	}
 }
 
+static struct file_lock *get_next_blocked_member(struct file_lock *node)
+{
+	struct file_lock *tmp;
+
+	/* NULL node or root node */
+	if (node == NULL || node->fl_blocker == NULL)
+		return NULL;
+
+	/* Next member in the linked list could be itself */
+	tmp = list_next_entry(node, fl_blocked_member);
+	if (list_entry_is_head(tmp, &node->fl_blocker->fl_blocked_requests, fl_blocked_member)
+		|| tmp == node) {
+		return NULL;
+	}
+
+	return tmp;
+}
+
 static int locks_show(struct seq_file *f, void *v)
 {
 	struct locks_iterator *iter = f->private;
-	struct file_lock *fl, *bfl;
+	struct file_lock *cur, *tmp;
 	struct pid_namespace *proc_pidns = proc_pid_ns(file_inode(f->file)->i_sb);
+	int level = 0;
 
-	fl = hlist_entry(v, struct file_lock, fl_link);
+	cur = hlist_entry(v, struct file_lock, fl_link);
 
-	if (locks_translate_pid(fl, proc_pidns) == 0)
+	if (locks_translate_pid(cur, proc_pidns) == 0)
 		return 0;
 
-	lock_get_status(f, fl, iter->li_pos, "");
+	/* View this crossed linked list as a binary tree, the first member of fl_blocked_requests
+	 * is the left child of current node, the next silibing in fl_blocked_member is the
+	 * right child, we can alse get the parent of current node from fl_blocker, so this
+	 * question becomes traversal of a binary tree
+	 */
+	while (cur != NULL) {
+		if (level)
+			lock_get_status(f, cur, iter->li_pos, "-> ", level);
+		else
+			lock_get_status(f, cur, iter->li_pos, "", level);
 
-	list_for_each_entry(bfl, &fl->fl_blocked_requests, fl_blocked_member)
-		lock_get_status(f, bfl, iter->li_pos, " ->");
+		if (!list_empty(&cur->fl_blocked_requests)) {
+			/* Turn left */
+			cur = list_first_entry_or_null(&cur->fl_blocked_requests,
+				struct file_lock, fl_blocked_member);
+			level++;
+		} else {
+			/* Turn right */
+			tmp = get_next_blocked_member(cur);
+			/* Fall back to parent node */
+			while (tmp == NULL && cur->fl_blocker != NULL) {
+				cur = cur->fl_blocker;
+				level--;
+				tmp = get_next_blocked_member(cur);
+			}
+			cur = tmp;
+		}
+	}
 
 	return 0;
 }
@@ -2941,7 +2988,7 @@  static void __show_fd_locks(struct seq_file *f,
 
 		(*id)++;
 		seq_puts(f, "lock:\t");
-		lock_get_status(f, fl, *id, "");
+		lock_get_status(f, fl, *id, "", 0);
 	}
 }