xref: /titanic_52/usr/src/uts/common/fs/smbsrv/smb_fsinfo.c (revision 8f798d3afbe38d59cc0a708261dbb729f1b6b209)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #include <smbsrv/smb_kproto.h>
27 #include <smbsrv/smb_fsops.h>
28 #include <smbsrv/smbinfo.h>
29 
30 /*
31  * smb_fssize_t
32  * volume_units and volume avail are the total allocated and
33  * available units on the volume.
34  * caller_units and caller_avail are the allocated and available
35  * units on the volume for the user associated with the calling
36  * thread.
37  */
38 typedef struct smb_fssize {
39 	uint64_t	fs_volume_units;
40 	uint64_t	fs_volume_avail;
41 	uint64_t	fs_caller_units;
42 	uint64_t	fs_caller_avail;
43 	uint32_t	fs_sectors_per_unit;
44 	uint32_t	fs_bytes_per_sector;
45 } smb_fssize_t;
46 
47 /*
48  * File System Control Flags for smb_com_trans2_query|set_fs_information
49  * level SMB_FILE_FS_CONTROL_INFORMATION
50  */
51 #define	FILE_VC_QUOTA_TRACK		0x00000001
52 #define	FILE_VC_QUOTA_ENFORCE		0x00000002
53 #define	FILE_VC_CONTENT_INDEX_DISABLED	0x00000008
54 #define	FILE_VC_LOG_QUOTA_THRESHOLD	0x00000010
55 #define	FILE_VC_LOG_QUOTA_LIMIT		0x00000020
56 #define	FILE_VC_LOG_VOLUME_THRESHOLD	0x00000040
57 #define	FILE_VC_LOG_VOLUME_LIMIT	0x00000080
58 #define	FILE_VC_QUOTAS_INCOMPLETE	0x00000100
59 #define	FILE_VC_QUOTAS_REBUILDING	0x00000200
60 
61 static int smb_fssize(smb_request_t *, smb_fssize_t *);
62 static int smb_trans2_set_fs_ctrl_info(smb_request_t *, smb_xa_t *);
63 
64 /*
65  * smb_com_query_information_disk
66  *
67  * The SMB_COM_QUERY_INFORMATION_DISK command is used to determine the
68  * capacity and remaining free space on the drive hosting the directory
69  * structure indicated by Tid in the SMB header.
70  *
71  * The blocking/allocation units used in this response may be independent
72  * of the actual physical or logical blocking/allocation algorithm(s) used
73  * internally by the server.  However, they must accurately reflect the
74  * amount of space on the server.
75  *
76  * This SMB only returns 16 bits of information for each field, which may
77  * not be large enough for some disk systems.  In particular TotalUnits is
78  * commonly > 64K.  Fortunately, it turns out the all the client cares
79  * about is the total disk size, in bytes, and the free space, in bytes.
80  * So,  it is reasonable for a server to adjust the relative values of
81  * BlocksPerUnit and BlockSize to accommodate.  If after all adjustment,
82  * the numbers are still too high, the largest possible values for
83  * TotalUnit or FreeUnits (i.e. 0xFFFF) should be returned.
84  */
85 
86 smb_sdrc_t
87 smb_pre_query_information_disk(smb_request_t *sr)
88 {
89 	DTRACE_SMB_1(op__QueryInformationDisk__start, smb_request_t *, sr);
90 	return (SDRC_SUCCESS);
91 }
92 
93 void
94 smb_post_query_information_disk(smb_request_t *sr)
95 {
96 	DTRACE_SMB_1(op__QueryInformationDisk__done, smb_request_t *, sr);
97 }
98 
99 smb_sdrc_t
100 smb_com_query_information_disk(smb_request_t *sr)
101 {
102 	int			rc;
103 	fsblkcnt64_t		total_blocks, free_blocks;
104 	unsigned long		block_size, unit_size;
105 	unsigned short		blocks_per_unit, bytes_per_block;
106 	unsigned short		total_units, free_units;
107 	smb_fssize_t		fssize;
108 
109 	if (STYPE_ISIPC(sr->tid_tree->t_res_type)) {
110 		smbsr_error(sr, NT_STATUS_ACCESS_DENIED, ERRDOS, ERRnoaccess);
111 		return (SDRC_ERROR);
112 	}
113 
114 	if (smb_fssize(sr, &fssize) != 0)
115 		return (SDRC_ERROR);
116 
117 	unit_size = fssize.fs_sectors_per_unit;
118 	block_size = fssize.fs_bytes_per_sector;
119 	total_blocks = fssize.fs_caller_units;
120 	free_blocks = fssize.fs_caller_avail;
121 
122 	/*
123 	 * It seems that DOS clients cannot handle block sizes
124 	 * bigger than 512 KB. So we have to set the block size at
125 	 * most to 512
126 	 */
127 	while (block_size > 512) {
128 		block_size >>= 1;
129 		unit_size <<= 1;
130 	}
131 
132 	/* adjust blocks and sizes until they fit into a word */
133 	while (total_blocks >= 0xFFFF) {
134 		total_blocks >>= 1;
135 		free_blocks >>= 1;
136 		if ((unit_size <<= 1) > 0xFFFF) {
137 			unit_size >>= 1;
138 			total_blocks = 0xFFFF;
139 			free_blocks <<= 1;
140 			break;
141 		}
142 	}
143 
144 	total_units = (total_blocks >= 0xFFFF) ?
145 	    0xFFFF : (unsigned short)total_blocks;
146 	free_units = (free_blocks >= 0xFFFF) ?
147 	    0xFFFF : (unsigned short)free_blocks;
148 	bytes_per_block = (unsigned short)block_size;
149 	blocks_per_unit = (unsigned short)unit_size;
150 
151 	rc = smbsr_encode_result(sr, 5, 0, "bwwww2.w",
152 	    5,
153 	    total_units,	/* total_units */
154 	    blocks_per_unit,	/* blocks_per_unit */
155 	    bytes_per_block,	/* blocksize */
156 	    free_units,		/* free_units */
157 	    0);			/* bcc */
158 
159 	return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
160 }
161 
162 /*
163  * smb_com_trans2_query_fs_information
164  *
165  * This transaction requests information about the filesystem.
166  * The following information levels are supported:
167  *
168  *  InformationLevel               	Value
169  *  ==================================  ======
170  *  SMB_INFO_ALLOCATION            	1
171  *  SMB_INFO_VOLUME                	2
172  *  SMB_QUERY_FS_VOLUME_INFO       	0x102
173  *  SMB_QUERY_FS_SIZE_INFO         	0x103
174  *  SMB_QUERY_FS_DEVICE_INFO       	0x104
175  *  SMB_QUERY_FS_ATTRIBUTE_INFO    	0x105
176  *  SMB_FILE_FS_VOLUME_INFORMATION	1001
177  *  SMB_FILE_FS_SIZE_INFORMATION	1003
178  *  SMB_FILE_FS_DEVICE_INFORMATION	1004
179  *  SMB_FILE_FS_ATTRIBUTE_INFORMATION	1005
180  *  SMB_FILE_FS_CONTROL_INFORMATION	1006
181  *  SMB_FILE_FS_FULLSIZE_INFORMATION	1007
182  *
183  * The fsid provides a system-wide unique file system ID.
184  * fsid.val[0] is the 32-bit dev for the file system of the share root
185  * smb_node.
186  * fsid.val[1] is the file system type.
187  */
188 smb_sdrc_t
189 smb_com_trans2_query_fs_information(smb_request_t *sr, smb_xa_t *xa)
190 {
191 	uint32_t		flags;
192 	char			*encode_str, *tmpbuf;
193 	uint64_t		max_int;
194 	uint16_t		infolev;
195 	int			rc, length, buflen;
196 	smb_tree_t		*tree;
197 	smb_node_t		*snode;
198 	char 			*fsname = "NTFS";
199 	fsid_t			fsid;
200 	smb_fssize_t		fssize;
201 	smb_msgbuf_t		mb;
202 
203 	tree = sr->tid_tree;
204 
205 	if (!STYPE_ISDSK(tree->t_res_type)) {
206 		smbsr_error(sr, NT_STATUS_ACCESS_DENIED,
207 		    ERRDOS, ERROR_ACCESS_DENIED);
208 		return (SDRC_ERROR);
209 	}
210 
211 	if (smb_mbc_decodef(&xa->req_param_mb, "w", &infolev) != 0)
212 		return (SDRC_ERROR);
213 
214 	snode = tree->t_snode;
215 	fsid = SMB_NODE_FSID(snode);
216 
217 	switch (infolev) {
218 	case SMB_INFO_ALLOCATION:
219 		if (smb_fssize(sr, &fssize) != 0)
220 			return (SDRC_ERROR);
221 
222 		max_int = (uint64_t)UINT_MAX;
223 		if (fssize.fs_caller_units > max_int)
224 			fssize.fs_caller_units = max_int;
225 		if (fssize.fs_caller_avail > max_int)
226 			fssize.fs_caller_avail = max_int;
227 
228 		(void) smb_mbc_encodef(&xa->rep_data_mb, "llllw",
229 		    0,
230 		    fssize.fs_sectors_per_unit,
231 		    fssize.fs_caller_units,
232 		    fssize.fs_caller_avail,
233 		    fssize.fs_bytes_per_sector);
234 		break;
235 
236 	case SMB_INFO_VOLUME:
237 		/*
238 		 * In this response, the unicode volume label is NOT
239 		 * expected to be aligned. Encode ('U') into a temporary
240 		 * buffer, then encode buffer as a byte stream ('#c').
241 		 */
242 		if ((sr->smb_flg2 & SMB_FLAGS2_UNICODE) ||
243 		    (sr->session->native_os == NATIVE_OS_WIN95)) {
244 			length = smb_wcequiv_strlen(tree->t_volume);
245 			buflen = length + sizeof (smb_wchar_t);
246 			tmpbuf = smb_srm_zalloc(sr, buflen);
247 			smb_msgbuf_init(&mb, (uint8_t *)tmpbuf, buflen,
248 			    SMB_MSGBUF_UNICODE);
249 			rc = smb_msgbuf_encode(&mb, "U", tree->t_volume);
250 			if (rc >= 0) {
251 				rc = smb_mbc_encodef(&xa->rep_data_mb,
252 				    "%lb#c", sr, fsid.val[0],
253 				    length, length, tmpbuf);
254 			}
255 			smb_msgbuf_term(&mb);
256 		} else {
257 			length = strlen(tree->t_volume);
258 			rc = smb_mbc_encodef(&xa->rep_data_mb, "%lbs", sr,
259 			    fsid.val[0], length, tree->t_volume);
260 		}
261 
262 		if (rc < 0)
263 			return (SDRC_ERROR);
264 		break;
265 
266 	case SMB_QUERY_FS_VOLUME_INFO:
267 	case SMB_FILE_FS_VOLUME_INFORMATION:
268 		if ((sr->smb_flg2 & SMB_FLAGS2_UNICODE) ||
269 		    (sr->session->native_os == NATIVE_OS_WIN95)) {
270 			length = smb_wcequiv_strlen(tree->t_volume);
271 			encode_str = "%qllb.U";
272 		} else {
273 			length = strlen(tree->t_volume);
274 			encode_str = "%qllb.s";
275 		}
276 
277 		/*
278 		 * NT has the "supports objects" flag set to 1.
279 		 */
280 		(void) smb_mbc_encodef(&xa->rep_data_mb, encode_str, sr,
281 		    0ll,			/* Volume creation time */
282 		    fsid.val[0],		/* Volume serial number */
283 		    length,			/* label length */
284 		    0,				/* Supports objects */
285 		    tree->t_volume);
286 		break;
287 
288 	case SMB_QUERY_FS_SIZE_INFO:
289 	case SMB_FILE_FS_SIZE_INFORMATION:
290 		if (smb_fssize(sr, &fssize) != 0)
291 			return (SDRC_ERROR);
292 
293 		(void) smb_mbc_encodef(&xa->rep_data_mb, "qqll",
294 		    fssize.fs_caller_units,
295 		    fssize.fs_caller_avail,
296 		    fssize.fs_sectors_per_unit,
297 		    fssize.fs_bytes_per_sector);
298 		break;
299 
300 	case SMB_QUERY_FS_DEVICE_INFO:
301 	case SMB_FILE_FS_DEVICE_INFORMATION:
302 		(void) smb_mbc_encodef(&xa->rep_data_mb, "ll",
303 		    FILE_DEVICE_FILE_SYSTEM,
304 		    FILE_DEVICE_IS_MOUNTED);
305 		break;
306 
307 	case SMB_QUERY_FS_ATTRIBUTE_INFO:
308 	case SMB_FILE_FS_ATTRIBUTE_INFORMATION:
309 		if ((sr->smb_flg2 & SMB_FLAGS2_UNICODE) ||
310 		    (sr->session->native_os == NATIVE_OS_WINNT) ||
311 		    (sr->session->native_os == NATIVE_OS_WIN2000) ||
312 		    (sr->session->native_os == NATIVE_OS_WIN95) ||
313 		    (sr->session->native_os == NATIVE_OS_MACOS)) {
314 			length = smb_wcequiv_strlen(fsname);
315 			encode_str = "%lllU";
316 			sr->smb_flg2 |= SMB_FLAGS2_UNICODE;
317 		} else {
318 			length = strlen(fsname);
319 			encode_str = "%llls";
320 		}
321 
322 		flags = FILE_CASE_PRESERVED_NAMES;
323 
324 		if (tree->t_flags & SMB_TREE_UNICODE_ON_DISK)
325 			flags |= FILE_UNICODE_ON_DISK;
326 
327 		if (tree->t_flags & SMB_TREE_SUPPORTS_ACLS)
328 			flags |= FILE_PERSISTENT_ACLS;
329 
330 		if ((tree->t_flags & SMB_TREE_CASEINSENSITIVE) == 0)
331 			flags |= FILE_CASE_SENSITIVE_SEARCH;
332 
333 		if (tree->t_flags & SMB_TREE_STREAMS)
334 			flags |= FILE_NAMED_STREAMS;
335 
336 		if (tree->t_flags & SMB_TREE_QUOTA)
337 			flags |= FILE_VOLUME_QUOTAS;
338 
339 		(void) smb_mbc_encodef(&xa->rep_data_mb, encode_str, sr,
340 		    flags,
341 		    MAXNAMELEN,	/* max name */
342 		    length,	/* label length */
343 		    fsname);
344 		break;
345 
346 	case SMB_FILE_FS_CONTROL_INFORMATION:
347 		if (!smb_tree_has_feature(sr->tid_tree, SMB_TREE_QUOTA)) {
348 			smbsr_error(sr, NT_STATUS_NOT_SUPPORTED,
349 			    ERRDOS, ERROR_NOT_SUPPORTED);
350 			return (SDRC_ERROR);
351 		}
352 
353 		(void) smb_mbc_encodef(&xa->rep_data_mb, "qqqqqll",
354 		    0,		/* free space start filtering - MUST be 0 */
355 		    0,		/* free space threshold - MUST be 0 */
356 		    0,		/* free space stop filtering - MUST be 0 */
357 		    SMB_QUOTA_UNLIMITED,	/* default quota threshold */
358 		    SMB_QUOTA_UNLIMITED,	/* default quota limit */
359 		    FILE_VC_QUOTA_ENFORCE,	/* fs control flag */
360 		    0);				/* pad bytes */
361 		break;
362 
363 	case SMB_FILE_FS_FULLSIZE_INFORMATION:
364 		if (smb_fssize(sr, &fssize) != 0)
365 			return (SDRC_ERROR);
366 
367 		(void) smb_mbc_encodef(&xa->rep_data_mb, "qqqll",
368 		    fssize.fs_caller_units,
369 		    fssize.fs_caller_avail,
370 		    fssize.fs_volume_avail,
371 		    fssize.fs_sectors_per_unit,
372 		    fssize.fs_bytes_per_sector);
373 		break;
374 
375 	case SMB_FILE_FS_LABEL_INFORMATION:
376 	case SMB_FILE_FS_OBJECTID_INFORMATION:
377 	case SMB_FILE_FS_DRIVERPATH_INFORMATION:
378 		smbsr_error(sr, NT_STATUS_NOT_SUPPORTED,
379 		    ERRDOS, ERROR_NOT_SUPPORTED);
380 		return (SDRC_ERROR);
381 
382 	default:
383 		smbsr_error(sr, NT_STATUS_INVALID_LEVEL,
384 		    ERRDOS, ERROR_INVALID_LEVEL);
385 		return (SDRC_ERROR);
386 	}
387 
388 	return (SDRC_SUCCESS);
389 }
390 
391 /*
392  * smb_fssize
393  *
394  * File system size information, for the volume and for the user
395  * initiating the request.
396  *
397  * If there's no quota entry for the user initiating the request,
398  * caller_units and caller_avail are the total and available units
399  * for the volume (volume_units, volume_avail).
400  * If there is a quota entry for the user initiating the request,
401  * and it is not SMB_QUOTA_UNLIMITED, calculate caller_units and
402  * caller_avail as follows:
403  *   caller_units = quota limit / bytes_per_unit
404  *   caller_avail = remaining quota / bytes_per_unit
405  *
406  * A quota limit of SMB_QUOTA_UNLIMITED means that the user's quota
407  * is specfied as unlimited. A quota limit of 0 means there is no
408  * quota specified for the user.
409  *
410  * Returns: 0 - success
411  *         -1 - error. Error status set in sr.
412  */
413 static int
414 smb_fssize(smb_request_t *sr, smb_fssize_t *fssize)
415 {
416 	smb_node_t *node;
417 	struct statvfs64 df;
418 	uid_t uid;
419 	smb_quota_t quota;
420 	int rc, bytes_per_unit;
421 
422 	bzero(fssize, sizeof (smb_fssize_t));
423 	node = sr->tid_tree->t_snode;
424 	if ((rc = smb_fsop_statfs(sr->user_cr, node, &df)) != 0) {
425 		smbsr_errno(sr, rc);
426 		return (-1);
427 	}
428 
429 	fssize->fs_bytes_per_sector = 512;
430 	fssize->fs_sectors_per_unit = df.f_frsize >> 9;
431 	if (df.f_bavail > df.f_blocks)
432 		df.f_bavail = 0;
433 
434 	fssize->fs_volume_units = df.f_blocks;
435 	fssize->fs_volume_avail = df.f_bavail;
436 	fssize->fs_caller_units = df.f_blocks;
437 	fssize->fs_caller_avail = df.f_bavail;
438 	bytes_per_unit =
439 	    fssize->fs_bytes_per_sector * fssize->fs_sectors_per_unit;
440 
441 	if (!smb_tree_has_feature(sr->tid_tree, SMB_TREE_QUOTA))
442 		return (0);
443 
444 	uid = crgetuid(sr->uid_user->u_cred);
445 	if (smb_quota_query_user_quota(sr, uid, &quota) != NT_STATUS_SUCCESS)
446 		return (0);
447 
448 	if ((quota.q_limit != SMB_QUOTA_UNLIMITED) && (quota.q_limit != 0)) {
449 		fssize->fs_caller_units = quota.q_limit / bytes_per_unit;
450 		if (quota.q_limit <= quota.q_used)
451 			fssize->fs_caller_avail = 0;
452 		else
453 			fssize->fs_caller_avail =
454 			    (quota.q_limit - quota.q_used) / bytes_per_unit;
455 	}
456 
457 	return (0);
458 }
459 
460 /*
461  * smb_com_trans2_set_fs_information
462  *
463  * This transaction sets filesystem information.
464  * The following information levels are supported:
465  *
466  *  InformationLevel               	Value
467  *  ==================================  ======
468  *  SMB_FILE_FS_CONTROL_INFORMATION	1006
469  */
470 smb_sdrc_t
471 smb_com_trans2_set_fs_information(smb_request_t *sr, smb_xa_t *xa)
472 {
473 	smb_tree_t		*tree;
474 	uint16_t		infolev;
475 
476 	tree = sr->tid_tree;
477 	if (!STYPE_ISDSK(tree->t_res_type)) {
478 		smbsr_error(sr, NT_STATUS_ACCESS_DENIED,
479 		    ERRDOS, ERROR_ACCESS_DENIED);
480 		return (SDRC_ERROR);
481 	}
482 
483 	if (smb_mbc_decodef(&xa->req_param_mb, "ww",
484 	    &sr->smb_fid, &infolev) != 0)
485 		return (SDRC_ERROR);
486 
487 	switch (infolev) {
488 	case SMB_FILE_FS_CONTROL_INFORMATION:
489 		if (smb_trans2_set_fs_ctrl_info(sr, xa) != 0)
490 			return (SDRC_ERROR);
491 		break;
492 
493 	case SMB_FILE_FS_VOLUME_INFORMATION:
494 	case SMB_FILE_FS_LABEL_INFORMATION:
495 	case SMB_FILE_FS_SIZE_INFORMATION:
496 	case SMB_FILE_FS_DEVICE_INFORMATION:
497 	case SMB_FILE_FS_ATTRIBUTE_INFORMATION:
498 	case SMB_FILE_FS_FULLSIZE_INFORMATION:
499 	case SMB_FILE_FS_OBJECTID_INFORMATION:
500 	case SMB_FILE_FS_DRIVERPATH_INFORMATION:
501 		smbsr_error(sr, NT_STATUS_NOT_SUPPORTED,
502 		    ERRDOS, ERROR_NOT_SUPPORTED);
503 		return (SDRC_ERROR);
504 
505 	default:
506 		smbsr_error(sr, NT_STATUS_INVALID_LEVEL,
507 		    ERRDOS, ERROR_INVALID_LEVEL);
508 		return (SDRC_ERROR);
509 	}
510 
511 	return (SDRC_SUCCESS);
512 }
513 
514 /*
515  * smb_trans2_set_fs_ctrl_info
516  *
517  * Only users with Admin privileges (i.e. of the BUILTIN/Administrators
518  * group) will be allowed to set quotas.
519  *
520  * Currently QUOTAS are always ENFORCED and the default values
521  * are always SMB_QUOTA_UNLIMITED (none). Any attempt to set
522  * values other than these will result in NT_STATUS_NOT_SUPPORTED.
523  */
524 static int
525 smb_trans2_set_fs_ctrl_info(smb_request_t *sr, smb_xa_t *xa)
526 {
527 	int rc;
528 	uint64_t fstart, fthresh, fstop, qthresh, qlimit;
529 	uint32_t qctrl, qpad;
530 
531 	if (!smb_tree_has_feature(sr->tid_tree, SMB_TREE_QUOTA)) {
532 		smbsr_error(sr, NT_STATUS_NOT_SUPPORTED,
533 		    ERRDOS, ERROR_NOT_SUPPORTED);
534 		return (-1);
535 	}
536 
537 	if (!smb_user_is_admin(sr->uid_user)) {
538 		smbsr_error(sr, NT_STATUS_ACCESS_DENIED,
539 		    ERRDOS, ERROR_ACCESS_DENIED);
540 		return (-1);
541 	}
542 
543 	rc = smb_mbc_decodef(&xa->req_data_mb, "qqqqqll", &fstart,
544 	    &fthresh, &fstop, &qthresh, &qlimit, &qctrl, &qpad);
545 
546 	if ((rc != 0) || (fstart != 0) || (fthresh != 0) || (fstop != 0)) {
547 		smbsr_error(sr, NT_STATUS_INVALID_PARAMETER,
548 		    ERRDOS, ERROR_INVALID_PARAMETER);
549 		return (-1);
550 	}
551 
552 	/* Only support ENFORCED quotas with UNLIMITED default */
553 	if ((qctrl != FILE_VC_QUOTA_ENFORCE) ||
554 	    (qlimit != SMB_QUOTA_UNLIMITED) ||
555 	    (qthresh != SMB_QUOTA_UNLIMITED)) {
556 		smbsr_error(sr, NT_STATUS_NOT_SUPPORTED,
557 		    ERRDOS, ERROR_NOT_SUPPORTED);
558 		return (-1);
559 	}
560 
561 	return (0);
562 }
563