diff options
Diffstat (limited to 'fs/nfsd')
-rw-r--r-- | fs/nfsd/export.c | 6 | ||||
-rw-r--r-- | fs/nfsd/nfs3xdr.c | 3 | ||||
-rw-r--r-- | fs/nfsd/nfs4xdr.c | 9 | ||||
-rw-r--r-- | fs/nfsd/nfscache.c | 3 | ||||
-rw-r--r-- | fs/nfsd/nfsctl.c | 98 | ||||
-rw-r--r-- | fs/nfsd/nfssvc.c | 80 | ||||
-rw-r--r-- | fs/nfsd/vfs.c | 9 |
7 files changed, 165 insertions, 43 deletions
diff --git a/fs/nfsd/export.c b/fs/nfsd/export.c index 057aff745506..417ec02df44f 100644 --- a/fs/nfsd/export.c +++ b/fs/nfsd/export.c | |||
@@ -190,8 +190,7 @@ static int expkey_parse(struct cache_detail *cd, char *mesg, int mlen) | |||
190 | out: | 190 | out: |
191 | if (dom) | 191 | if (dom) |
192 | auth_domain_put(dom); | 192 | auth_domain_put(dom); |
193 | if (buf) | 193 | kfree(buf); |
194 | kfree(buf); | ||
195 | return err; | 194 | return err; |
196 | } | 195 | } |
197 | 196 | ||
@@ -428,8 +427,7 @@ static int svc_export_parse(struct cache_detail *cd, char *mesg, int mlen) | |||
428 | path_release(&nd); | 427 | path_release(&nd); |
429 | if (dom) | 428 | if (dom) |
430 | auth_domain_put(dom); | 429 | auth_domain_put(dom); |
431 | if (buf) | 430 | kfree(buf); |
432 | kfree(buf); | ||
433 | return err; | 431 | return err; |
434 | } | 432 | } |
435 | 433 | ||
diff --git a/fs/nfsd/nfs3xdr.c b/fs/nfsd/nfs3xdr.c index e0e134d6baba..9147b8524d05 100644 --- a/fs/nfsd/nfs3xdr.c +++ b/fs/nfsd/nfs3xdr.c | |||
@@ -366,7 +366,8 @@ nfs3svc_decode_writeargs(struct svc_rqst *rqstp, u32 *p, | |||
366 | len = args->len = ntohl(*p++); | 366 | len = args->len = ntohl(*p++); |
367 | 367 | ||
368 | hdr = (void*)p - rqstp->rq_arg.head[0].iov_base; | 368 | hdr = (void*)p - rqstp->rq_arg.head[0].iov_base; |
369 | if (rqstp->rq_arg.len < len + hdr) | 369 | if (rqstp->rq_arg.len < hdr || |
370 | rqstp->rq_arg.len - hdr < len) | ||
370 | return 0; | 371 | return 0; |
371 | 372 | ||
372 | args->vec[0].iov_base = (void*)p; | 373 | args->vec[0].iov_base = (void*)p; |
diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c index 4c4146350236..dcd673186944 100644 --- a/fs/nfsd/nfs4xdr.c +++ b/fs/nfsd/nfs4xdr.c | |||
@@ -151,8 +151,7 @@ static u32 *read_buf(struct nfsd4_compoundargs *argp, int nbytes) | |||
151 | if (nbytes <= sizeof(argp->tmp)) | 151 | if (nbytes <= sizeof(argp->tmp)) |
152 | p = argp->tmp; | 152 | p = argp->tmp; |
153 | else { | 153 | else { |
154 | if (argp->tmpp) | 154 | kfree(argp->tmpp); |
155 | kfree(argp->tmpp); | ||
156 | p = argp->tmpp = kmalloc(nbytes, GFP_KERNEL); | 155 | p = argp->tmpp = kmalloc(nbytes, GFP_KERNEL); |
157 | if (!p) | 156 | if (!p) |
158 | return NULL; | 157 | return NULL; |
@@ -2476,10 +2475,8 @@ void nfsd4_release_compoundargs(struct nfsd4_compoundargs *args) | |||
2476 | kfree(args->ops); | 2475 | kfree(args->ops); |
2477 | args->ops = args->iops; | 2476 | args->ops = args->iops; |
2478 | } | 2477 | } |
2479 | if (args->tmpp) { | 2478 | kfree(args->tmpp); |
2480 | kfree(args->tmpp); | 2479 | args->tmpp = NULL; |
2481 | args->tmpp = NULL; | ||
2482 | } | ||
2483 | while (args->to_free) { | 2480 | while (args->to_free) { |
2484 | struct tmpbuf *tb = args->to_free; | 2481 | struct tmpbuf *tb = args->to_free; |
2485 | args->to_free = tb->next; | 2482 | args->to_free = tb->next; |
diff --git a/fs/nfsd/nfscache.c b/fs/nfsd/nfscache.c index 119e4d4495b8..d852ebb538e3 100644 --- a/fs/nfsd/nfscache.c +++ b/fs/nfsd/nfscache.c | |||
@@ -93,8 +93,7 @@ nfsd_cache_shutdown(void) | |||
93 | 93 | ||
94 | cache_disabled = 1; | 94 | cache_disabled = 1; |
95 | 95 | ||
96 | if (hash_list) | 96 | kfree (hash_list); |
97 | kfree (hash_list); | ||
98 | hash_list = NULL; | 97 | hash_list = NULL; |
99 | } | 98 | } |
100 | 99 | ||
diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c index 841c562991e8..a0871b3efeb7 100644 --- a/fs/nfsd/nfsctl.c +++ b/fs/nfsd/nfsctl.c | |||
@@ -23,6 +23,7 @@ | |||
23 | #include <linux/seq_file.h> | 23 | #include <linux/seq_file.h> |
24 | #include <linux/pagemap.h> | 24 | #include <linux/pagemap.h> |
25 | #include <linux/init.h> | 25 | #include <linux/init.h> |
26 | #include <linux/string.h> | ||
26 | 27 | ||
27 | #include <linux/nfs.h> | 28 | #include <linux/nfs.h> |
28 | #include <linux/nfsd_idmap.h> | 29 | #include <linux/nfsd_idmap.h> |
@@ -35,6 +36,8 @@ | |||
35 | 36 | ||
36 | #include <asm/uaccess.h> | 37 | #include <asm/uaccess.h> |
37 | 38 | ||
39 | unsigned int nfsd_versbits = ~0; | ||
40 | |||
38 | /* | 41 | /* |
39 | * We have a single directory with 9 nodes in it. | 42 | * We have a single directory with 9 nodes in it. |
40 | */ | 43 | */ |
@@ -50,8 +53,15 @@ enum { | |||
50 | NFSD_List, | 53 | NFSD_List, |
51 | NFSD_Fh, | 54 | NFSD_Fh, |
52 | NFSD_Threads, | 55 | NFSD_Threads, |
56 | NFSD_Versions, | ||
57 | /* | ||
58 | * The below MUST come last. Otherwise we leave a hole in nfsd_files[] | ||
59 | * with !CONFIG_NFSD_V4 and simple_fill_super() goes oops | ||
60 | */ | ||
61 | #ifdef CONFIG_NFSD_V4 | ||
53 | NFSD_Leasetime, | 62 | NFSD_Leasetime, |
54 | NFSD_RecoveryDir, | 63 | NFSD_RecoveryDir, |
64 | #endif | ||
55 | }; | 65 | }; |
56 | 66 | ||
57 | /* | 67 | /* |
@@ -66,8 +76,11 @@ static ssize_t write_getfd(struct file *file, char *buf, size_t size); | |||
66 | static ssize_t write_getfs(struct file *file, char *buf, size_t size); | 76 | static ssize_t write_getfs(struct file *file, char *buf, size_t size); |
67 | static ssize_t write_filehandle(struct file *file, char *buf, size_t size); | 77 | static ssize_t write_filehandle(struct file *file, char *buf, size_t size); |
68 | static ssize_t write_threads(struct file *file, char *buf, size_t size); | 78 | static ssize_t write_threads(struct file *file, char *buf, size_t size); |
79 | static ssize_t write_versions(struct file *file, char *buf, size_t size); | ||
80 | #ifdef CONFIG_NFSD_V4 | ||
69 | static ssize_t write_leasetime(struct file *file, char *buf, size_t size); | 81 | static ssize_t write_leasetime(struct file *file, char *buf, size_t size); |
70 | static ssize_t write_recoverydir(struct file *file, char *buf, size_t size); | 82 | static ssize_t write_recoverydir(struct file *file, char *buf, size_t size); |
83 | #endif | ||
71 | 84 | ||
72 | static ssize_t (*write_op[])(struct file *, char *, size_t) = { | 85 | static ssize_t (*write_op[])(struct file *, char *, size_t) = { |
73 | [NFSD_Svc] = write_svc, | 86 | [NFSD_Svc] = write_svc, |
@@ -79,8 +92,11 @@ static ssize_t (*write_op[])(struct file *, char *, size_t) = { | |||
79 | [NFSD_Getfs] = write_getfs, | 92 | [NFSD_Getfs] = write_getfs, |
80 | [NFSD_Fh] = write_filehandle, | 93 | [NFSD_Fh] = write_filehandle, |
81 | [NFSD_Threads] = write_threads, | 94 | [NFSD_Threads] = write_threads, |
95 | [NFSD_Versions] = write_versions, | ||
96 | #ifdef CONFIG_NFSD_V4 | ||
82 | [NFSD_Leasetime] = write_leasetime, | 97 | [NFSD_Leasetime] = write_leasetime, |
83 | [NFSD_RecoveryDir] = write_recoverydir, | 98 | [NFSD_RecoveryDir] = write_recoverydir, |
99 | #endif | ||
84 | }; | 100 | }; |
85 | 101 | ||
86 | static ssize_t nfsctl_transaction_write(struct file *file, const char __user *buf, size_t size, loff_t *pos) | 102 | static ssize_t nfsctl_transaction_write(struct file *file, const char __user *buf, size_t size, loff_t *pos) |
@@ -104,9 +120,23 @@ static ssize_t nfsctl_transaction_write(struct file *file, const char __user *bu | |||
104 | return rv; | 120 | return rv; |
105 | } | 121 | } |
106 | 122 | ||
123 | static ssize_t nfsctl_transaction_read(struct file *file, char __user *buf, size_t size, loff_t *pos) | ||
124 | { | ||
125 | if (! file->private_data) { | ||
126 | /* An attempt to read a transaction file without writing | ||
127 | * causes a 0-byte write so that the file can return | ||
128 | * state information | ||
129 | */ | ||
130 | ssize_t rv = nfsctl_transaction_write(file, buf, 0, pos); | ||
131 | if (rv < 0) | ||
132 | return rv; | ||
133 | } | ||
134 | return simple_transaction_read(file, buf, size, pos); | ||
135 | } | ||
136 | |||
107 | static struct file_operations transaction_ops = { | 137 | static struct file_operations transaction_ops = { |
108 | .write = nfsctl_transaction_write, | 138 | .write = nfsctl_transaction_write, |
109 | .read = simple_transaction_read, | 139 | .read = nfsctl_transaction_read, |
110 | .release = simple_transaction_release, | 140 | .release = simple_transaction_release, |
111 | }; | 141 | }; |
112 | 142 | ||
@@ -329,6 +359,70 @@ static ssize_t write_threads(struct file *file, char *buf, size_t size) | |||
329 | return strlen(buf); | 359 | return strlen(buf); |
330 | } | 360 | } |
331 | 361 | ||
362 | static ssize_t write_versions(struct file *file, char *buf, size_t size) | ||
363 | { | ||
364 | /* | ||
365 | * Format: | ||
366 | * [-/+]vers [-/+]vers ... | ||
367 | */ | ||
368 | char *mesg = buf; | ||
369 | char *vers, sign; | ||
370 | int len, num; | ||
371 | ssize_t tlen = 0; | ||
372 | char *sep; | ||
373 | |||
374 | if (size>0) { | ||
375 | if (nfsd_serv) | ||
376 | return -EBUSY; | ||
377 | if (buf[size-1] != '\n') | ||
378 | return -EINVAL; | ||
379 | buf[size-1] = 0; | ||
380 | |||
381 | vers = mesg; | ||
382 | len = qword_get(&mesg, vers, size); | ||
383 | if (len <= 0) return -EINVAL; | ||
384 | do { | ||
385 | sign = *vers; | ||
386 | if (sign == '+' || sign == '-') | ||
387 | num = simple_strtol((vers+1), NULL, 0); | ||
388 | else | ||
389 | num = simple_strtol(vers, NULL, 0); | ||
390 | switch(num) { | ||
391 | case 2: | ||
392 | case 3: | ||
393 | case 4: | ||
394 | if (sign != '-') | ||
395 | NFSCTL_VERSET(nfsd_versbits, num); | ||
396 | else | ||
397 | NFSCTL_VERUNSET(nfsd_versbits, num); | ||
398 | break; | ||
399 | default: | ||
400 | return -EINVAL; | ||
401 | } | ||
402 | vers += len + 1; | ||
403 | tlen += len; | ||
404 | } while ((len = qword_get(&mesg, vers, size)) > 0); | ||
405 | /* If all get turned off, turn them back on, as | ||
406 | * having no versions is BAD | ||
407 | */ | ||
408 | if ((nfsd_versbits & NFSCTL_VERALL)==0) | ||
409 | nfsd_versbits = NFSCTL_VERALL; | ||
410 | } | ||
411 | /* Now write current state into reply buffer */ | ||
412 | len = 0; | ||
413 | sep = ""; | ||
414 | for (num=2 ; num <= 4 ; num++) | ||
415 | if (NFSCTL_VERISSET(NFSCTL_VERALL, num)) { | ||
416 | len += sprintf(buf+len, "%s%c%d", sep, | ||
417 | NFSCTL_VERISSET(nfsd_versbits, num)?'+':'-', | ||
418 | num); | ||
419 | sep = " "; | ||
420 | } | ||
421 | len += sprintf(buf+len, "\n"); | ||
422 | return len; | ||
423 | } | ||
424 | |||
425 | #ifdef CONFIG_NFSD_V4 | ||
332 | extern time_t nfs4_leasetime(void); | 426 | extern time_t nfs4_leasetime(void); |
333 | 427 | ||
334 | static ssize_t write_leasetime(struct file *file, char *buf, size_t size) | 428 | static ssize_t write_leasetime(struct file *file, char *buf, size_t size) |
@@ -370,6 +464,7 @@ static ssize_t write_recoverydir(struct file *file, char *buf, size_t size) | |||
370 | status = nfs4_reset_recoverydir(recdir); | 464 | status = nfs4_reset_recoverydir(recdir); |
371 | return strlen(buf); | 465 | return strlen(buf); |
372 | } | 466 | } |
467 | #endif | ||
373 | 468 | ||
374 | /*----------------------------------------------------------------------------*/ | 469 | /*----------------------------------------------------------------------------*/ |
375 | /* | 470 | /* |
@@ -389,6 +484,7 @@ static int nfsd_fill_super(struct super_block * sb, void * data, int silent) | |||
389 | [NFSD_List] = {"exports", &exports_operations, S_IRUGO}, | 484 | [NFSD_List] = {"exports", &exports_operations, S_IRUGO}, |
390 | [NFSD_Fh] = {"filehandle", &transaction_ops, S_IWUSR|S_IRUSR}, | 485 | [NFSD_Fh] = {"filehandle", &transaction_ops, S_IWUSR|S_IRUSR}, |
391 | [NFSD_Threads] = {"threads", &transaction_ops, S_IWUSR|S_IRUSR}, | 486 | [NFSD_Threads] = {"threads", &transaction_ops, S_IWUSR|S_IRUSR}, |
487 | [NFSD_Versions] = {"versions", &transaction_ops, S_IWUSR|S_IRUSR}, | ||
392 | #ifdef CONFIG_NFSD_V4 | 488 | #ifdef CONFIG_NFSD_V4 |
393 | [NFSD_Leasetime] = {"nfsv4leasetime", &transaction_ops, S_IWUSR|S_IRUSR}, | 489 | [NFSD_Leasetime] = {"nfsv4leasetime", &transaction_ops, S_IWUSR|S_IRUSR}, |
394 | [NFSD_RecoveryDir] = {"nfsv4recoverydir", &transaction_ops, S_IWUSR|S_IRUSR}, | 490 | [NFSD_RecoveryDir] = {"nfsv4recoverydir", &transaction_ops, S_IWUSR|S_IRUSR}, |
diff --git a/fs/nfsd/nfssvc.c b/fs/nfsd/nfssvc.c index 1697539a7171..89ed04696865 100644 --- a/fs/nfsd/nfssvc.c +++ b/fs/nfsd/nfssvc.c | |||
@@ -30,6 +30,7 @@ | |||
30 | #include <linux/nfsd/nfsd.h> | 30 | #include <linux/nfsd/nfsd.h> |
31 | #include <linux/nfsd/stats.h> | 31 | #include <linux/nfsd/stats.h> |
32 | #include <linux/nfsd/cache.h> | 32 | #include <linux/nfsd/cache.h> |
33 | #include <linux/nfsd/syscall.h> | ||
33 | #include <linux/lockd/bind.h> | 34 | #include <linux/lockd/bind.h> |
34 | #include <linux/nfsacl.h> | 35 | #include <linux/nfsacl.h> |
35 | 36 | ||
@@ -52,7 +53,7 @@ | |||
52 | extern struct svc_program nfsd_program; | 53 | extern struct svc_program nfsd_program; |
53 | static void nfsd(struct svc_rqst *rqstp); | 54 | static void nfsd(struct svc_rqst *rqstp); |
54 | struct timeval nfssvc_boot; | 55 | struct timeval nfssvc_boot; |
55 | static struct svc_serv *nfsd_serv; | 56 | struct svc_serv *nfsd_serv; |
56 | static atomic_t nfsd_busy; | 57 | static atomic_t nfsd_busy; |
57 | static unsigned long nfsd_last_call; | 58 | static unsigned long nfsd_last_call; |
58 | static DEFINE_SPINLOCK(nfsd_call_lock); | 59 | static DEFINE_SPINLOCK(nfsd_call_lock); |
@@ -63,6 +64,31 @@ struct nfsd_list { | |||
63 | }; | 64 | }; |
64 | static struct list_head nfsd_list = LIST_HEAD_INIT(nfsd_list); | 65 | static struct list_head nfsd_list = LIST_HEAD_INIT(nfsd_list); |
65 | 66 | ||
67 | static struct svc_version * nfsd_version[] = { | ||
68 | [2] = &nfsd_version2, | ||
69 | #if defined(CONFIG_NFSD_V3) | ||
70 | [3] = &nfsd_version3, | ||
71 | #endif | ||
72 | #if defined(CONFIG_NFSD_V4) | ||
73 | [4] = &nfsd_version4, | ||
74 | #endif | ||
75 | }; | ||
76 | |||
77 | #define NFSD_MINVERS 2 | ||
78 | #define NFSD_NRVERS (sizeof(nfsd_version)/sizeof(nfsd_version[0])) | ||
79 | static struct svc_version *nfsd_versions[NFSD_NRVERS]; | ||
80 | |||
81 | struct svc_program nfsd_program = { | ||
82 | .pg_prog = NFS_PROGRAM, /* program number */ | ||
83 | .pg_nvers = NFSD_NRVERS, /* nr of entries in nfsd_version */ | ||
84 | .pg_vers = nfsd_versions, /* version table */ | ||
85 | .pg_name = "nfsd", /* program name */ | ||
86 | .pg_class = "nfsd", /* authentication class */ | ||
87 | .pg_stats = &nfsd_svcstats, /* version table */ | ||
88 | .pg_authenticate = &svc_set_client, /* export authentication */ | ||
89 | |||
90 | }; | ||
91 | |||
66 | /* | 92 | /* |
67 | * Maximum number of nfsd processes | 93 | * Maximum number of nfsd processes |
68 | */ | 94 | */ |
@@ -80,11 +106,12 @@ int | |||
80 | nfsd_svc(unsigned short port, int nrservs) | 106 | nfsd_svc(unsigned short port, int nrservs) |
81 | { | 107 | { |
82 | int error; | 108 | int error; |
83 | int none_left; | 109 | int none_left, found_one, i; |
84 | struct list_head *victim; | 110 | struct list_head *victim; |
85 | 111 | ||
86 | lock_kernel(); | 112 | lock_kernel(); |
87 | dprintk("nfsd: creating service\n"); | 113 | dprintk("nfsd: creating service: vers 0x%x\n", |
114 | nfsd_versbits); | ||
88 | error = -EINVAL; | 115 | error = -EINVAL; |
89 | if (nrservs <= 0) | 116 | if (nrservs <= 0) |
90 | nrservs = 0; | 117 | nrservs = 0; |
@@ -99,6 +126,27 @@ nfsd_svc(unsigned short port, int nrservs) | |||
99 | if (error<0) | 126 | if (error<0) |
100 | goto out; | 127 | goto out; |
101 | if (!nfsd_serv) { | 128 | if (!nfsd_serv) { |
129 | /* | ||
130 | * Use the nfsd_ctlbits to define which | ||
131 | * versions that will be advertised. | ||
132 | * If nfsd_ctlbits doesn't list any version, | ||
133 | * export them all. | ||
134 | */ | ||
135 | found_one = 0; | ||
136 | |||
137 | for (i = NFSD_MINVERS; i < NFSD_NRVERS; i++) { | ||
138 | if (NFSCTL_VERISSET(nfsd_versbits, i)) { | ||
139 | nfsd_program.pg_vers[i] = nfsd_version[i]; | ||
140 | found_one = 1; | ||
141 | } else | ||
142 | nfsd_program.pg_vers[i] = NULL; | ||
143 | } | ||
144 | |||
145 | if (!found_one) { | ||
146 | for (i = NFSD_MINVERS; i < NFSD_NRVERS; i++) | ||
147 | nfsd_program.pg_vers[i] = nfsd_version[i]; | ||
148 | } | ||
149 | |||
102 | atomic_set(&nfsd_busy, 0); | 150 | atomic_set(&nfsd_busy, 0); |
103 | error = -ENOMEM; | 151 | error = -ENOMEM; |
104 | nfsd_serv = svc_create(&nfsd_program, NFSD_BUFSIZE); | 152 | nfsd_serv = svc_create(&nfsd_program, NFSD_BUFSIZE); |
@@ -379,6 +427,7 @@ static struct svc_program nfsd_acl_program = { | |||
379 | .pg_name = "nfsd", | 427 | .pg_name = "nfsd", |
380 | .pg_class = "nfsd", | 428 | .pg_class = "nfsd", |
381 | .pg_stats = &nfsd_acl_svcstats, | 429 | .pg_stats = &nfsd_acl_svcstats, |
430 | .pg_authenticate = &svc_set_client, | ||
382 | }; | 431 | }; |
383 | 432 | ||
384 | static struct svc_stat nfsd_acl_svcstats = { | 433 | static struct svc_stat nfsd_acl_svcstats = { |
@@ -389,28 +438,3 @@ static struct svc_stat nfsd_acl_svcstats = { | |||
389 | #else | 438 | #else |
390 | #define nfsd_acl_program_p NULL | 439 | #define nfsd_acl_program_p NULL |
391 | #endif /* defined(CONFIG_NFSD_V2_ACL) || defined(CONFIG_NFSD_V3_ACL) */ | 440 | #endif /* defined(CONFIG_NFSD_V2_ACL) || defined(CONFIG_NFSD_V3_ACL) */ |
392 | |||
393 | extern struct svc_version nfsd_version2, nfsd_version3, nfsd_version4; | ||
394 | |||
395 | static struct svc_version * nfsd_version[] = { | ||
396 | [2] = &nfsd_version2, | ||
397 | #if defined(CONFIG_NFSD_V3) | ||
398 | [3] = &nfsd_version3, | ||
399 | #endif | ||
400 | #if defined(CONFIG_NFSD_V4) | ||
401 | [4] = &nfsd_version4, | ||
402 | #endif | ||
403 | }; | ||
404 | |||
405 | #define NFSD_NRVERS (sizeof(nfsd_version)/sizeof(nfsd_version[0])) | ||
406 | struct svc_program nfsd_program = { | ||
407 | .pg_next = nfsd_acl_program_p, | ||
408 | .pg_prog = NFS_PROGRAM, /* program number */ | ||
409 | .pg_nvers = NFSD_NRVERS, /* nr of entries in nfsd_version */ | ||
410 | .pg_vers = nfsd_version, /* version table */ | ||
411 | .pg_name = "nfsd", /* program name */ | ||
412 | .pg_class = "nfsd", /* authentication class */ | ||
413 | .pg_stats = &nfsd_svcstats, /* version table */ | ||
414 | .pg_authenticate = &svc_set_client, /* export authentication */ | ||
415 | |||
416 | }; | ||
diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c index 4f2cd3d27566..af7c3c3074b0 100644 --- a/fs/nfsd/vfs.c +++ b/fs/nfsd/vfs.c | |||
@@ -254,12 +254,19 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp, struct iattr *iap, | |||
254 | 254 | ||
255 | /* Get inode */ | 255 | /* Get inode */ |
256 | err = fh_verify(rqstp, fhp, ftype, accmode); | 256 | err = fh_verify(rqstp, fhp, ftype, accmode); |
257 | if (err || !iap->ia_valid) | 257 | if (err) |
258 | goto out; | 258 | goto out; |
259 | 259 | ||
260 | dentry = fhp->fh_dentry; | 260 | dentry = fhp->fh_dentry; |
261 | inode = dentry->d_inode; | 261 | inode = dentry->d_inode; |
262 | 262 | ||
263 | /* Ignore any mode updates on symlinks */ | ||
264 | if (S_ISLNK(inode->i_mode)) | ||
265 | iap->ia_valid &= ~ATTR_MODE; | ||
266 | |||
267 | if (!iap->ia_valid) | ||
268 | goto out; | ||
269 | |||
263 | /* NFSv2 does not differentiate between "set-[ac]time-to-now" | 270 | /* NFSv2 does not differentiate between "set-[ac]time-to-now" |
264 | * which only requires access, and "set-[ac]time-to-X" which | 271 | * which only requires access, and "set-[ac]time-to-X" which |
265 | * requires ownership. | 272 | * requires ownership. |