diff options
author | Chuck Lever <chuck.lever@oracle.com> | 2010-12-14 09:57:32 -0500 |
---|---|---|
committer | Trond Myklebust <Trond.Myklebust@netapp.com> | 2010-12-16 12:37:24 -0500 |
commit | 85a56480191ca9f08fc775c129b9eb5c8c1f2c05 (patch) | |
tree | af91e03abcb7344662d25ad24e036f6de1af0c02 /fs/nfsd | |
parent | a033db487eec09afde00a3562842982a8053c887 (diff) |
NFSD: Update XDR decoders in NFSv4 callback client
Clean up.
Remove old-style NFSv4 XDR macros in favor of the style now used in
fs/nfs/nfs4xdr.c. These were forgotten during the recent nfs4xdr.c
rewrite.
Additional whitespace cleanup adds to the size of this patch.
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
Tested-by: J. Bruce Fields <bfields@redhat.com>
Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
Diffstat (limited to 'fs/nfsd')
-rw-r--r-- | fs/nfsd/nfs4callback.c | 415 |
1 files changed, 239 insertions, 176 deletions
diff --git a/fs/nfsd/nfs4callback.c b/fs/nfsd/nfs4callback.c index d8148cc461e7..c3c6a903144c 100644 --- a/fs/nfsd/nfs4callback.c +++ b/fs/nfsd/nfs4callback.c | |||
@@ -74,37 +74,6 @@ enum { | |||
74 | cb_sequence_dec_sz + \ | 74 | cb_sequence_dec_sz + \ |
75 | op_dec_sz) | 75 | op_dec_sz) |
76 | 76 | ||
77 | /* | ||
78 | * Generic decode routines from fs/nfs/nfs4xdr.c | ||
79 | */ | ||
80 | #define DECODE_TAIL \ | ||
81 | status = 0; \ | ||
82 | out: \ | ||
83 | return status; \ | ||
84 | xdr_error: \ | ||
85 | dprintk("NFSD: xdr error! (%s:%d)\n", __FILE__, __LINE__); \ | ||
86 | status = -EIO; \ | ||
87 | goto out | ||
88 | |||
89 | #define READ32(x) (x) = ntohl(*p++) | ||
90 | #define READ64(x) do { \ | ||
91 | (x) = (u64)ntohl(*p++) << 32; \ | ||
92 | (x) |= ntohl(*p++); \ | ||
93 | } while (0) | ||
94 | #define READTIME(x) do { \ | ||
95 | p++; \ | ||
96 | (x.tv_sec) = ntohl(*p++); \ | ||
97 | (x.tv_nsec) = ntohl(*p++); \ | ||
98 | } while (0) | ||
99 | #define READ_BUF(nbytes) do { \ | ||
100 | p = xdr_inline_decode(xdr, nbytes); \ | ||
101 | if (!p) { \ | ||
102 | dprintk("NFSD: %s: reply buffer overflowed in line %d.\n", \ | ||
103 | __func__, __LINE__); \ | ||
104 | return -EIO; \ | ||
105 | } \ | ||
106 | } while (0) | ||
107 | |||
108 | struct nfs4_cb_compound_hdr { | 77 | struct nfs4_cb_compound_hdr { |
109 | /* args */ | 78 | /* args */ |
110 | u32 ident; /* minorversion 0 only */ | 79 | u32 ident; /* minorversion 0 only */ |
@@ -115,57 +84,14 @@ struct nfs4_cb_compound_hdr { | |||
115 | int status; | 84 | int status; |
116 | }; | 85 | }; |
117 | 86 | ||
118 | static struct { | 87 | /* |
119 | int stat; | 88 | * Handle decode buffer overflows out-of-line. |
120 | int errno; | 89 | */ |
121 | } nfs_cb_errtbl[] = { | 90 | static void print_overflow_msg(const char *func, const struct xdr_stream *xdr) |
122 | { NFS4_OK, 0 }, | ||
123 | { NFS4ERR_PERM, EPERM }, | ||
124 | { NFS4ERR_NOENT, ENOENT }, | ||
125 | { NFS4ERR_IO, EIO }, | ||
126 | { NFS4ERR_NXIO, ENXIO }, | ||
127 | { NFS4ERR_ACCESS, EACCES }, | ||
128 | { NFS4ERR_EXIST, EEXIST }, | ||
129 | { NFS4ERR_XDEV, EXDEV }, | ||
130 | { NFS4ERR_NOTDIR, ENOTDIR }, | ||
131 | { NFS4ERR_ISDIR, EISDIR }, | ||
132 | { NFS4ERR_INVAL, EINVAL }, | ||
133 | { NFS4ERR_FBIG, EFBIG }, | ||
134 | { NFS4ERR_NOSPC, ENOSPC }, | ||
135 | { NFS4ERR_ROFS, EROFS }, | ||
136 | { NFS4ERR_MLINK, EMLINK }, | ||
137 | { NFS4ERR_NAMETOOLONG, ENAMETOOLONG }, | ||
138 | { NFS4ERR_NOTEMPTY, ENOTEMPTY }, | ||
139 | { NFS4ERR_DQUOT, EDQUOT }, | ||
140 | { NFS4ERR_STALE, ESTALE }, | ||
141 | { NFS4ERR_BADHANDLE, EBADHANDLE }, | ||
142 | { NFS4ERR_BAD_COOKIE, EBADCOOKIE }, | ||
143 | { NFS4ERR_NOTSUPP, ENOTSUPP }, | ||
144 | { NFS4ERR_TOOSMALL, ETOOSMALL }, | ||
145 | { NFS4ERR_SERVERFAULT, ESERVERFAULT }, | ||
146 | { NFS4ERR_BADTYPE, EBADTYPE }, | ||
147 | { NFS4ERR_LOCKED, EAGAIN }, | ||
148 | { NFS4ERR_RESOURCE, EREMOTEIO }, | ||
149 | { NFS4ERR_SYMLINK, ELOOP }, | ||
150 | { NFS4ERR_OP_ILLEGAL, EOPNOTSUPP }, | ||
151 | { NFS4ERR_DEADLOCK, EDEADLK }, | ||
152 | { -1, EIO } | ||
153 | }; | ||
154 | |||
155 | static int | ||
156 | nfs_cb_stat_to_errno(int stat) | ||
157 | { | 91 | { |
158 | int i; | 92 | dprintk("NFS: %s prematurely hit the end of our receive buffer. " |
159 | for (i = 0; nfs_cb_errtbl[i].stat != -1; i++) { | 93 | "Remaining buffer length is %tu words.\n", |
160 | if (nfs_cb_errtbl[i].stat == stat) | 94 | func, xdr->end - xdr->p); |
161 | return nfs_cb_errtbl[i].errno; | ||
162 | } | ||
163 | /* If we cannot translate the error, the recovery routines should | ||
164 | * handle it. | ||
165 | * Note: remaining NFSv4 error codes have values > 10000, so should | ||
166 | * not conflict with native Linux error codes. | ||
167 | */ | ||
168 | return stat; | ||
169 | } | 95 | } |
170 | 96 | ||
171 | static __be32 *xdr_encode_empty_array(__be32 *p) | 97 | static __be32 *xdr_encode_empty_array(__be32 *p) |
@@ -263,6 +189,89 @@ static void encode_sessionid4(struct xdr_stream *xdr, | |||
263 | } | 189 | } |
264 | 190 | ||
265 | /* | 191 | /* |
192 | * nfsstat4 | ||
193 | */ | ||
194 | static const struct { | ||
195 | int stat; | ||
196 | int errno; | ||
197 | } nfs_cb_errtbl[] = { | ||
198 | { NFS4_OK, 0 }, | ||
199 | { NFS4ERR_PERM, -EPERM }, | ||
200 | { NFS4ERR_NOENT, -ENOENT }, | ||
201 | { NFS4ERR_IO, -EIO }, | ||
202 | { NFS4ERR_NXIO, -ENXIO }, | ||
203 | { NFS4ERR_ACCESS, -EACCES }, | ||
204 | { NFS4ERR_EXIST, -EEXIST }, | ||
205 | { NFS4ERR_XDEV, -EXDEV }, | ||
206 | { NFS4ERR_NOTDIR, -ENOTDIR }, | ||
207 | { NFS4ERR_ISDIR, -EISDIR }, | ||
208 | { NFS4ERR_INVAL, -EINVAL }, | ||
209 | { NFS4ERR_FBIG, -EFBIG }, | ||
210 | { NFS4ERR_NOSPC, -ENOSPC }, | ||
211 | { NFS4ERR_ROFS, -EROFS }, | ||
212 | { NFS4ERR_MLINK, -EMLINK }, | ||
213 | { NFS4ERR_NAMETOOLONG, -ENAMETOOLONG }, | ||
214 | { NFS4ERR_NOTEMPTY, -ENOTEMPTY }, | ||
215 | { NFS4ERR_DQUOT, -EDQUOT }, | ||
216 | { NFS4ERR_STALE, -ESTALE }, | ||
217 | { NFS4ERR_BADHANDLE, -EBADHANDLE }, | ||
218 | { NFS4ERR_BAD_COOKIE, -EBADCOOKIE }, | ||
219 | { NFS4ERR_NOTSUPP, -ENOTSUPP }, | ||
220 | { NFS4ERR_TOOSMALL, -ETOOSMALL }, | ||
221 | { NFS4ERR_SERVERFAULT, -ESERVERFAULT }, | ||
222 | { NFS4ERR_BADTYPE, -EBADTYPE }, | ||
223 | { NFS4ERR_LOCKED, -EAGAIN }, | ||
224 | { NFS4ERR_RESOURCE, -EREMOTEIO }, | ||
225 | { NFS4ERR_SYMLINK, -ELOOP }, | ||
226 | { NFS4ERR_OP_ILLEGAL, -EOPNOTSUPP }, | ||
227 | { NFS4ERR_DEADLOCK, -EDEADLK }, | ||
228 | { -1, -EIO } | ||
229 | }; | ||
230 | |||
231 | /* | ||
232 | * If we cannot translate the error, the recovery routines should | ||
233 | * handle it. | ||
234 | * | ||
235 | * Note: remaining NFSv4 error codes have values > 10000, so should | ||
236 | * not conflict with native Linux error codes. | ||
237 | */ | ||
238 | static int nfs_cb_stat_to_errno(int status) | ||
239 | { | ||
240 | int i; | ||
241 | |||
242 | for (i = 0; nfs_cb_errtbl[i].stat != -1; i++) { | ||
243 | if (nfs_cb_errtbl[i].stat == status) | ||
244 | return nfs_cb_errtbl[i].errno; | ||
245 | } | ||
246 | |||
247 | dprintk("NFSD: Unrecognized NFS CB status value: %u\n", status); | ||
248 | return -status; | ||
249 | } | ||
250 | |||
251 | static int decode_cb_op_status(struct xdr_stream *xdr, enum nfs_opnum4 expected, | ||
252 | enum nfsstat4 *status) | ||
253 | { | ||
254 | __be32 *p; | ||
255 | u32 op; | ||
256 | |||
257 | p = xdr_inline_decode(xdr, 4 + 4); | ||
258 | if (unlikely(p == NULL)) | ||
259 | goto out_overflow; | ||
260 | op = be32_to_cpup(p++); | ||
261 | if (unlikely(op != expected)) | ||
262 | goto out_unexpected; | ||
263 | *status = be32_to_cpup(p); | ||
264 | return 0; | ||
265 | out_overflow: | ||
266 | print_overflow_msg(__func__, xdr); | ||
267 | return -EIO; | ||
268 | out_unexpected: | ||
269 | dprintk("NFSD: Callback server returned operation %d but " | ||
270 | "we issued a request for %d\n", op, expected); | ||
271 | return -EIO; | ||
272 | } | ||
273 | |||
274 | /* | ||
266 | * CB_COMPOUND4args | 275 | * CB_COMPOUND4args |
267 | * | 276 | * |
268 | * struct CB_COMPOUND4args { | 277 | * struct CB_COMPOUND4args { |
@@ -296,6 +305,37 @@ static void encode_cb_nops(struct nfs4_cb_compound_hdr *hdr) | |||
296 | } | 305 | } |
297 | 306 | ||
298 | /* | 307 | /* |
308 | * CB_COMPOUND4res | ||
309 | * | ||
310 | * struct CB_COMPOUND4res { | ||
311 | * nfsstat4 status; | ||
312 | * utf8str_cs tag; | ||
313 | * nfs_cb_resop4 resarray<>; | ||
314 | * }; | ||
315 | */ | ||
316 | static int decode_cb_compound4res(struct xdr_stream *xdr, | ||
317 | struct nfs4_cb_compound_hdr *hdr) | ||
318 | { | ||
319 | u32 length; | ||
320 | __be32 *p; | ||
321 | |||
322 | p = xdr_inline_decode(xdr, 4 + 4); | ||
323 | if (unlikely(p == NULL)) | ||
324 | goto out_overflow; | ||
325 | hdr->status = be32_to_cpup(p++); | ||
326 | /* Ignore the tag */ | ||
327 | length = be32_to_cpup(p++); | ||
328 | p = xdr_inline_decode(xdr, length + 4); | ||
329 | if (unlikely(p == NULL)) | ||
330 | goto out_overflow; | ||
331 | hdr->nops = be32_to_cpup(p); | ||
332 | return 0; | ||
333 | out_overflow: | ||
334 | print_overflow_msg(__func__, xdr); | ||
335 | return -EIO; | ||
336 | } | ||
337 | |||
338 | /* | ||
299 | * CB_RECALL4args | 339 | * CB_RECALL4args |
300 | * | 340 | * |
301 | * struct CB_RECALL4args { | 341 | * struct CB_RECALL4args { |
@@ -357,6 +397,97 @@ static void encode_cb_sequence4args(struct xdr_stream *xdr, | |||
357 | } | 397 | } |
358 | 398 | ||
359 | /* | 399 | /* |
400 | * CB_SEQUENCE4resok | ||
401 | * | ||
402 | * struct CB_SEQUENCE4resok { | ||
403 | * sessionid4 csr_sessionid; | ||
404 | * sequenceid4 csr_sequenceid; | ||
405 | * slotid4 csr_slotid; | ||
406 | * slotid4 csr_highest_slotid; | ||
407 | * slotid4 csr_target_highest_slotid; | ||
408 | * }; | ||
409 | * | ||
410 | * union CB_SEQUENCE4res switch (nfsstat4 csr_status) { | ||
411 | * case NFS4_OK: | ||
412 | * CB_SEQUENCE4resok csr_resok4; | ||
413 | * default: | ||
414 | * void; | ||
415 | * }; | ||
416 | * | ||
417 | * Our current back channel implmentation supports a single backchannel | ||
418 | * with a single slot. | ||
419 | */ | ||
420 | static int decode_cb_sequence4resok(struct xdr_stream *xdr, | ||
421 | struct nfsd4_callback *cb) | ||
422 | { | ||
423 | struct nfsd4_session *session = cb->cb_clp->cl_cb_session; | ||
424 | struct nfs4_sessionid id; | ||
425 | int status; | ||
426 | __be32 *p; | ||
427 | u32 dummy; | ||
428 | |||
429 | status = -ESERVERFAULT; | ||
430 | |||
431 | /* | ||
432 | * If the server returns different values for sessionID, slotID or | ||
433 | * sequence number, the server is looney tunes. | ||
434 | */ | ||
435 | p = xdr_inline_decode(xdr, NFS4_MAX_SESSIONID_LEN + 4 + 4); | ||
436 | if (unlikely(p == NULL)) | ||
437 | goto out_overflow; | ||
438 | memcpy(id.data, p, NFS4_MAX_SESSIONID_LEN); | ||
439 | if (memcmp(id.data, session->se_sessionid.data, | ||
440 | NFS4_MAX_SESSIONID_LEN) != 0) { | ||
441 | dprintk("NFS: %s Invalid session id\n", __func__); | ||
442 | goto out; | ||
443 | } | ||
444 | p += XDR_QUADLEN(NFS4_MAX_SESSIONID_LEN); | ||
445 | |||
446 | dummy = be32_to_cpup(p++); | ||
447 | if (dummy != session->se_cb_seq_nr) { | ||
448 | dprintk("NFS: %s Invalid sequence number\n", __func__); | ||
449 | goto out; | ||
450 | } | ||
451 | |||
452 | dummy = be32_to_cpup(p++); | ||
453 | if (dummy != 0) { | ||
454 | dprintk("NFS: %s Invalid slotid\n", __func__); | ||
455 | goto out; | ||
456 | } | ||
457 | |||
458 | /* | ||
459 | * FIXME: process highest slotid and target highest slotid | ||
460 | */ | ||
461 | status = 0; | ||
462 | out: | ||
463 | return status; | ||
464 | out_overflow: | ||
465 | print_overflow_msg(__func__, xdr); | ||
466 | return -EIO; | ||
467 | } | ||
468 | |||
469 | static int decode_cb_sequence4res(struct xdr_stream *xdr, | ||
470 | struct nfsd4_callback *cb) | ||
471 | { | ||
472 | enum nfsstat4 nfserr; | ||
473 | int status; | ||
474 | |||
475 | if (cb->cb_minorversion == 0) | ||
476 | return 0; | ||
477 | |||
478 | status = decode_cb_op_status(xdr, OP_CB_SEQUENCE, &nfserr); | ||
479 | if (unlikely(status)) | ||
480 | goto out; | ||
481 | if (unlikely(nfserr != NFS4_OK)) | ||
482 | goto out_default; | ||
483 | status = decode_cb_sequence4resok(xdr, cb); | ||
484 | out: | ||
485 | return status; | ||
486 | out_default: | ||
487 | return nfs_cb_stat_to_errno(status); | ||
488 | } | ||
489 | |||
490 | /* | ||
360 | * NFSv4.0 and NFSv4.1 XDR encode functions | 491 | * NFSv4.0 and NFSv4.1 XDR encode functions |
361 | * | 492 | * |
362 | * NFSv4.0 callback argument types are defined in section 15 of RFC | 493 | * NFSv4.0 callback argument types are defined in section 15 of RFC |
@@ -399,119 +530,51 @@ static int nfs4_xdr_enc_cb_recall(struct rpc_rqst *req, __be32 *p, | |||
399 | } | 530 | } |
400 | 531 | ||
401 | 532 | ||
402 | static int | ||
403 | decode_cb_compound_hdr(struct xdr_stream *xdr, struct nfs4_cb_compound_hdr *hdr){ | ||
404 | __be32 *p; | ||
405 | u32 taglen; | ||
406 | |||
407 | READ_BUF(8); | ||
408 | READ32(hdr->status); | ||
409 | /* We've got no use for the tag; ignore it: */ | ||
410 | READ32(taglen); | ||
411 | READ_BUF(taglen + 4); | ||
412 | p += XDR_QUADLEN(taglen); | ||
413 | READ32(hdr->nops); | ||
414 | return 0; | ||
415 | } | ||
416 | |||
417 | static int | ||
418 | decode_cb_op_hdr(struct xdr_stream *xdr, enum nfs_opnum4 expected) | ||
419 | { | ||
420 | __be32 *p; | ||
421 | u32 op; | ||
422 | int32_t nfserr; | ||
423 | |||
424 | READ_BUF(8); | ||
425 | READ32(op); | ||
426 | if (op != expected) { | ||
427 | dprintk("NFSD: decode_cb_op_hdr: Callback server returned " | ||
428 | " operation %d but we issued a request for %d\n", | ||
429 | op, expected); | ||
430 | return -EIO; | ||
431 | } | ||
432 | READ32(nfserr); | ||
433 | if (nfserr != NFS_OK) | ||
434 | return -nfs_cb_stat_to_errno(nfserr); | ||
435 | return 0; | ||
436 | } | ||
437 | |||
438 | /* | 533 | /* |
439 | * Our current back channel implmentation supports a single backchannel | 534 | * NFSv4.0 and NFSv4.1 XDR decode functions |
440 | * with a single slot. | 535 | * |
536 | * NFSv4.0 callback result types are defined in section 15 of RFC | ||
537 | * 3530: "Network File System (NFS) version 4 Protocol" and section 20 | ||
538 | * of RFC 5661: "Network File System (NFS) Version 4 Minor Version 1 | ||
539 | * Protocol". | ||
441 | */ | 540 | */ |
442 | static int | ||
443 | decode_cb_sequence(struct xdr_stream *xdr, struct nfsd4_callback *cb, | ||
444 | struct rpc_rqst *rqstp) | ||
445 | { | ||
446 | struct nfsd4_session *ses = cb->cb_clp->cl_cb_session; | ||
447 | struct nfs4_sessionid id; | ||
448 | int status; | ||
449 | u32 dummy; | ||
450 | __be32 *p; | ||
451 | |||
452 | if (cb->cb_minorversion == 0) | ||
453 | return 0; | ||
454 | |||
455 | status = decode_cb_op_hdr(xdr, OP_CB_SEQUENCE); | ||
456 | if (status) | ||
457 | return status; | ||
458 | |||
459 | /* | ||
460 | * If the server returns different values for sessionID, slotID or | ||
461 | * sequence number, the server is looney tunes. | ||
462 | */ | ||
463 | status = -ESERVERFAULT; | ||
464 | |||
465 | READ_BUF(NFS4_MAX_SESSIONID_LEN + 16); | ||
466 | memcpy(id.data, p, NFS4_MAX_SESSIONID_LEN); | ||
467 | p += XDR_QUADLEN(NFS4_MAX_SESSIONID_LEN); | ||
468 | if (memcmp(id.data, ses->se_sessionid.data, NFS4_MAX_SESSIONID_LEN)) { | ||
469 | dprintk("%s Invalid session id\n", __func__); | ||
470 | goto out; | ||
471 | } | ||
472 | READ32(dummy); | ||
473 | if (dummy != ses->se_cb_seq_nr) { | ||
474 | dprintk("%s Invalid sequence number\n", __func__); | ||
475 | goto out; | ||
476 | } | ||
477 | READ32(dummy); /* slotid must be 0 */ | ||
478 | if (dummy != 0) { | ||
479 | dprintk("%s Invalid slotid\n", __func__); | ||
480 | goto out; | ||
481 | } | ||
482 | /* FIXME: process highest slotid and target highest slotid */ | ||
483 | status = 0; | ||
484 | out: | ||
485 | return status; | ||
486 | } | ||
487 | |||
488 | 541 | ||
489 | static int | 542 | static int nfs4_xdr_dec_cb_null(struct rpc_rqst *req, __be32 *p, void *__unused) |
490 | nfs4_xdr_dec_cb_null(struct rpc_rqst *req, __be32 *p) | ||
491 | { | 543 | { |
492 | return 0; | 544 | return 0; |
493 | } | 545 | } |
494 | 546 | ||
495 | static int | 547 | /* |
496 | nfs4_xdr_dec_cb_recall(struct rpc_rqst *rqstp, __be32 *p, | 548 | * 20.2. Operation 4: CB_RECALL - Recall a Delegation |
497 | struct nfsd4_callback *cb) | 549 | */ |
550 | static int nfs4_xdr_dec_cb_recall(struct rpc_rqst *rqstp, __be32 *p, | ||
551 | struct nfsd4_callback *cb) | ||
498 | { | 552 | { |
499 | struct xdr_stream xdr; | 553 | struct xdr_stream xdr; |
500 | struct nfs4_cb_compound_hdr hdr; | 554 | struct nfs4_cb_compound_hdr hdr; |
555 | enum nfsstat4 nfserr; | ||
501 | int status; | 556 | int status; |
502 | 557 | ||
503 | xdr_init_decode(&xdr, &rqstp->rq_rcv_buf, p); | 558 | xdr_init_decode(&xdr, &rqstp->rq_rcv_buf, p); |
504 | status = decode_cb_compound_hdr(&xdr, &hdr); | 559 | status = decode_cb_compound4res(&xdr, &hdr); |
505 | if (status) | 560 | if (unlikely(status)) |
506 | goto out; | 561 | goto out; |
507 | if (cb) { | 562 | |
508 | status = decode_cb_sequence(&xdr, cb, rqstp); | 563 | if (cb != NULL) { |
509 | if (status) | 564 | status = decode_cb_sequence4res(&xdr, cb); |
565 | if (unlikely(status)) | ||
510 | goto out; | 566 | goto out; |
511 | } | 567 | } |
512 | status = decode_cb_op_hdr(&xdr, OP_CB_RECALL); | 568 | |
569 | status = decode_cb_op_status(&xdr, OP_CB_RECALL, &nfserr); | ||
570 | if (unlikely(status)) | ||
571 | goto out; | ||
572 | if (unlikely(nfserr != NFS4_OK)) | ||
573 | goto out_default; | ||
513 | out: | 574 | out: |
514 | return status; | 575 | return status; |
576 | out_default: | ||
577 | return nfs_cb_stat_to_errno(status); | ||
515 | } | 578 | } |
516 | 579 | ||
517 | /* | 580 | /* |