ntdll/tests: Remove a redundant 'if'.
[wine] / dlls / ntdll / om.c
1 /*
2  *      Object management functions
3  *
4  * Copyright 1999, 2000 Juergen Schmied
5  * Copyright 2005 Vitaliy Margolen
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  */
21
22 #include "config.h"
23
24 #include <stdarg.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #ifdef HAVE_IO_H
28 # include <io.h>
29 #endif
30 #ifdef HAVE_UNISTD_H
31 # include <unistd.h>
32 #endif
33
34 #include "ntstatus.h"
35 #define WIN32_NO_STATUS
36 #include "wine/debug.h"
37 #include "windef.h"
38 #include "winternl.h"
39 #include "ntdll_misc.h"
40 #include "wine/server.h"
41
42 WINE_DEFAULT_DEBUG_CHANNEL(ntdll);
43
44
45 /*
46  *      Generic object functions
47  */
48
49 /******************************************************************************
50  * NtQueryObject [NTDLL.@]
51  * ZwQueryObject [NTDLL.@]
52  */
53 NTSTATUS WINAPI NtQueryObject(IN HANDLE handle,
54                               IN OBJECT_INFORMATION_CLASS info_class,
55                               OUT PVOID ptr, IN ULONG len, OUT PULONG used_len)
56 {
57     NTSTATUS status;
58
59     TRACE("(%p,0x%08x,%p,0x%08x,%p): stub\n",
60           handle, info_class, ptr, len, used_len);
61
62     if (used_len) *used_len = 0;
63
64     switch (info_class)
65     {
66     case ObjectBasicInformation:
67         {
68             POBJECT_BASIC_INFORMATION p = (POBJECT_BASIC_INFORMATION)ptr;
69
70             if (len < sizeof(*p)) return STATUS_INVALID_BUFFER_SIZE;
71
72             SERVER_START_REQ( get_object_info )
73             {
74                 req->handle = handle;
75                 status = wine_server_call( req );
76                 if (status == STATUS_SUCCESS)
77                 {
78                     memset( p, 0, sizeof(*p) );
79                     p->GrantedAccess = reply->access;
80                     p->PointerCount = reply->ref_count;
81                     p->HandleCount = 1; /* at least one */
82                     if (used_len) *used_len = sizeof(*p);
83                 }
84             }
85             SERVER_END_REQ;
86         }
87         break;
88     case ObjectDataInformation:
89         {
90             OBJECT_DATA_INFORMATION* p = (OBJECT_DATA_INFORMATION*)ptr;
91
92             if (len < sizeof(*p)) return STATUS_INVALID_BUFFER_SIZE;
93
94             SERVER_START_REQ( set_handle_info )
95             {
96                 req->handle = handle;
97                 req->flags  = 0;
98                 req->mask   = 0;
99                 status = wine_server_call( req );
100                 if (status == STATUS_SUCCESS)
101                 {
102                     p->InheritHandle = (reply->old_flags & HANDLE_FLAG_INHERIT) ? TRUE : FALSE;
103                     p->ProtectFromClose = (reply->old_flags & HANDLE_FLAG_PROTECT_FROM_CLOSE) ? TRUE : FALSE;
104                     if (used_len) *used_len = sizeof(*p);
105                 }
106             }
107             SERVER_END_REQ;
108         }
109         break;
110     default:
111         FIXME("Unsupported information class %u\n", info_class);
112         status = STATUS_NOT_IMPLEMENTED;
113         break;
114     }
115     return status;
116 }
117
118 /******************************************************************
119  *              NtSetInformationObject [NTDLL.@]
120  *              ZwSetInformationObject [NTDLL.@]
121  *
122  */
123 NTSTATUS WINAPI NtSetInformationObject(IN HANDLE handle,
124                                        IN OBJECT_INFORMATION_CLASS info_class,
125                                        IN PVOID ptr, IN ULONG len)
126 {
127     NTSTATUS status;
128
129     TRACE("(%p,0x%08x,%p,0x%08x): stub\n",
130           handle, info_class, ptr, len);
131
132     switch (info_class)
133     {
134     case ObjectDataInformation:
135         {
136             OBJECT_DATA_INFORMATION* p = (OBJECT_DATA_INFORMATION*)ptr;
137
138             if (len < sizeof(*p)) return STATUS_INVALID_BUFFER_SIZE;
139
140             SERVER_START_REQ( set_handle_info )
141             {
142                 req->handle = handle;
143                 req->flags  = 0;
144                 req->mask   = HANDLE_FLAG_INHERIT | HANDLE_FLAG_PROTECT_FROM_CLOSE;
145                 if (p->InheritHandle)    req->flags |= HANDLE_FLAG_INHERIT;
146                 if (p->ProtectFromClose) req->flags |= HANDLE_FLAG_PROTECT_FROM_CLOSE;
147                 status = wine_server_call( req );
148             }
149             SERVER_END_REQ;
150         }
151         break;
152     default:
153         FIXME("Unsupported information class %u\n", info_class);
154         status = STATUS_NOT_IMPLEMENTED;
155         break;
156     }
157     return status;
158 }
159
160 /******************************************************************************
161  *  NtQuerySecurityObject       [NTDLL.@]
162  *
163  * An ntdll analogue to GetKernelObjectSecurity().
164  *
165  */
166 NTSTATUS WINAPI
167 NtQuerySecurityObject(
168         IN HANDLE Object,
169         IN SECURITY_INFORMATION RequestedInformation,
170         OUT PSECURITY_DESCRIPTOR pSecurityDescriptor,
171         IN ULONG Length,
172         OUT PULONG ResultLength)
173 {
174     PISECURITY_DESCRIPTOR_RELATIVE psd = pSecurityDescriptor;
175     NTSTATUS status;
176     unsigned int buffer_size = 512;
177     BOOLEAN need_more_memory;
178
179     TRACE("(%p,0x%08x,%p,0x%08x,%p)\n",
180         Object, RequestedInformation, pSecurityDescriptor, Length, ResultLength);
181
182     do
183     {
184         char *buffer = RtlAllocateHeap(GetProcessHeap(), 0, buffer_size);
185         if (!buffer)
186             return STATUS_NO_MEMORY;
187
188         need_more_memory = FALSE;
189
190         SERVER_START_REQ( get_security_object )
191         {
192             req->handle = Object;
193             req->security_info = RequestedInformation;
194             wine_server_set_reply( req, buffer, buffer_size );
195             status = wine_server_call( req );
196             if (status == STATUS_SUCCESS)
197             {
198                 struct security_descriptor *sd = (struct security_descriptor *)buffer;
199                 if (reply->sd_len)
200                 {
201                     *ResultLength = sizeof(SECURITY_DESCRIPTOR_RELATIVE) +
202                         sd->owner_len + sd->group_len + sd->sacl_len + sd->dacl_len;
203                     if (Length >= *ResultLength)
204                     {
205                         psd->Revision = SECURITY_DESCRIPTOR_REVISION;
206                         psd->Sbz1 = 0;
207                         psd->Control = sd->control | SE_SELF_RELATIVE;
208                         psd->Owner = sd->owner_len ? sizeof(SECURITY_DESCRIPTOR_RELATIVE) : 0;
209                         psd->Group = sd->group_len ? sizeof(SECURITY_DESCRIPTOR_RELATIVE) + sd->owner_len : 0;
210                         psd->Sacl = sd->sacl_len ? sizeof(SECURITY_DESCRIPTOR_RELATIVE) + sd->owner_len + sd->group_len : 0;
211                         psd->Dacl = sd->dacl_len ? sizeof(SECURITY_DESCRIPTOR_RELATIVE) + sd->owner_len + sd->group_len + sd->sacl_len : 0;
212                         /* owner, group, sacl and dacl are the same type as in the server
213                          * and in the same order so we copy the memory in one block */
214                         memcpy((char *)pSecurityDescriptor + sizeof(SECURITY_DESCRIPTOR_RELATIVE),
215                                buffer + sizeof(struct security_descriptor),
216                                sd->owner_len + sd->group_len + sd->sacl_len + sd->dacl_len);
217                     }
218                     else
219                         status = STATUS_BUFFER_TOO_SMALL;
220                 }
221                 else
222                 {
223                     *ResultLength = sizeof(SECURITY_DESCRIPTOR_RELATIVE);
224                     if (Length >= *ResultLength)
225                     {
226                         memset(psd, 0, sizeof(*psd));
227                         psd->Revision = SECURITY_DESCRIPTOR_REVISION;
228                         psd->Control = SE_SELF_RELATIVE;
229                     }
230                     else
231                         status = STATUS_BUFFER_TOO_SMALL;
232                 }
233             }
234             else if (status == STATUS_BUFFER_TOO_SMALL)
235             {
236                 buffer_size = reply->sd_len;
237                 need_more_memory = TRUE;
238             }
239         }
240         SERVER_END_REQ;
241         RtlFreeHeap(GetProcessHeap(), 0, buffer);
242     } while (need_more_memory);
243
244     return status;
245 }
246
247
248 /******************************************************************************
249  *  NtDuplicateObject           [NTDLL.@]
250  *  ZwDuplicateObject           [NTDLL.@]
251  */
252 NTSTATUS WINAPI NtDuplicateObject( HANDLE source_process, HANDLE source,
253                                    HANDLE dest_process, PHANDLE dest,
254                                    ACCESS_MASK access, ULONG attributes, ULONG options )
255 {
256     NTSTATUS ret;
257     SERVER_START_REQ( dup_handle )
258     {
259         req->src_process = source_process;
260         req->src_handle  = source;
261         req->dst_process = dest_process;
262         req->access      = access;
263         req->attributes  = attributes;
264         req->options     = options;
265
266         if (!(ret = wine_server_call( req )))
267         {
268             if (dest) *dest = reply->handle;
269             if (reply->closed)
270             {
271                 if (reply->self)
272                 {
273                     int fd = server_remove_fd_from_cache( source );
274                     if (fd != -1) close( fd );
275                 }
276             }
277             else if (options & DUPLICATE_CLOSE_SOURCE)
278                 WARN( "failed to close handle %p in process %p\n", source, source_process );
279         }
280     }
281     SERVER_END_REQ;
282     return ret;
283 }
284
285 /**************************************************************************
286  *                 NtClose                              [NTDLL.@]
287  *
288  * Close a handle reference to an object.
289  * 
290  * PARAMS
291  *  Handle [I] handle to close
292  *
293  * RETURNS
294  *  Success: ERROR_SUCCESS.
295  *  Failure: An NTSTATUS error code.
296  */
297 NTSTATUS WINAPI NtClose( HANDLE Handle )
298 {
299     NTSTATUS ret;
300     int fd = server_remove_fd_from_cache( Handle );
301
302     SERVER_START_REQ( close_handle )
303     {
304         req->handle = Handle;
305         ret = wine_server_call( req );
306     }
307     SERVER_END_REQ;
308     if (fd != -1) close( fd );
309     return ret;
310 }
311
312 /*
313  *      Directory functions
314  */
315
316 /**************************************************************************
317  * NtOpenDirectoryObject [NTDLL.@]
318  * ZwOpenDirectoryObject [NTDLL.@]
319  *
320  * Open a namespace directory object.
321  * 
322  * PARAMS
323  *  DirectoryHandle  [O] Destination for the new directory handle
324  *  DesiredAccess    [I] Desired access to the directory
325  *  ObjectAttributes [I] Structure describing the directory
326  *
327  * RETURNS
328  *  Success: ERROR_SUCCESS.
329  *  Failure: An NTSTATUS error code.
330  */
331 NTSTATUS WINAPI NtOpenDirectoryObject(PHANDLE DirectoryHandle, ACCESS_MASK DesiredAccess,
332                                       POBJECT_ATTRIBUTES ObjectAttributes)
333 {
334     NTSTATUS ret;
335     TRACE("(%p,0x%08x)\n", DirectoryHandle, DesiredAccess);
336     dump_ObjectAttributes(ObjectAttributes);
337
338     if (!DirectoryHandle) return STATUS_ACCESS_VIOLATION;
339     if (!ObjectAttributes) return STATUS_INVALID_PARAMETER;
340     /* Have to test it here because server won't know difference between
341      * ObjectName == NULL and ObjectName == "" */
342     if (!ObjectAttributes->ObjectName)
343     {
344         if (ObjectAttributes->RootDirectory)
345             return STATUS_OBJECT_NAME_INVALID;
346         else
347             return STATUS_OBJECT_PATH_SYNTAX_BAD;
348     }
349
350     SERVER_START_REQ(open_directory)
351     {
352         req->access = DesiredAccess;
353         req->attributes = ObjectAttributes->Attributes;
354         req->rootdir = ObjectAttributes->RootDirectory;
355         if (ObjectAttributes->ObjectName)
356             wine_server_add_data(req, ObjectAttributes->ObjectName->Buffer,
357                                  ObjectAttributes->ObjectName->Length);
358         ret = wine_server_call( req );
359         *DirectoryHandle = reply->handle;
360     }
361     SERVER_END_REQ;
362     return ret;
363 }
364
365 /******************************************************************************
366  *  NtCreateDirectoryObject     [NTDLL.@]
367  *  ZwCreateDirectoryObject     [NTDLL.@]
368  *
369  * Create a namespace directory object.
370  * 
371  * PARAMS
372  *  DirectoryHandle  [O] Destination for the new directory handle
373  *  DesiredAccess    [I] Desired access to the directory
374  *  ObjectAttributes [I] Structure describing the directory
375  *
376  * RETURNS
377  *  Success: ERROR_SUCCESS.
378  *  Failure: An NTSTATUS error code.
379  */
380 NTSTATUS WINAPI NtCreateDirectoryObject(PHANDLE DirectoryHandle, ACCESS_MASK DesiredAccess,
381                                         POBJECT_ATTRIBUTES ObjectAttributes)
382 {
383     NTSTATUS ret;
384     TRACE("(%p,0x%08x)\n", DirectoryHandle, DesiredAccess);
385     dump_ObjectAttributes(ObjectAttributes);
386
387     if (!DirectoryHandle) return STATUS_ACCESS_VIOLATION;
388
389     SERVER_START_REQ(create_directory)
390     {
391         req->access = DesiredAccess;
392         req->attributes = ObjectAttributes ? ObjectAttributes->Attributes : 0;
393         req->rootdir = ObjectAttributes ? ObjectAttributes->RootDirectory : 0;
394         if (ObjectAttributes && ObjectAttributes->ObjectName)
395             wine_server_add_data(req, ObjectAttributes->ObjectName->Buffer,
396                                  ObjectAttributes->ObjectName->Length);
397         ret = wine_server_call( req );
398         *DirectoryHandle = reply->handle;
399     }
400     SERVER_END_REQ;
401     return ret;
402 }
403
404 /******************************************************************************
405  * NtQueryDirectoryObject [NTDLL.@]
406  * ZwQueryDirectoryObject [NTDLL.@]
407  *
408  * Read information from a namespace directory.
409  * 
410  * PARAMS
411  *  handle        [I]   Handle to a directory object
412  *  buffer        [O]   Buffer to hold the read data
413  *  size          [I]   Size of the buffer in bytes
414  *  single_entry  [I]   If TRUE, return a single entry, if FALSE, return as many as fit in the buffer
415  *  restart       [I]   If TRUE, start scanning from the start, if FALSE, scan from Context
416  *  context       [I/O] Indicates what point of the directory the scan is at
417  *  ret_size      [O]   Caller supplied storage for the number of bytes written (or NULL)
418  *
419  * RETURNS
420  *  Success: ERROR_SUCCESS.
421  *  Failure: An NTSTATUS error code.
422  */
423 NTSTATUS WINAPI NtQueryDirectoryObject(HANDLE handle, PDIRECTORY_BASIC_INFORMATION buffer,
424                                        ULONG size, BOOLEAN single_entry, BOOLEAN restart,
425                                        PULONG context, PULONG ret_size)
426 {
427     NTSTATUS ret;
428
429     if (restart) *context = 0;
430
431     if (single_entry)
432     {
433         if (size <= sizeof(*buffer) + 2*sizeof(WCHAR)) return STATUS_BUFFER_OVERFLOW;
434
435         SERVER_START_REQ( get_directory_entry )
436         {
437             req->handle = handle;
438             req->index = *context;
439             wine_server_set_reply( req, buffer + 1, size - sizeof(*buffer) - 2*sizeof(WCHAR) );
440             if (!(ret = wine_server_call( req )))
441             {
442                 buffer->ObjectName.Buffer = (WCHAR *)(buffer + 1);
443                 buffer->ObjectName.Length = reply->name_len;
444                 buffer->ObjectName.MaximumLength = reply->name_len + sizeof(WCHAR);
445                 buffer->ObjectTypeName.Buffer = (WCHAR *)(buffer + 1) + reply->name_len/sizeof(WCHAR) + 1;
446                 buffer->ObjectTypeName.Length = wine_server_reply_size( reply ) - reply->name_len;
447                 buffer->ObjectTypeName.MaximumLength = buffer->ObjectTypeName.Length + sizeof(WCHAR);
448                 /* make room for the terminating null */
449                 memmove( buffer->ObjectTypeName.Buffer, buffer->ObjectTypeName.Buffer - 1,
450                          buffer->ObjectTypeName.Length );
451                 buffer->ObjectName.Buffer[buffer->ObjectName.Length/sizeof(WCHAR)] = 0;
452                 buffer->ObjectTypeName.Buffer[buffer->ObjectTypeName.Length/sizeof(WCHAR)] = 0;
453                 (*context)++;
454             }
455         }
456         SERVER_END_REQ;
457         if (ret_size)
458             *ret_size = buffer->ObjectName.MaximumLength + buffer->ObjectTypeName.MaximumLength + sizeof(*buffer);
459     }
460     else
461     {
462         FIXME("multiple entries not implemented\n");
463         ret = STATUS_NOT_IMPLEMENTED;
464     }
465
466     return ret;
467 }
468
469 /*
470  *      Link objects
471  */
472
473 /******************************************************************************
474  *  NtOpenSymbolicLinkObject    [NTDLL.@]
475  *  ZwOpenSymbolicLinkObject    [NTDLL.@]
476  *
477  * Open a namespace symbolic link object.
478  * 
479  * PARAMS
480  *  LinkHandle       [O] Destination for the new symbolic link handle
481  *  DesiredAccess    [I] Desired access to the symbolic link
482  *  ObjectAttributes [I] Structure describing the symbolic link
483  *
484  * RETURNS
485  *  Success: ERROR_SUCCESS.
486  *  Failure: An NTSTATUS error code.
487  */
488 NTSTATUS WINAPI NtOpenSymbolicLinkObject(OUT PHANDLE LinkHandle, IN ACCESS_MASK DesiredAccess,
489                                          IN POBJECT_ATTRIBUTES ObjectAttributes)
490 {
491     NTSTATUS ret;
492     TRACE("(%p,0x%08x,%p)\n",LinkHandle, DesiredAccess, ObjectAttributes);
493     dump_ObjectAttributes(ObjectAttributes);
494
495     if (!LinkHandle) return STATUS_ACCESS_VIOLATION;
496     if (!ObjectAttributes) return STATUS_INVALID_PARAMETER;
497     /* Have to test it here because server won't know difference between
498      * ObjectName == NULL and ObjectName == "" */
499     if (!ObjectAttributes->ObjectName)
500     {
501         if (ObjectAttributes->RootDirectory)
502             return STATUS_OBJECT_NAME_INVALID;
503         else
504             return STATUS_OBJECT_PATH_SYNTAX_BAD;
505     }
506
507     SERVER_START_REQ(open_symlink)
508     {
509         req->access = DesiredAccess;
510         req->attributes = ObjectAttributes->Attributes;
511         req->rootdir = ObjectAttributes->RootDirectory;
512         if (ObjectAttributes->ObjectName)
513             wine_server_add_data(req, ObjectAttributes->ObjectName->Buffer,
514                                  ObjectAttributes->ObjectName->Length);
515         ret = wine_server_call( req );
516         *LinkHandle = reply->handle;
517     }
518     SERVER_END_REQ;
519     return ret;
520 }
521
522 /******************************************************************************
523  *  NtCreateSymbolicLinkObject  [NTDLL.@]
524  *  ZwCreateSymbolicLinkObject  [NTDLL.@]
525  *
526  * Open a namespace symbolic link object.
527  * 
528  * PARAMS
529  *  SymbolicLinkHandle [O] Destination for the new symbolic link handle
530  *  DesiredAccess      [I] Desired access to the symbolic link
531  *  ObjectAttributes   [I] Structure describing the symbolic link
532  *  TargetName         [I] Name of the target symbolic link points to
533  *
534  * RETURNS
535  *  Success: ERROR_SUCCESS.
536  *  Failure: An NTSTATUS error code.
537  */
538 NTSTATUS WINAPI NtCreateSymbolicLinkObject(OUT PHANDLE SymbolicLinkHandle,IN ACCESS_MASK DesiredAccess,
539                                            IN POBJECT_ATTRIBUTES ObjectAttributes,
540                                            IN PUNICODE_STRING TargetName)
541 {
542     NTSTATUS ret;
543     TRACE("(%p,0x%08x,%p, -> %s)\n", SymbolicLinkHandle, DesiredAccess, ObjectAttributes,
544                                       debugstr_us(TargetName));
545     dump_ObjectAttributes(ObjectAttributes);
546
547     if (!SymbolicLinkHandle || !TargetName) return STATUS_ACCESS_VIOLATION;
548     if (!TargetName->Buffer) return STATUS_INVALID_PARAMETER;
549
550     SERVER_START_REQ(create_symlink)
551     {
552         req->access = DesiredAccess;
553         req->attributes = ObjectAttributes ? ObjectAttributes->Attributes : 0;
554         req->rootdir = ObjectAttributes ? ObjectAttributes->RootDirectory : 0;
555         if (ObjectAttributes && ObjectAttributes->ObjectName)
556         {
557             req->name_len = ObjectAttributes->ObjectName->Length;
558             wine_server_add_data(req, ObjectAttributes->ObjectName->Buffer,
559                                  ObjectAttributes->ObjectName->Length);
560         }
561         else
562             req->name_len = 0;
563         wine_server_add_data(req, TargetName->Buffer, TargetName->Length);
564         ret = wine_server_call( req );
565         *SymbolicLinkHandle = reply->handle;
566     }
567     SERVER_END_REQ;
568     return ret;
569 }
570
571 /******************************************************************************
572  *  NtQuerySymbolicLinkObject   [NTDLL.@]
573  *  ZwQuerySymbolicLinkObject   [NTDLL.@]
574  *
575  * Query a namespace symbolic link object target name.
576  * 
577  * PARAMS
578  *  LinkHandle     [I] Handle to a symbolic link object
579  *  LinkTarget     [O] Destination for the symbolic link target
580  *  ReturnedLength [O] Size of returned data
581  *
582  * RETURNS
583  *  Success: ERROR_SUCCESS.
584  *  Failure: An NTSTATUS error code.
585  */
586 NTSTATUS WINAPI NtQuerySymbolicLinkObject(IN HANDLE LinkHandle, IN OUT PUNICODE_STRING LinkTarget,
587                                           OUT PULONG ReturnedLength OPTIONAL)
588 {
589     NTSTATUS ret;
590     TRACE("(%p,%p,%p)\n", LinkHandle, LinkTarget, ReturnedLength);
591
592     if (!LinkTarget) return STATUS_ACCESS_VIOLATION;
593
594     SERVER_START_REQ(query_symlink)
595     {
596         req->handle = LinkHandle;
597         wine_server_set_reply( req, LinkTarget->Buffer, LinkTarget->MaximumLength );
598         if (!(ret = wine_server_call( req )))
599         {
600             LinkTarget->Length = wine_server_reply_size(reply);
601             if (ReturnedLength) *ReturnedLength = LinkTarget->Length;
602         }
603     }
604     SERVER_END_REQ;
605     return ret;
606 }
607
608 /******************************************************************************
609  *  NtAllocateUuids   [NTDLL.@]
610  */
611 NTSTATUS WINAPI NtAllocateUuids(
612         PULARGE_INTEGER Time,
613         PULONG Range,
614         PULONG Sequence)
615 {
616         FIXME("(%p,%p,%p), stub.\n", Time, Range, Sequence);
617         return 0;
618 }