version 0.3.1
[expresskeys.git] / src-expresskeys / main_setup.c
1 /*
2  main_setup.c -- Support ExpressKeys & Touch Strips on a Wacom Intuos3 tablet.
3
4  Copyright (C) 2005-2006 - Mats Johannesson
5
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10
11  This program is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  GNU General Public License for more details.
15
16  You should have received a copy of the GNU General Public License
17  along with this program; if not, write to the Free Software
18  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307, USA.
19 */
20
21 #include "globals.h"
22
23 int main (int argc, char *argv[])
24 {
25
26         our_prog_name = basename(argv[0]);
27
28         int len = 0;
29
30         struct program *p;
31
32         FILE *fp = NULL;
33         FILE *errorfp = NULL;
34         
35 /* Locate the home directory of the user running this program */
36
37         char *user_homedir;
38         if ((user_homedir = getenv("HOME")) == NULL) {
39                 exit_on_error(errorfp, "%s ERROR: Can not find your HOME directory!\n", our_prog_name, "");
40         }
41
42 /* Concatenate the home directory string with the string of our preferred
43    configuration file directory. The address to the whole string is then
44    copied to a global pointer, so we won't have to perform this part again */
45
46         char *total_config_dir_block;
47         len = strlen(user_homedir) + strlen(config_dir) + 1;
48         if ((total_config_dir_block = (char *)malloc(len)) == NULL) {
49                 exit_on_error(errorfp, "%s ERROR: Memory allocation trouble at stage 1!\n", our_prog_name, "");
50         }
51         sprintf(total_config_dir_block, "%s%s", user_homedir, config_dir);
52         total_config_dir = total_config_dir_block;
53
54 /* Concatenate the full path with the pid file name. Store address */
55
56         char *total_pid_file_block;
57         len = strlen(total_config_dir) + strlen(pid_file) + 1;
58         if ((total_pid_file_block = (char *)malloc(len)) == NULL) {
59                 exit_on_error(errorfp, "%s ERROR: Memory allocation trouble at stage 2!\n", our_prog_name, "");
60         }
61         sprintf(total_pid_file_block, "%s%s", total_config_dir, pid_file);
62         total_pid_file = total_pid_file_block;
63
64 /* Concatenate the full path with the error file name. Store address */
65
66         char *total_error_file_block;
67         len = strlen(total_config_dir) + strlen(error_file) + 1;
68         if ((total_error_file_block = (char *)malloc(len)) == NULL) {
69                 exit_on_error(errorfp, "%s ERROR: Memory allocation trouble at stage 3!\n", our_prog_name, "");
70         }
71         sprintf(total_error_file_block, "%s%s", total_config_dir, error_file);
72         total_error_file = total_error_file_block;
73
74 /* Concatenate the full path with the status file name. Store address */
75
76         char *total_status_file_block;
77         len = strlen(total_config_dir) + strlen(status_file) + 1;
78         if ((total_status_file_block = (char *)malloc(len)) == NULL) {
79                 exit_on_error(errorfp, "%s ERROR: Memory allocation trouble at stage 4!\n", our_prog_name, "");
80         }
81         sprintf(total_status_file_block, "%s%s", total_config_dir, status_file);
82         total_status_file = total_status_file_block;
83
84 /* Try to open the the configuration directory for reading, just as a
85    test to see if it exists. A failure here can mean many things, but we
86    then try to create it as a means to rule out a true lack of existence */
87
88         if ((fp = fopen(total_config_dir, "r")) == NULL) {
89                 if ((mkdir(total_config_dir, 0777)) == NON_VALID) {
90                         exit_on_error(errorfp, "%s ERROR: Can not read or create %s\n", our_prog_name, total_config_dir);
91                 }
92         } else {
93                 fclose(fp);
94         }
95
96 /* Open (and truncate) an error log for future reference */
97
98         if ((errorfp = fopen(total_error_file, "w")) == NULL) {
99                 exit_on_error(errorfp, "%s ERROR: Can not open %s in write mode\n", our_prog_name, total_error_file);
100         }
101
102 /* Prelaunch sanity checks: See if X is OK */
103
104         if ((display = XOpenDisplay(NULL)) == NULL) {
105                 exit_on_error(errorfp, "%s ERROR: Can not connect to your X Server\n", our_prog_name, "");
106         }
107         screen = DefaultScreen(display);
108
109 /* Can we use XTest to send fake key presses */
110
111         int event_base, error_base;
112         int major_version, minor_version;
113         if (!XTestQueryExtension(display, &event_base, &error_base,
114                 &major_version, &minor_version)) {
115                 exit_on_error(errorfp, "%s ERROR: XTest extension not present on your X server\n", our_prog_name, "");
116         }
117
118 /* Can we use XInput to talk with the tablet */
119
120         XExtensionVersion *xinputext;
121         xinputext = XGetExtensionVersion(display, INAME);
122         if (xinputext && (xinputext != (XExtensionVersion*) NoSuchExtension)) {
123                 XFree(xinputext);
124         } else {
125                 exit_on_error(errorfp, "%s ERROR: XInput extension not present on your X server\n", our_prog_name, "");
126         }
127
128 /* Register our error handler for Xserver error returns */
129
130         XSetErrorHandler(xerror_handler);
131
132 /* Automatically discover, open and register events with the first devices
133    that contain the string 'pad' and/or 'stylus' in their xorg.conf "Identifier"
134    entries (case insensitive). These devices will be invalidated if the user
135    specifies a named device on the command line (at least for now, since we only
136    allow one device of each kind to be registered) */
137
138         pad1_info = (void *) get_device_info(display, pad1_info_block, pad1_autoname);
139         if (pad1_info) {
140                 identify_device(pad1_info->name);
141                 if (register_events(display, pad1_info, pad1_info->name)) {
142                         pad1_autoname = pad1_info->name;
143                 } else {
144                         pad1_autoname = 0;
145                 }
146         } else {
147                 pad1_autoname = 0;
148         }
149
150         stylus1_info = (void *) get_device_info(display, stylus1_info_block, stylus1_autoname);
151         if (stylus1_info) {
152                 if (register_events(display, stylus1_info, stylus1_info->name)) {
153                         stylus1_autoname = stylus1_info->name;
154                 } else {
155                         stylus1_autoname = 0;
156                 }
157         } else {
158                 stylus1_autoname = 0;
159         }
160
161 /* Free X memory allocations for missing devices */
162
163         if (!pad1_autoname) {
164                 XFreeDeviceList(pad1_info_block);
165                 pad1_info_block = 0;
166         }
167         if (!stylus1_autoname) {
168                 XFreeDeviceList(stylus1_info_block);
169                 stylus1_info_block = 0;
170         }
171
172 /* If NO device can be found or have its events registered, we exit. Fair? */
173
174         if ((!pad1_autoname) && (!stylus1_autoname)) {
175                 exit_on_error(errorfp, "%s ERROR: Absolutely NO 'pad' or 'stylus' device seem to be connected. Check your system (eg xorg.conf)! Verify this lack by running \"xsetwacom list\" or \"xidump -l\"\n", our_prog_name, "");
176         }
177
178 /* Command line handling */
179
180         char read_buffer [MAXBUFFER];
181         char write_buffer [MAXBUFFER];
182
183         int send_sigterm = 0;
184         int send_sigusr1 = 0;
185         int send_sigusr2 = 0;
186         int give_help = 0;
187
188         int i, c, d;
189
190         while ((--argc > 0) && (**++argv == '-' || **argv != '-')) {
191                 if (**argv == '-') {
192                         while (((c = *++argv[0])) && (argc != NON_VALID)) {
193                                 switch (c) {
194
195                                         case 'd':
196                                         go_daemon = 1;
197                                         break;
198
199                                         case 'k':
200                                         send_sigterm = 1;
201                                         break;
202
203                                         case 'r':
204                                         send_sigusr1 = 1;
205                                         break;
206
207                                         case 'v':
208                                         be_verbose = 1;
209                                         break;
210
211                                         case 'x':
212                                         just_exit = 1;
213                                         be_verbose = 1;
214                                         break;
215
216                                         case 's':
217                                         send_sigusr2 = 1;
218                                         break;
219
220                                         case 'h':
221                                         give_help = 1;
222                                         break;
223
224                                         default:
225                                         fprintf(stderr, "\n%s ERROR: Invalid switch '-%c' on command line.\n", our_prog_name, c);
226                                         argc = NON_VALID;
227                                         break;
228                                 }
229                         }
230                 } else {
231                         len = strlen(*argv);
232                         if (len < MAXBUFFER) {
233                                 sprintf(read_buffer, "%s", *argv);
234                                 for (i = 0; i < len; i++) {
235                                         d = tolower(read_buffer[i]); /* Turn everything into lower case */
236                                         sprintf(write_buffer+i, "%c", d);
237                                         ++read_buffer[i];
238                                 }
239                                 strncpy(write_buffer + len, "\0", 1);
240                         } else {
241                                 exit_on_error(errorfp, "%s ERROR: A device name on the command line was too long to handle!\n", our_prog_name, "");
242                         }
243                         if (!pad1_name) {
244                                 if ((strstr(write_buffer, "pad")) !=NULL) {
245                                         pad1_name = *argv;
246                                 }
247                         }
248                         if (!stylus1_name) {
249                                 if ((strstr(write_buffer, "stylus")) !=NULL) {
250                                         stylus1_name = *argv;
251                                 }
252                         }
253                         if ((!pad1_name) && (!stylus1_name)) {
254                                 argc = NON_VALID;
255                         }
256                 }
257         }
258         if ((argc != 0) || (give_help == 1)) {
259                 fprintf(stderr, "\n");
260                 fprintf(stderr, "%s Version: %s\n", our_prog_name, our_prog_version);
261                 fprintf(stderr, "\n");
262                 fprintf(stderr, "Usage: %s [OPTION]... [DEVICE]...\n", our_prog_name);
263                 fprintf(stderr, "Any combination of pad and/or stylus can be specified.\n");
264                 fprintf(stderr, "An empty command line means automatic device search.\n");
265                 fprintf(stderr, "Options can be written like -dv or -d -v in any place.\n");
266                 fprintf(stderr, "\n");
267                 fprintf(stderr, "  -d makes the program a daemon (runs in the background).\n");
268                 fprintf(stderr, "  -k terminates (kills) an already running daemon instance.\n");
269                 fprintf(stderr, "  -r re-reads the configuration file of a running daemon.\n");
270                 fprintf(stderr, "  -v prints info to the screen at many execution points.\n");
271                 fprintf(stderr, "  -x sets -v and exits after some important info blocks.\n");
272                 fprintf(stderr, "  -s tells a daemon instance to report status (file/screen).\n");
273                 fprintf(stderr, "  -h unconditionally brings up this help text.\n");
274                 fprintf(stderr, "\n");
275                 fprintf(stderr, "Example1: %s -d (first 'pad' and/or 'stylus' found get used)\n", our_prog_name);
276                 fprintf(stderr, "Example2: %s 1stIntuos3Pad 1stIntuos3Stylus2 -d (named devices)\n", our_prog_name);
277                 fprintf(stderr, "Example3: %s -rv (visibly re-read the configuration file)\n", our_prog_name);
278                 fprintf(stderr, "\n");
279                 fprintf(stderr, "Please direct any bug reports or questions to the top address\n");
280                 fprintf(stderr, "in the AUTHORS file. This program is _not_ a linuxwacom project.\n");
281                 go_daemon = 0; /* Prevent a live PID-file deletion */
282                 exit_on_error(errorfp, "", "", "");
283         }
284
285 /* Prepare a scratch buffer for reading in an eventual PID */
286
287         char pid_buffer [MAXBUFFER];
288
289 /* The following routine handles both intentional re-runs of the program
290    (configuration file re-read requests or a terminate to a daemon), and
291    unintentional attempts to start another daemon. The latter goes like:
292
293    If a pid file exists it is a sign of either A) program already runs, or
294    B) a crash/brutal kill not handled by our exit routine has occured
295    previously. We therefore read in such a PID and perform a "fake" kill
296    with it (signal number 0). If -1 (error) is returned we just carry on.
297    Otherwise our kill test detected a process with that PID and we exit,
298    based on the assumption that another instance is running */
299
300         if ((fp = fopen(total_pid_file, "r")) != NULL) { /* File exists */
301                 fgets(pid_buffer, MAXBUFFER, fp);
302                 fclose(fp);
303                 if (((kill(atoi(pid_buffer), 0)) != NON_VALID)) {
304                         if ((send_sigusr1) || (send_sigusr2) || (send_sigterm)) {
305                                 go_daemon = 0; /* Prevent a live PID-file deletion */
306                                 if (send_sigusr1) {
307                                         if ((kill(atoi(pid_buffer), SIGUSR1)) != NON_VALID) {
308                                                 if (be_verbose) {
309                                                         exit_on_error(errorfp, "%s SUCCESS: The %s configuration file has been re-read\n", our_prog_name, our_prog_name);
310                                                 }
311                                                 exit_on_error(errorfp, "", "", "");
312                                         } else {
313                                                 exit_on_error(errorfp, "%s FAILURE: The %s configuration file could not be re-read!\n", our_prog_name, our_prog_name);
314                                         }
315                                 }
316                                 if (send_sigusr2) {
317                                         if ((kill(atoi(pid_buffer), SIGUSR2)) != NON_VALID) {
318                                                 if (be_verbose) {
319                                                         exit_on_error(errorfp, "%s SUCCESS: The %s status has been reported\n", our_prog_name, our_prog_name);
320                                                 }
321                                                 exit_on_error(errorfp, "", "", "");
322                                         } else {
323                                                 exit_on_error(errorfp, "%s FAILURE: The %s status could not be reported!\n", our_prog_name, our_prog_name);
324                                         }
325                                 }
326                                 if (send_sigterm) {
327                                         if ((kill(atoi(pid_buffer), SIGTERM)) != NON_VALID) {
328                                                 if (be_verbose) {
329                                                         exit_on_error(errorfp, "%s SUCCESS: The %s daemon has been terminated\n", our_prog_name, our_prog_name);
330                                                 }
331                                                 exit_on_error(errorfp, "", "", "");
332                                         } else {
333                                                 exit_on_error(errorfp, "%s FAILURE: The %s daemon could not be terminated!\n", our_prog_name, our_prog_name);
334                                         }
335                                 }
336                         } else {
337                                 go_daemon = 0; /* Prevent a live PID-file deletion */
338                                 exit_on_error(errorfp, "%s ERROR: Another instance of %s seems to be running!\n", our_prog_name, our_prog_name);
339                         }
340                 } else { /* Dead pid-file */
341                         if ((send_sigusr1) || (send_sigusr2) || (send_sigterm)) {
342                                 exit_on_error(errorfp, "%s ERROR: No daemon instance of %s found to send a -r -s or -k\n", our_prog_name, our_prog_name);
343                         }
344                 }
345         } else { /* No file at all */
346                 if ((send_sigusr1) || (send_sigusr2) || (send_sigterm)) {
347                         exit_on_error(errorfp, "%s ERROR: No daemon instance of %s found to send a -r -s or -k\n", our_prog_name, our_prog_name);
348                 }
349         }
350
351 /* See if a requested pad is for real, and if events can be registered with it */
352
353         if (pad1_name) {
354                 if ((pad1_autoname) && ((strlen(pad1_autoname) != (strlen(pad1_name))))) {
355                         XFreeDeviceList(pad1_info_block);
356                         pad1_info_block = 0;
357                         pad1_autoname = 0;
358                 } else {
359                         if ((pad1_autoname) && ((strcmp(pad1_autoname, pad1_name)) != 0)) {
360                                 XFreeDeviceList(pad1_info_block);
361                                 pad1_info_block = 0;
362                                 pad1_autoname = 0;
363                         }
364                 }
365                 if (!pad1_info_block) {
366                         pad1_info = (void *) get_device_info(display, pad1_info_block, pad1_name);
367                         if (!pad1_info) {
368                                 exit_on_error(errorfp, "%s ERROR: Can not find pad device: %s\n", our_prog_name, pad1_name);
369                         }
370                         identify_device(pad1_name);
371                         if (!register_events(display, pad1_info, pad1_name)) {
372                                 exit_on_error(errorfp, "%s ERROR: Could not register any pad events with %s\n", our_prog_name, pad1_name);
373                         }
374                 } else {
375                         pad1_name = pad1_autoname;
376                 }
377         } else {
378                 pad1_name = pad1_autoname;
379         }
380
381 /* See if a requested stylus is for real, and if events can be registered with it */
382
383         if (stylus1_name) {
384                 if ((stylus1_autoname) && ((strlen(stylus1_autoname) != (strlen(stylus1_name))))) {
385                         XFreeDeviceList(stylus1_info_block);
386                         stylus1_info_block = 0;
387                         stylus1_autoname = 0;
388                 } else {
389                         if ((stylus1_autoname) && ((strcmp(stylus1_autoname, stylus1_name)) != 0)) {
390                                 XFreeDeviceList(stylus1_info_block);
391                                 stylus1_info_block = 0;
392                                 stylus1_autoname = 0;
393                         }
394                 }
395                 if (!stylus1_info_block) {
396                         stylus1_info = (void *) get_device_info(display, stylus1_info_block, stylus1_name);
397                         if (!stylus1_info) {
398                                 exit_on_error(errorfp, "%s ERROR: Can not find stylus device: %s\n", our_prog_name, stylus1_name);
399                         }
400                         if (!register_events(display, stylus1_info, stylus1_name)) {
401                                 exit_on_error(errorfp, "%s ERROR: Could not register any stylus events with %s\n", our_prog_name, stylus1_name);
402                         }
403                 } else {
404                         stylus1_name = stylus1_autoname;
405                 }
406         } else {
407                 stylus1_name = stylus1_autoname;
408         }
409
410 /* Now determine what the config file will be named as */
411
412 if (!is_graphire4) {
413         if ((pad1_name) && (stylus1_name)) {
414                 config_file = config_file_intuos3;
415         } else {
416                 if (stylus1_name) {
417                         config_file = config_file_padless;
418                 } else {
419                         config_file = config_file_intuos3; /* User xorg.conf error? 'pad' without 'stylus'! */
420                 }
421         }
422 } else {
423         config_file = config_file_graphire4;
424 }
425
426 /* Concatenate the full path with the config file name. Store address */
427
428         char *total_config_file_block;
429         len = strlen(total_config_dir) + strlen(config_file) + 1;
430         if ((total_config_file_block = (char *)malloc(len)) == NULL) {
431                 exit_on_error(errorfp, "%s ERROR: Memory allocation trouble at stage 5!\n", our_prog_name, "");
432         }
433         sprintf(total_config_file_block, "%s%s", total_config_dir, config_file);
434         total_config_file = total_config_file_block;
435
436         if (be_verbose) {
437                 fprintf(stderr, "PGR VERSION = %s\n", our_prog_version);
438                 fprintf(stderr, "USR HOMEDIR = %s\n", user_homedir);
439                 fprintf(stderr, "OUR CNF-DIR = %s\n", total_config_dir);
440                 fprintf(stderr, "OUR CNFFILE = %s\n", total_config_file);
441                 fprintf(stderr, "OUR PIDFILE = %s\n", total_pid_file);
442                 fprintf(stderr, "OUR INFFILE = %s\n", total_status_file);
443                 fprintf(stderr, "OUR ERRFILE = %s\n", total_error_file);
444                 if (pad1_name) {
445                         fprintf(stderr, "OUR PD1NAME = %s\n", pad1_name);
446                 }
447                 if (stylus1_name) {
448                         fprintf(stderr, "OUR ST1NAME = %s\n", stylus1_name);
449                 }
450         }
451
452 /* If no configuration file exists, write out a short one from an internal
453    memory structure. Also tag it with a Config File Version number */
454
455         if ((fp = fopen(total_config_file, "a+")) == NULL) {
456                 exit_on_error(errorfp, "%s ERROR: Can not open %s in read/write mode\n", our_prog_name, total_config_file);
457         } else {
458                 rewind(fp);
459                 if (fgetc(fp) == EOF) {
460                         write_file_config_header(fp);
461                         for (p = internal_list; p < internal_list + num_list; p++) {
462                                 write_file_config((void *)&p, fp);
463                                 if (ferror(fp)) {
464                                         exit_on_error(errorfp, "%s ERROR: Write error in %s\n", our_prog_name, total_config_file);
465                                 }
466                         }
467                 }
468         }
469         fclose(fp);
470
471         p = external_list;
472         if ((fp = fopen(total_config_file, "r")) == NULL) {
473                 exit_on_error(errorfp, "%s ERROR: Can not open %s in read mode\n", our_prog_name, total_config_file);
474         } else {
475                 switch (read_file_config((void *)&p, fp)){
476                         case 0: /* No errors */
477                         fclose(fp);
478                         break; /* OBS An identical list of error code returns exist in the on_signal.c
479                                         file (the re_read_file_config function). So change both when altering. */
480
481                         case 1:
482                         fclose(fp);
483                         exit_on_error(errorfp, "%s ERROR: No complete record found in %s\n", our_prog_name, total_config_file);
484
485                         case 2:
486                         fclose(fp);
487                         exit_on_error(errorfp, "%s ERROR: Memory allocation error while parsing %s\n", our_prog_name, total_config_file);
488
489                         case 3:
490                         fclose(fp);
491                         exit_on_error(errorfp, "%s ERROR: Config File Version 3 or higher not found\n", our_prog_name, "");
492
493                         case 4:
494                         fclose(fp);
495                         exit_on_error(errorfp, "%s ERROR: A line was too long to handle in the Config File %s\n", our_prog_name, total_config_file);
496
497                         case 5:
498                         fclose(fp);
499                         exit_on_error(errorfp, "%s ERROR: A program record named \"default\" must exist in %s\n", our_prog_name, total_config_file);
500
501                         default:
502                         fclose(fp);
503                         exit_on_error(errorfp, "%s ERROR: Unknown error while parsing %s\n", our_prog_name, total_config_file);
504                 }
505         }
506
507 /* Exit if all we wanted to see was the main 'debugging' info block */
508
509         if (just_exit) {
510                 exit_on_error(errorfp, "", "", "");
511         }
512
513 /* Replace some of the normal signal handlers with our own functions. We
514    want SIGUSR1 to read in the config file after a modification, SIGUSR2
515    to print status (the information we would get from -x) to the screen,
516    and all the normal program exits should first clean up a bit */
517
518         if ((signal(SIGUSR1, re_read_file_config) == SIG_ERR)
519                 || (signal(SIGUSR2, status_report) == SIG_ERR)
520                 || (signal(SIGINT, clean_up_exit) == SIG_ERR)
521                 || (signal(SIGHUP, clean_up_exit) == SIG_ERR)
522                 || (signal(SIGTERM, clean_up_exit) == SIG_ERR)) {
523                 exit_on_error(errorfp, "%s ERROR: Failed to modify signal handling!\n", our_prog_name, "");
524         }
525
526 /* Store whatever information we have in the status file (silent use of the -s switch */
527
528         if (go_daemon) {
529                 go_daemon = 0;
530                 status_report(0);
531                 go_daemon = 1;
532         }
533
534 /* Ready to launch in the foreground or as a daemon.
535    In daemon mode we also take care of storing our PID in the config dir
536    Observe that with a (0, 0) standard input/output/error goes to /dev/null
537    I've found it better to use (0, 1) and see errors while writing the code
538    It also comes in handy when running in (-v) verbose mode */
539
540                 if (go_daemon) {
541                         if ((daemon(0, 1)) == NON_VALID) {
542                                 exit_on_error(errorfp, "%s ERROR: Failed to fork into daemon mode! EXITING!\n", our_prog_name, "");
543                         } else {
544                                 sprintf(pid_buffer, "%d", getpid());
545                                 if ((fp = fopen(total_pid_file, "w")) == NULL) {
546                                         exit_on_error(errorfp, "%s ERROR: Can not open %s in write mode\n", our_prog_name, total_pid_file);
547                                 } else {
548                                         if (be_verbose) {
549                                                 fprintf(stderr, "OUR RUN-PID = %s\n", pid_buffer);
550                                         }
551                                         fprintf(fp, "%s", pid_buffer);
552                                         if (ferror(fp)) {
553                                                 exit_on_error(errorfp, "%s ERROR: Write error in %s\n", our_prog_name, total_pid_file);
554                                         } else {
555                                                 fclose(fp);
556                                         }
557                                 }
558                         }
559                 }
560                 fclose(errorfp);
561                 use_events(display); /* <-- Our true launch! The event loop */
562
563         exit(EXIT_OK); /* We should never reach this */
564
565 }
566
567 /* End Code */
568