summaryrefslogtreecommitdiff
path: root/fs/lockd/svcshare.c
blob: a58b7035b58bf0b659879b5cececdaf405e7700e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
// SPDX-License-Identifier: GPL-2.0
/*
 * linux/fs/lockd/svcshare.c
 *
 * Management of DOS shares.
 *
 * Copyright (C) 1996 Olaf Kirch <okir@monad.swb.de>
 */

#include <linux/time.h>
#include <linux/unistd.h>
#include <linux/string.h>
#include <linux/slab.h>

#include <linux/sunrpc/clnt.h>
#include <linux/sunrpc/svc.h>

#include "lockd.h"
#include "share.h"

static inline int
nlm_cmp_owner(struct lockd_share *share, struct xdr_netobj *oh)
{
	return share->s_owner.len == oh->len
	    && !memcmp(share->s_owner.data, oh->data, oh->len);
}

/*
 * Recompute s_access / s_mode as the union of every (access, deny) pair
 * whose bit is currently set in s_access_deny_bmap.
 */
static void nlm_recompute_share(struct lockd_share *share)
{
	u32 new_access = 0, new_mode = 0;
	unsigned int i;

	for (i = 0; i < 16; i++) {
		if (share->s_access_deny_bmap & BIT(i)) {
			new_access |= i >> 2;
			new_mode   |= i & 3;
		}
	}
	share->s_access = new_access;
	share->s_mode = new_mode;
}

/**
 * nlmsvc_share_file - create a share
 * @host: Network client peer
 * @file: File to be shared
 * @oh: Share owner handle
 * @access: Requested access mode
 * @mode: Requested file sharing mode
 *
 * Returns an NLM status code.
 */
__be32
nlmsvc_share_file(struct nlm_host *host, struct nlm_file *file,
		  struct xdr_netobj *oh, u32 access, u32 mode)
{
	struct lockd_share	*share;
	u8			*ohdata;

	if (nlmsvc_file_cannot_lock(file))
		return nlm_lck_denied_nolocks;

	for (share = file->f_shares; share; share = share->s_next) {
		if (share->s_host == host && nlm_cmp_owner(share, oh))
			goto update;
		if ((access & share->s_mode) || (mode & share->s_access))
			return nlm_lck_denied;
	}

	share = kmalloc(sizeof(*share) + oh->len, GFP_KERNEL);
	if (share == NULL)
		return nlm_lck_denied_nolocks;

	/* Copy owner handle */
	ohdata = (u8 *) (share + 1);
	memcpy(ohdata, oh->data, oh->len);

	share->s_file	    = file;
	share->s_host       = host;
	share->s_owner.data = ohdata;
	share->s_owner.len  = oh->len;
	share->s_access_deny_bmap  = 0;
	share->s_next       = file->f_shares;
	file->f_shares      = share;

update:
	share->s_access_deny_bmap |= LOCKD_FSH_BIT(access, mode);
	nlm_recompute_share(share);
	return nlm_granted;
}

/**
 * nlmsvc_unshare_file - delete a share
 * @host: Network client peer
 * @file: File to be unshared
 * @oh: Share owner handle
 * @access: Access mode of the SHARE being released
 * @mode: Deny mode of the SHARE being released
 *
 * Returns an NLM status code.
 */
__be32
nlmsvc_unshare_file(struct nlm_host *host, struct nlm_file *file,
		    struct xdr_netobj *oh, u32 access, u32 mode)
{
	struct lockd_share	*share, **shpp;

	if (nlmsvc_file_cannot_lock(file))
		return nlm_lck_denied_nolocks;

	for (shpp = &file->f_shares; (share = *shpp) != NULL;
					shpp = &share->s_next) {
		if (share->s_host == host && nlm_cmp_owner(share, oh)) {
			share->s_access_deny_bmap &= ~LOCKD_FSH_BIT(access, mode);
			nlm_recompute_share(share);
			if (!share->s_access_deny_bmap) {
				*shpp = share->s_next;
				kfree(share);
			}
			return nlm_granted;
		}
	}

	/* X/Open spec says return success even if there was no
	 * corresponding share. */
	return nlm_granted;
}

/*
 * Traverse all shares for a given file, and delete
 * those owned by the given (type of) host
 */
void nlmsvc_traverse_shares(struct nlm_host *host, struct nlm_file *file,
		nlm_host_match_fn_t match)
{
	struct lockd_share	*share, **shpp;

	shpp = &file->f_shares;
	while ((share = *shpp) !=  NULL) {
		if (match(share->s_host, host)) {
			*shpp = share->s_next;
			kfree(share);
			continue;
		}
		shpp = &share->s_next;
	}
}