* dbus/*-win.*,bus/*-win.*: added win32 platform related
[platform/upstream/dbus.git] / dbus / dbus-spawn-win.c
1 #include "config.h"
2
3 #if !defined(DBUS_ENABLE_VERBOSE_MODE) || defined(_MSC_VER)
4 #define PING()
5 #else
6 #define PING() fprintf (stderr, "%s:%s:%d\n", __FILE__, __FUNCTION__, __LINE__); fflush (stderr)
7 #endif
8
9 #include <stdio.h>
10 #ifdef DBUS_WINCE
11 #include <process.h>
12 #endif
13
14 /* -*- mode: C; c-file-style: "gnu" -*- */
15 /* dbus-spawn-win32.c Wrapper around g_spawn
16  * 
17  * Copyright (C) 2002, 2003, 2004  Red Hat, Inc.
18  * Copyright (C) 2003 CodeFactory AB
19  * Copyright (C) 2005 Novell, Inc.
20  *
21  * Licensed under the Academic Free License version 2.1
22  * 
23  * This program is free software; you can redistribute it and/or modify
24  * it under the terms of the GNU General Public License as published by
25  * the Free Software Foundation; either version 2 of the License, or
26  * (at your option) any later version.
27  *
28  * This program is distributed in the hope that it will be useful,
29  * but WITHOUT ANY WARRANTY; without even the implied warranty of
30  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
31  * GNU General Public License for more details.
32  * 
33  * You should have received a copy of the GNU General Public License
34  * along with this program; if not, write to the Free Software
35  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
36  *
37  */
38 #include "dbus-spawn.h"
39 #include "dbus-sysdeps.h"
40 #include "dbus-sysdeps-win.h"
41 #include "dbus-internals.h"
42 #include "dbus-test.h"
43 #include "dbus-protocol.h"
44
45 #define WIN32_LEAN_AND_MEAN
46 //#define STRICT
47 //#include <windows.h>
48 //#undef STRICT
49 #include <winsock2.h>
50 #undef interface
51
52 #include <stdlib.h>
53
54 #include <process.h>
55
56 /**
57  * Babysitter implementation details
58  */
59 struct DBusBabysitter
60   {
61     int refcount;
62
63     HANDLE start_sync_event;
64 #ifdef DBUS_BUILD_TESTS
65
66     HANDLE end_sync_event;
67 #endif
68
69     char *executable;
70     DBusSpawnChildSetupFunc child_setup;
71     void *user_data;
72
73     int argc;
74     char **argv;
75     char **envp;
76
77     HANDLE child_handle;
78     int socket_to_babysitter;   /* Connection to the babysitter thread */
79     int socket_to_main;
80
81     DBusWatchList *watches;
82     DBusWatch *sitter_watch;
83
84     dbus_bool_t have_spawn_errno;
85     int spawn_errno;
86     dbus_bool_t have_child_status;
87     int child_status;
88   };
89
90 static DBusBabysitter*
91 _dbus_babysitter_new (void)
92 {
93   DBusBabysitter *sitter;
94
95   sitter = dbus_new0 (DBusBabysitter, 1);
96   if (sitter == NULL)
97     return NULL;
98
99   sitter->refcount = 1;
100
101   sitter->start_sync_event = CreateEvent (NULL, FALSE, FALSE, NULL);
102   if (sitter->start_sync_event == NULL)
103     {
104       _dbus_babysitter_unref (sitter);
105       return NULL;
106     }
107
108 #ifdef DBUS_BUILD_TESTS
109   sitter->end_sync_event = CreateEvent (NULL, FALSE, FALSE, NULL);
110   if (sitter->end_sync_event == NULL)
111     {
112       _dbus_babysitter_unref (sitter);
113       return NULL;
114     }
115 #endif
116
117   sitter->child_handle = NULL;
118
119   sitter->socket_to_babysitter = sitter->socket_to_main = -1;
120
121   sitter->argc = 0;
122   sitter->argv = NULL;
123   sitter->envp = NULL;
124
125   sitter->watches = _dbus_watch_list_new ();
126   if (sitter->watches == NULL)
127     {
128       _dbus_babysitter_unref (sitter);
129       return NULL;
130     }
131
132   sitter->have_spawn_errno = FALSE;
133   sitter->have_child_status = FALSE;
134
135   return sitter;
136 }
137
138 /**
139  * Increment the reference count on the babysitter object.
140  *
141  * @param sitter the babysitter
142  * @returns the babysitter
143  */
144 DBusBabysitter *
145 _dbus_babysitter_ref (DBusBabysitter *sitter)
146 {
147   PING();
148   _dbus_assert (sitter != NULL);
149   _dbus_assert (sitter->refcount > 0);
150
151   sitter->refcount += 1;
152
153   return sitter;
154 }
155
156 /**
157  * Decrement the reference count on the babysitter object.
158  *
159  * @param sitter the babysitter
160  */
161 void
162 _dbus_babysitter_unref (DBusBabysitter *sitter)
163 {
164   int i;
165
166   PING();
167   _dbus_assert (sitter != NULL);
168   _dbus_assert (sitter->refcount > 0);
169
170   sitter->refcount -= 1;
171
172   if (sitter->refcount == 0)
173     {
174       if (sitter->socket_to_babysitter != -1)
175         {
176           _dbus_close_socket (sitter->socket_to_babysitter, NULL);
177           sitter->socket_to_babysitter = -1;
178         }
179
180       if (sitter->socket_to_main != -1)
181         {
182           _dbus_close_socket (sitter->socket_to_main, NULL);
183           sitter->socket_to_main = -1;
184         }
185
186       PING();
187       if (sitter->argv != NULL)
188         {
189           for (i = 0; i < sitter->argc; i++)
190             if (sitter->argv[i] != NULL)
191               {
192                 dbus_free (sitter->argv[i]);
193                 sitter->argv[i] = NULL;
194               }
195           dbus_free (sitter->argv);
196           sitter->argv = NULL;
197         }
198
199       if (sitter->envp != NULL)
200         {
201           char **e = sitter->envp;
202
203           while (*e)
204             dbus_free (*e++);
205           dbus_free (sitter->envp);
206           sitter->envp = NULL;
207         }
208
209       if (sitter->child_handle != NULL)
210         {
211           CloseHandle (sitter->child_handle);
212           sitter->child_handle = NULL;
213         }
214
215       if (sitter->sitter_watch)
216         {
217           _dbus_watch_invalidate (sitter->sitter_watch);
218           _dbus_watch_unref (sitter->sitter_watch);
219           sitter->sitter_watch = NULL;
220         }
221
222       if (sitter->watches)
223         _dbus_watch_list_free (sitter->watches);
224
225       if (sitter->start_sync_event != NULL)
226         {
227           PING();
228           CloseHandle (sitter->start_sync_event);
229           sitter->end_sync_event = NULL;
230         }
231
232 #ifdef DBUS_BUILD_TESTS
233       if (sitter->end_sync_event != NULL)
234         {
235           CloseHandle (sitter->end_sync_event);
236           sitter->end_sync_event = NULL;
237         }
238 #endif
239
240       dbus_free (sitter->executable);
241
242       dbus_free (sitter);
243     }
244 }
245
246 void
247 _dbus_babysitter_kill_child (DBusBabysitter *sitter)
248 {
249   PING();
250   if (sitter->child_handle == NULL)
251     return; /* child is already dead, or we're so hosed we'll never recover */
252
253   PING();
254   TerminateProcess (sitter->child_handle, 12345);
255 }
256
257 /**
258  * Checks whether the child has exited, without blocking.
259  *
260  * @param sitter the babysitter
261  */
262 dbus_bool_t
263 _dbus_babysitter_get_child_exited (DBusBabysitter *sitter)
264 {
265   PING();
266   return (sitter->child_handle == NULL);
267 }
268
269 /**
270  * Sets the #DBusError with an explanation of why the spawned
271  * child process exited (on a signal, or whatever). If
272  * the child process has not exited, does nothing (error
273  * will remain unset).
274  *
275  * @param sitter the babysitter
276  * @param error an error to fill in
277  */
278 void
279 _dbus_babysitter_set_child_exit_error (DBusBabysitter *sitter,
280                                        DBusError      *error)
281 {
282   PING();
283   if (!_dbus_babysitter_get_child_exited (sitter))
284     return;
285
286   PING();
287   if (sitter->have_spawn_errno)
288     {
289       dbus_set_error (error, DBUS_ERROR_SPAWN_EXEC_FAILED,
290                       "Failed to execute program %s: %s",
291                       sitter->executable, _dbus_strerror (sitter->spawn_errno));
292     }
293   else if (sitter->have_child_status)
294     {
295       PING();
296       dbus_set_error (error, DBUS_ERROR_SPAWN_CHILD_EXITED,
297                       "Process %s exited with status %d",
298                       sitter->executable, sitter->child_status);
299     }
300   else
301     {
302       PING();
303       dbus_set_error (error, DBUS_ERROR_FAILED,
304                       "Process %s exited, status unknown",
305                       sitter->executable);
306     }
307   PING();
308 }
309
310 dbus_bool_t
311 _dbus_babysitter_set_watch_functions (DBusBabysitter            *sitter,
312                                       DBusAddWatchFunction       add_function,
313                                       DBusRemoveWatchFunction    remove_function,
314                                       DBusWatchToggledFunction   toggled_function,
315                                       void                      *data,
316                                       DBusFreeFunction           free_data_function)
317 {
318   PING();
319   return _dbus_watch_list_set_functions (sitter->watches,
320                                          add_function,
321                                          remove_function,
322                                          toggled_function,
323                                          data,
324                                          free_data_function);
325 }
326
327 static dbus_bool_t
328 handle_watch (DBusWatch       *watch,
329               unsigned int     condition,
330               void            *data)
331 {
332   DBusBabysitter *sitter = data;
333
334   /* On Unix dbus-spawn uses a babysitter *process*, thus it has to
335    * actually send the exit statuses, error codes and whatnot through
336    * sockets and/or pipes. On Win32, the babysitter is jus a thread,
337    * so it can set the status fields directly in the babysitter struct
338    * just fine. The socket pipe is used just so we can watch it with
339    * select(), as soon as anything is written to it we know that the
340    * babysitter thread has recorded the status in the babysitter
341    * struct.
342    */
343
344   PING();
345   _dbus_close_socket (sitter->socket_to_babysitter, NULL);
346   PING();
347   sitter->socket_to_babysitter = -1;
348
349   return TRUE;
350 }
351
352 /* protect_argv lifted from GLib, relicensed by author, Tor Lillqvist */
353 static int
354 protect_argv (char  **argv,
355               char ***new_argv)
356 {
357   int i;
358   int argc = 0;
359
360   while (argv[argc])
361     ++argc;
362   *new_argv = dbus_malloc ((argc + 1) * sizeof (char *));
363   if (*new_argv == NULL)
364     return -1;
365
366   for (i = 0; i < argc; i++)
367     (*new_argv)[i] = NULL;
368
369   /* Quote each argv element if necessary, so that it will get
370    * reconstructed correctly in the C runtime startup code.  Note that
371    * the unquoting algorithm in the C runtime is really weird, and
372    * rather different than what Unix shells do. See stdargv.c in the C
373    * runtime sources (in the Platform SDK, in src/crt).
374    *
375    * Note that an new_argv[0] constructed by this function should
376    * *not* be passed as the filename argument to a spawn* or exec*
377    * family function. That argument should be the real file name
378    * without any quoting.
379    */
380   for (i = 0; i < argc; i++)
381     {
382       char *p = argv[i];
383       char *q;
384       int len = 0;
385       int need_dblquotes = FALSE;
386       while (*p)
387         {
388           if (*p == ' ' || *p == '\t')
389             need_dblquotes = TRUE;
390           else if (*p == '"')
391             len++;
392           else if (*p == '\\')
393             {
394               char *pp = p;
395               while (*pp && *pp == '\\')
396                 pp++;
397               if (*pp == '"')
398                 len++;
399             }
400           len++;
401           p++;
402         }
403
404       q = (*new_argv)[i] = dbus_malloc (len + need_dblquotes*2 + 1);
405
406       if (q == NULL)
407         return -1;
408
409
410       p = argv[i];
411
412       if (need_dblquotes)
413         *q++ = '"';
414
415       while (*p)
416         {
417           if (*p == '"')
418             *q++ = '\\';
419           else if (*p == '\\')
420             {
421               char *pp = p;
422               while (*pp && *pp == '\\')
423                 pp++;
424               if (*pp == '"')
425                 *q++ = '\\';
426             }
427           *q++ = *p;
428           p++;
429         }
430
431       if (need_dblquotes)
432         *q++ = '"';
433       *q++ = '\0';
434       /* printf ("argv[%d]:%s, need_dblquotes:%s len:%d => %s\n", i, argv[i], need_dblquotes?"TRUE":"FALSE", len, (*new_argv)[i]); */
435     }
436   (*new_argv)[argc] = NULL;
437
438   return argc;
439 }
440
441 static unsigned __stdcall
442 babysitter (void *parameter)
443 {
444   DBusBabysitter *sitter = (DBusBabysitter *) parameter;
445   DBusSocket *sock;
446   PING();
447   _dbus_babysitter_ref (sitter);
448
449   if (sitter->child_setup)
450     {
451       PING();
452       (*sitter->child_setup) (sitter->user_data);
453     }
454
455   _dbus_verbose ("babysitter: spawning %s\n", sitter->executable);
456   fprintf (stderr, "babysitter: spawning %s\n", sitter->executable);
457
458   PING();
459   if (sitter->envp != NULL)
460     sitter->child_handle = (HANDLE) spawnve (P_NOWAIT, sitter->executable,
461                            (const char * const *) sitter->argv,
462                            (const char * const *) sitter->envp);
463   else
464     sitter->child_handle = (HANDLE) spawnv (P_NOWAIT, sitter->executable,
465                                             (const char * const *) sitter->argv);
466
467   PING();
468   if (sitter->child_handle == (HANDLE) -1)
469     {
470       sitter->child_handle = NULL;
471       sitter->have_spawn_errno = TRUE;
472       sitter->spawn_errno = errno;
473     }
474
475   PING();
476   SetEvent (sitter->start_sync_event);
477
478   if (sitter->child_handle != NULL)
479     {
480       int ret;
481       DWORD status;
482
483       PING();
484       WaitForSingleObject (sitter->child_handle, INFINITE);
485
486       PING();
487       ret = GetExitCodeProcess (sitter->child_handle, &status);
488
489       sitter->child_status = status;
490       sitter->have_child_status = TRUE;
491
492       CloseHandle (sitter->child_handle);
493       sitter->child_handle = NULL;
494     }
495
496 #ifdef DBUS_BUILD_TESTS
497   SetEvent (sitter->end_sync_event);
498 #endif
499
500   PING();
501   _dbus_handle_to_socket (sitter->socket_to_main, &sock);
502   send (sock->fd, " ", 1, 0);
503
504   _dbus_babysitter_unref (sitter);
505
506   return 0;
507 }
508
509 dbus_bool_t
510 _dbus_spawn_async_with_babysitter (DBusBabysitter           **sitter_p,
511                                    char                     **argv,
512                                    char                     **envp,
513                                    DBusSpawnChildSetupFunc    child_setup,
514                                    void                      *user_data,
515                                    DBusError                 *error)
516 {
517   DBusBabysitter *sitter;
518   HANDLE sitter_thread;
519   int sitter_thread_id;
520
521   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
522
523   *sitter_p = NULL;
524
525   PING();
526   sitter = _dbus_babysitter_new ();
527   if (sitter == NULL)
528     {
529       _DBUS_SET_OOM (error);
530       return FALSE;
531     }
532
533   sitter->child_setup = child_setup;
534   sitter->user_data = user_data;
535
536   sitter->executable = _dbus_strdup (argv[0]);
537   if (sitter->executable == NULL)
538     {
539       _DBUS_SET_OOM (error);
540       goto out0;
541     }
542
543   PING();
544   if (!_dbus_full_duplex_pipe (&sitter->socket_to_babysitter,
545                                &sitter->socket_to_main,
546                                FALSE, error))
547     goto out0;
548
549   sitter->sitter_watch = _dbus_watch_new (sitter->socket_to_babysitter,
550                                           DBUS_WATCH_READABLE,
551                                           TRUE, handle_watch, sitter, NULL);
552   PING();
553   if (sitter->sitter_watch == NULL)
554     {
555       _DBUS_SET_OOM (error);
556       goto out0;
557     }
558
559   PING();
560   if (!_dbus_watch_list_add_watch (sitter->watches,  sitter->sitter_watch))
561     {
562       _DBUS_SET_OOM (error);
563       goto out0;
564     }
565
566   sitter->argc = protect_argv (argv, &sitter->argv);
567   if (sitter->argc == -1)
568     {
569       _DBUS_SET_OOM (error);
570       goto out0;
571     }
572   sitter->envp = envp;
573
574   PING();
575   sitter_thread = (HANDLE) _beginthreadex (NULL, 0, babysitter,
576                   sitter, 0, &sitter_thread_id);
577
578   if (sitter_thread == 0)
579     {
580       PING();
581       dbus_set_error_const (error, DBUS_ERROR_SPAWN_FORK_FAILED,
582                             "Failed to create new thread");
583       goto out0;
584     }
585   CloseHandle (sitter_thread);
586
587   PING();
588   WaitForSingleObject (sitter->start_sync_event, INFINITE);
589
590   PING();
591   if (sitter_p != NULL)
592     *sitter_p = sitter;
593   else
594     _dbus_babysitter_unref (sitter);
595
596   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
597
598   PING();
599   return TRUE;
600
601 out0:
602   _dbus_babysitter_unref (sitter);
603
604   return FALSE;
605 }
606
607 #ifdef DBUS_BUILD_TESTS
608
609 #define LIVE_CHILDREN(sitter) ((sitter)->child_handle != NULL)
610
611 static void
612 _dbus_babysitter_block_for_child_exit (DBusBabysitter *sitter)
613 {
614   if (sitter->child_handle == NULL)
615     return;
616
617   WaitForSingleObject (sitter->end_sync_event, INFINITE);
618 }
619
620 static dbus_bool_t
621 check_spawn_nonexistent (void *data)
622 {
623   char *argv[4] = { NULL, NULL, NULL, NULL };
624   DBusBabysitter *sitter;
625   DBusError error;
626
627   sitter = NULL;
628
629   dbus_error_init (&error);
630
631   /*** Test launching nonexistent binary */
632
633   argv[0] = "/this/does/not/exist/32542sdgafgafdg";
634   if (_dbus_spawn_async_with_babysitter (&sitter, argv, NULL,
635                                          NULL, NULL,
636                                          &error))
637     {
638       _dbus_babysitter_block_for_child_exit (sitter);
639       _dbus_babysitter_set_child_exit_error (sitter, &error);
640     }
641
642   if (sitter)
643     _dbus_babysitter_unref (sitter);
644
645   if (!dbus_error_is_set (&error))
646     {
647       _dbus_warn ("Did not get an error launching nonexistent executable\n");
648       return FALSE;
649     }
650
651   if (!(dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY) ||
652         dbus_error_has_name (&error, DBUS_ERROR_SPAWN_EXEC_FAILED)))
653     {
654       _dbus_warn ("Not expecting error when launching nonexistent executable: %s: %s\n",
655                   error.name, error.message);
656       dbus_error_free (&error);
657       return FALSE;
658     }
659
660   dbus_error_free (&error);
661
662   return TRUE;
663 }
664
665 static dbus_bool_t
666 check_spawn_segfault (void *data)
667 {
668   char *argv[4] = { NULL, NULL, NULL, NULL };
669   DBusBabysitter *sitter;
670   DBusError error;
671
672   sitter = NULL;
673
674   dbus_error_init (&error);
675
676   /*** Test launching segfault binary */
677
678   argv[0] = TEST_SEGFAULT_BINARY;
679   if (_dbus_spawn_async_with_babysitter (&sitter, argv, NULL,
680                                          NULL, NULL,
681                                          &error))
682     {
683       _dbus_babysitter_block_for_child_exit (sitter);
684       _dbus_babysitter_set_child_exit_error (sitter, &error);
685     }
686
687   if (sitter)
688     _dbus_babysitter_unref (sitter);
689
690   if (!dbus_error_is_set (&error))
691     {
692       _dbus_warn ("Did not get an error launching segfaulting binary\n");
693       return FALSE;
694     }
695
696   if (!(dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY) ||
697         dbus_error_has_name (&error, DBUS_ERROR_SPAWN_CHILD_EXITED)))
698     {
699       _dbus_warn ("Not expecting error when launching segfaulting executable: %s: %s\n",
700                   error.name, error.message);
701       dbus_error_free (&error);
702       return FALSE;
703     }
704
705   dbus_error_free (&error);
706
707   return TRUE;
708 }
709
710 static dbus_bool_t
711 check_spawn_exit (void *data)
712 {
713   char *argv[4] = { NULL, NULL, NULL, NULL };
714   DBusBabysitter *sitter;
715   DBusError error;
716
717   sitter = NULL;
718
719   dbus_error_init (&error);
720
721   /*** Test launching exit failure binary */
722
723   argv[0] = TEST_EXIT_BINARY;
724   if (_dbus_spawn_async_with_babysitter (&sitter, argv, NULL,
725                                          NULL, NULL,
726                                          &error))
727     {
728       _dbus_babysitter_block_for_child_exit (sitter);
729       _dbus_babysitter_set_child_exit_error (sitter, &error);
730     }
731
732   if (sitter)
733     _dbus_babysitter_unref (sitter);
734
735   if (!dbus_error_is_set (&error))
736     {
737       _dbus_warn ("Did not get an error launching binary that exited with failure code\n");
738       return FALSE;
739     }
740
741   if (!(dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY) ||
742         dbus_error_has_name (&error, DBUS_ERROR_SPAWN_CHILD_EXITED)))
743     {
744       _dbus_warn ("Not expecting error when launching exiting executable: %s: %s\n",
745                   error.name, error.message);
746       dbus_error_free (&error);
747       return FALSE;
748     }
749
750   dbus_error_free (&error);
751
752   return TRUE;
753 }
754
755 static dbus_bool_t
756 check_spawn_and_kill (void *data)
757 {
758   char *argv[4] = { NULL, NULL, NULL, NULL };
759   DBusBabysitter *sitter;
760   DBusError error;
761
762   sitter = NULL;
763
764   dbus_error_init (&error);
765
766   /*** Test launching sleeping binary then killing it */
767
768   argv[0] = TEST_SLEEP_FOREVER_BINARY;
769   if (_dbus_spawn_async_with_babysitter (&sitter, argv, NULL,
770                                          NULL, NULL,
771                                          &error))
772     {
773       _dbus_babysitter_kill_child (sitter);
774
775       _dbus_babysitter_block_for_child_exit (sitter);
776
777       _dbus_babysitter_set_child_exit_error (sitter, &error);
778     }
779
780   if (sitter)
781     _dbus_babysitter_unref (sitter);
782
783   if (!dbus_error_is_set (&error))
784     {
785       _dbus_warn ("Did not get an error after killing spawned binary\n");
786       return FALSE;
787     }
788
789   if (!(dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY) ||
790         dbus_error_has_name (&error, DBUS_ERROR_SPAWN_CHILD_EXITED)))
791     {
792       _dbus_warn ("Not expecting error when killing executable: %s: %s\n",
793                   error.name, error.message);
794       dbus_error_free (&error);
795       return FALSE;
796     }
797
798   dbus_error_free (&error);
799
800   return TRUE;
801 }
802
803 dbus_bool_t
804 _dbus_spawn_test (const char *test_data_dir)
805 {
806   if (!_dbus_test_oom_handling ("spawn_nonexistent",
807                                 check_spawn_nonexistent,
808                                 NULL))
809     return FALSE;
810
811   /* Don't run the obnoxious segfault test by default,
812    * it's a pain to have to click all those error boxes.
813    */
814   if (getenv ("DO_SEGFAULT_TEST"))
815     if (!_dbus_test_oom_handling ("spawn_segfault",
816                                   check_spawn_segfault,
817                                   NULL))
818       return FALSE;
819
820   if (!_dbus_test_oom_handling ("spawn_exit",
821                                 check_spawn_exit,
822                                 NULL))
823     return FALSE;
824
825   if (!_dbus_test_oom_handling ("spawn_and_kill",
826                                 check_spawn_and_kill,
827                                 NULL))
828     return FALSE;
829
830   return TRUE;
831 }
832 #endif