2 * CDE - Common Desktop Environment
4 * Copyright (c) 1993-2012, The Open Group. All rights reserved.
6 * These libraries and programs are free software; you can
7 * redistribute them and/or modify them under the terms of the GNU
8 * Lesser General Public License as published by the Free Software
9 * Foundation; either version 2 of the License, or (at your option)
12 * These libraries and programs are distributed in the hope that
13 * they will be useful, but WITHOUT ANY WARRANTY; without even the
14 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU Lesser General Public License for more
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with these libraries and programs; if not, write
20 * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
21 * Floor, Boston, MA 02110-1301 USA
23 /******************************************************************************
24 ******************************************************************************
27 ** RCS: $XConsortium: manager.c /main/8 1996/10/30 19:10:15 cde-hp $
31 ** (c) Copyright 1995, Hewlett-Packard Company, all rights reserved.
33 ******************************************************************************
34 *****************************************************************************/
38 #define OPTIONAL_PXAUTH_PRETEST 1
47 /******************************************************************************
48 ******************************************************************************
50 * The following PDM_MANAGER : PDM_START routines are registered
51 * per child, and dispatched out of XtAppNextEvent
54 /********************************************************************
56 * message_pipe_handler()
58 * Takes stderr from the child, via a pipe, and stores the output
59 * in the client tracking record.
62 static void message_pipe_handler( XtPointer w, int *source, XtInputId *id)
65 struct stat statBuffer;
70 * Find out who is generating output.
74 for ( i = 0; i < g.serviceRecNum; i++ ) {
75 if ( g.serviceRecs[i]->message_pipe[0] == *source ) {
78 * Fetch size and grow message_string to hold more.
80 if ( fstat(*source, &statBuffer) ) {
81 /* unable to get size */
82 statBuffer.st_size = 0; /* bail out below */
85 if ( statBuffer.st_size > 0 ) {
86 if ( g.serviceRecs[i]->message_string ) {
87 inc = strlen( g.serviceRecs[i]->message_string );
89 g.serviceRecs[i]->message_string = Xrealloc(
90 (char *) g.serviceRecs[i]->message_string,
91 statBuffer.st_size + inc + 1 );
96 g.serviceRecs[i]->message_string = Xmalloc(
97 statBuffer.st_size + 1 );
101 * Read off what we know is there.
105 n = read( *source, &(g.serviceRecs[i]->message_string[inc]),
106 statBuffer.st_size );
108 if ( n == statBuffer.st_size ) {
109 /* read all there is (per the previous stat) */
112 else if (( n == -1 ) && ( errno == EINTR )) {
113 /* an interrupt came in before the read could start */
116 else if (( n == -1 ) || ( n == 0 )) {
117 /* problems - bail out */
118 g.serviceRecs[i]->message_pipe[0] = -1;
124 /* only a partial read, probably a sig, try for more */
126 statBuffer.st_size -= n;
132 * NULL terminate what we have so far to make it look
135 g.serviceRecs[i]->message_string[statBuffer.st_size+inc] = '\0';
139 * No more to read - this really means the pipe is
142 g.serviceRecs[i]->message_pipe[0] = -1;
151 * Some stray pipe that we no longer have information on.
158 * Scan client records and see who is ready to be
165 /******************************************************************************
166 ******************************************************************************
168 * The following PDM_MANAGER : PDM_START routines are called by
169 * the dispatch routines to service selection requests and
173 /********************************************************************
175 * Setup a child service record per the selection request.
177 void mgr_initialize( XEvent *report, XpPdmServiceRec *rec )
182 Display *selection_display;
185 unsigned long tafter;
187 XTextProperty text_prop;
192 * Grab the PDM_CLIENT_PROP from which all the juicy
193 * information is retrieved.
195 selection_display = report->xselectionrequest.display;
196 requestor = report->xselectionrequest.requestor;
197 prop_atom = report->xselectionrequest.property;
199 if ( XGetWindowProperty( selection_display, requestor, prop_atom,
200 0, 100000, True, AnyPropertyType,
205 &text_prop.value ) != Success ) {
209 rec->pdm_exec_errorcode = g.pdm_start_error;
211 sprintf( buf, PDMD_MSG_5, g.prog_name );
212 rec->pdm_exec_errormessage = xpstrdup( buf );
217 if ( text_prop.format != 8 ) {
221 rec->pdm_exec_errorcode = g.pdm_start_error;
223 sprintf( buf, PDMD_MSG_6, g.prog_name );
224 rec->pdm_exec_errormessage = xpstrdup( buf );
229 if ( XmbTextPropertyToTextList( selection_display, &text_prop,
230 &list, &list_cnt ) < 0 ) {
234 rec->pdm_exec_errorcode = g.pdm_start_error;
236 sprintf( buf, PDMD_MSG_7, g.prog_name );
237 rec->pdm_exec_errormessage = xpstrdup( buf );
243 * Fill in the PDM_MANAGER portion of the client record.
245 rec->video_display_str = xpstrdup( list[0] );
246 rec->video_window = strtol(list[1], (char **)NULL, 16);
247 rec->print_display_str = xpstrdup( list[2] );
248 rec->print_window = strtol(list[3], (char **)NULL, 16);
249 #if 0 && defined(PRINTING_SUPPORTED)
250 rec->print_context = strtol(list[4], (char **)NULL, 16);
251 #endif /* PRINTING_SUPPORTED */
252 rec->locale_hint = xpstrdup( list[5] );
253 XFreeStringList( list );
255 rec->selection_display = selection_display;
256 rec->requestor = requestor;
257 rec->prop_atom = prop_atom;
258 rec->selection = report->xselectionrequest.selection;
259 rec->time = report->xselectionrequest.time;
261 rec->mgr_flag = True; /* mgr portion of rec now valid */
264 * Optimization. The only live display connection, for which we
265 * need to trap XIO errors, is "selection display". For the
266 * "video" and "print" displays, we have the display strings and
267 * can establish connections as we need them. Since they are rarely
268 * used, and opening them up here would create XIO liability problems
269 * and a startup performance hit, we won't establish connections now.
271 * One optimization however is to see if the "print" display would
272 * just happen to be the same at the "selection display" currently
275 if ( !strcmp( XDisplayString(rec->selection_display),
276 rec->print_display_str ) ) {
277 rec->seldpy_as_printdpy = True;
280 rec->seldpy_as_printdpy = False;
282 #ifdef OPTIONAL_PXAUTH_PRETEST
284 * Verify connectability to the Print Server.
286 * Note: once beyond the selection phase, all communication
287 * will be by way of the Print Server. If we cannot
288 * connect later, then we will have no way to deliver
289 * EXIT_PXAUTH, EXIT_VXAUTH, EXIT_ERROR, EXIT_OK or
290 * EXIT_CANCEL. Real bad news!
292 * It is better to discover now that we don't have
293 * connection authorization for the print display since
294 * we can still let the user know of PXAUTH problems
295 * via the selection display currently open.
297 * Unfortunately, this pre-test is a performance hit in the
300 if ( ! (testdpy = XOpenDisplay(rec->print_display_str)) ) {
301 rec->pdm_exec_errorcode = g.pdm_start_pxauth;
304 XCloseDisplay( testdpy );
305 #endif /* OPTIONAL_PXAUTH_PRETEST */
308 #ifdef OPTIONAL_VXAUTH_PRETEST
310 * Verify connectability to the Video Server.
312 * It is better to discover now that we don't have
313 * connection authorization for the video display since
314 * we can still let the user know of VXAUTH problems
315 * via the selection display currently open.
317 * Unfortunately, this pre-test is a performance hit in the
320 if ( ! (testdpy = XOpenDisplay(rec->video_display_str)) ) {
321 rec->pdm_exec_errorcode = g.pdm_start_vxauth;
324 XCloseDisplay( testdpy );
325 #endif /* OPTIONAL_VXAUTH_PRETEST */
328 /********************************************************************
330 * fork/exec a child pdm after setting up a message pipe.
332 void mgr_launch_pdm( XpPdmServiceRec *rec )
335 struct sigaction svec;
345 * Setup message pipe.
347 if ( pipe(rec->message_pipe) == -1 ) {
348 rec->pdm_exec_errorcode = g.pdm_start_error;
349 sprintf( buf, PDMD_MSG_8, g.prog_name );
350 rec->pdm_exec_errormessage = xpstrdup( buf );
354 rec->message_xtid = XtAppAddInput( g.context, rec->message_pipe[0],
355 (XtPointer) XtInputReadMask,
356 message_pipe_handler, (XtPointer) NULL );
359 * See if a cookie file is needed.
361 if (rec->cookie_cnt) {
363 * Create new .Xauthority file.
365 original_umask = umask (0077); /* disallow non-owner access */
366 tmpnam( rec->auth_filename );
367 rec->auth_file = fopen( rec->auth_filename, "w" );
369 if (rec->auth_file) {
371 * Copy existing .Xauthority entries.
373 existing_name = XauFileName ();
376 if (access (existing_name, R_OK) == 0) { /* checks REAL id */
377 existing_file = fopen (existing_name, "r");
380 entry = XauReadAuth (existing_file);
384 XauWriteAuth( rec->auth_file, entry );
385 XauDisposeAuth (entry);
387 fclose (existing_file);
393 * Merge in cookies recently sent.
395 for ( i = 0; i < rec->cookie_cnt; i++ ) {
396 XauWriteAuth( rec->auth_file, rec->cookies[i] );
399 fclose( rec->auth_file );
401 original_umask = umask (original_umask);
407 if ( rec->pid < 0 ) {
408 rec->pdm_exec_errorcode = g.pdm_start_error;
409 sprintf( buf, PDMD_MSG_9, g.prog_name );
410 rec->pdm_exec_errormessage = xpstrdup( buf );
413 else if ( rec->pid == 0) {
419 * Hook stderr back to parent via message pipe.
421 dup2(rec->message_pipe[1], 2);
422 close(rec->message_pipe[0]);
425 * The child should have default behavior for all signals.
427 sigemptyset(&svec.sa_mask);
429 svec.sa_handler = SIG_DFL;
430 (void) sigaction(SIGCHLD, &svec, (struct sigaction *) NULL);
432 for (i=3; i < FOPEN_MAX; i++) {
433 if ((i != rec->message_pipe[1]) &&
434 (rec->auth_file && (i != fileno(rec->auth_file))))
436 (void) fcntl (i, F_SETFD, 1);
441 * Set the new locale for the child.
443 * note: the locale hint will be of the form:
445 * name_spec[;registry_spec[;ver_spec[;encoding_spec]]]
447 * for now, just pull out the name_spec (e.g. 'C')
448 * and use it. With a little work, a more complex
449 * syntax could be understood and the appropriate
450 * actions taken here rather than just wedging
451 * name_spec into setlocale() and hoping.
453 if ( !(rec->locale_hint) ) {
455 * Leave current locale alone.
458 else if ( strcmp( rec->locale_hint, "" ) ) {
460 * Leave current locale alone. Note that "" into
461 * setlocale says to go with default vs leave it alone.
467 tptr1 = xpstrdup( rec->locale_hint );
468 tptr2 = strchr( tptr1, ';' );
469 if (tptr2) *tptr2 = '\0';
471 setlocale( LC_ALL, tptr1 );
476 * Set XAUTHORITY env var if needed.
478 if ((rec->cookie_cnt) && (rec->auth_file)) {
479 envstr = Xmalloc( strlen(rec->auth_filename) + 12 );
480 sprintf( envstr, "XAUTHORITY=%s", rec->auth_filename );
485 * Start the child for real.
487 (void) execvp(rec->pdm_exec_argvs[0], rec->pdm_exec_argvs);
489 (void) fprintf (stderr, PDMD_MSG_10, g.prog_name, rec->pdm_exec_argvs[0]);
492 * tomg - need to deal with failed child start.
494 exit(PDM_EXIT_ERROR);
502 * Close the write end of the pipe - only the child needs it.
504 close(rec->message_pipe[1]);
505 rec->message_pipe[1] = -1;
510 /********************************************************************
512 * Figure out which pdm executable to later fork/exec.
514 void mgr_fetch_pdm( XpPdmServiceRec *rec )
516 char tstr[1024], *tptr1 = NULL, *tptr2, *tptr3;
522 if ( g.override_pdm ) {
524 * Override all defaults and other possible settings.
526 tptr1 = xpstrdup(g.override_pdm);
530 * See if the print context says which pdm to run.
532 g.xerrno = 0; /* Error Handler */
533 lxerrno = 0; /* XIO Error Handler */
535 if ( setjmp( xio_quickie_jmp_buf ) == 0 ) {
536 XSetIOErrorHandler( xio_quickie_handler );
538 #if 0 && defined(PRINTING_SUPPORTED)
539 if ( rec->seldpy_as_printdpy ) {
540 tptr1 = XpGetOneAttribute( rec->selection_display,
542 XPPrinterAttr, "dt-pdm-command" );
545 tdpy = XOpenDisplay( rec->print_display_str );
547 tptr1 = XpGetOneAttribute( tdpy,
549 XPPrinterAttr, "dt-pdm-command" );
550 XCloseDisplay( tdpy );
553 #endif /* PRINTING_SUPPORTED */
555 XSetIOErrorHandler( NULL );
560 XSetIOErrorHandler( NULL );
564 * See if we got a useful pdm exec string. Use
567 if ( g.xerrno || lxerrno ) {
568 rec->pdm_exec_errorcode = g.pdm_start_error;
572 tptr1 = xpstrdup(g.default_pdm);
574 else if (!tptr1[0]) {
575 tptr1 = xpstrdup(g.default_pdm);
580 * Convert pdm-command into argv[] style array.
582 * Note: this parsing code does NOT respect shell
583 * quotes and other items. --tomg
585 rec->pdm_exec_argvs = (char **) NULL;
587 tptr2 = tptr1; /* retain orig pointer for freeing */
592 tptr3 = xpstrtok( tptr2, " \n\t" );
597 * There were NO useful tokens to begin with, so
598 * we'll have to fall back on the default.
600 xp_add_argv( &(rec->pdm_exec_argvs), xpstrdup( g.default_pdm ));
605 tptr3 = xpstrtok( (char *) NULL, " \n\t" );
609 xp_add_argv( &(rec->pdm_exec_argvs), xpstrdup( tptr3 ) );
618 * Add standard command line parameters.
620 xp_add_argv( &(rec->pdm_exec_argvs), xpstrdup("-display") );
621 xp_add_argv( &(rec->pdm_exec_argvs), xpstrdup(rec->video_display_str) );
623 xp_add_argv( &(rec->pdm_exec_argvs), xpstrdup("-window") );
624 sprintf( tstr, "0x%lx", rec->video_window );
625 xp_add_argv( &(rec->pdm_exec_argvs), xpstrdup(tstr) );
627 xp_add_argv( &(rec->pdm_exec_argvs), xpstrdup("-pdisplay") );
628 xp_add_argv( &(rec->pdm_exec_argvs), xpstrdup(rec->print_display_str) );
630 xp_add_argv( &(rec->pdm_exec_argvs), xpstrdup("-pwindow") );
631 sprintf( tstr, "0x%lx", rec->print_window );
632 xp_add_argv( &(rec->pdm_exec_argvs), xpstrdup(tstr) );
634 #if 0 && defined(PRINTING_SUPPORTED)
635 xp_add_argv( &(rec->pdm_exec_argvs), xpstrdup("-pcontext") );
636 sprintf( tstr, "0x%lx", rec->print_context );
637 xp_add_argv( &(rec->pdm_exec_argvs), xpstrdup(tstr) );
638 #endif /* PRINTING_SUPPORTED */
641 /********************************************************************
643 * Once a pdm has been lauched, reply to the SelectionRequest so that
644 * the requestors knows.
646 void mgr_launch_reply( XpPdmServiceRec *rec )
657 XChangeProperty( rec->selection_display, rec->requestor,
658 rec->prop_atom, XA_ATOM,
660 (unsigned char *) &(rec->pdm_exec_errorcode),
664 * Write optional error message to log file.
666 * Expected errors like PXAUTH and VXAUTH should not have
667 * textual descriptions too - they're obvious as is. Only
668 * real nasty errors should have a message for the log file.
670 if ((rec->pdm_exec_errormessage) && (g.log_file)) {
671 if ((errlog = fopen(g.log_file, "a+")) != NULL) {
672 now = time((time_t)0);
674 if ( rec->pdm_exec_errorcode == g.pdm_start_ok )
675 eec = "PDM_START_OK";
676 else if ( rec->pdm_exec_errorcode == g.pdm_start_vxauth )
677 eec = "PDM_START_VXAUTH";
678 else if ( rec->pdm_exec_errorcode == g.pdm_start_pxauth )
679 eec = "PDM_START_PXAUTH";
680 else if ( rec->pdm_exec_errorcode == g.pdm_start_error )
681 eec = "PDM_START_ERROR";
683 eec = "unknown error";
685 fprintf( errlog, PDMD_MSG_11, g.prog_name, ctime(&now),
686 rec->pdm_exec_errormessage,
688 rec->pdm_exec_argvs[0],
689 rec->print_display_str,
690 rec->video_display_str );
698 * Send a SelectionNotify event, which will conclude the
699 * selection handshake.
701 reply.xselection.type = SelectionNotify;
702 reply.xselection.requestor = rec->requestor;
703 reply.xselection.selection = rec->selection;
704 reply.xselection.target = g.pdm_start;
705 reply.xselection.property = rec->prop_atom;
706 reply.xselection.time = rec->time;
708 status = XSendEvent( rec->selection_display, rec->requestor, True, 0, &reply );
711 /********************************************************************
713 * Send the final OK/CANCEL ClientMessage.
715 void mgr_shutdown_reply( XpPdmServiceRec *rec )
726 * Setup client message event.
728 cme.xclient.type = ClientMessage;
729 cme.xclient.window = rec->print_window;
730 cme.xclient.message_type = g.pdm_reply;
731 cme.xclient.format = 32;
734 * Look at the current exit code. Let 'mess' be both a message
735 * string, and a string we would XInternAtom on if we find out
736 * that the "Print" X-Server is differnent than the "Selection"
737 * X-Server. Up till now, we've been using atom constants
738 * generated via the Selection X-Server.
740 switch (rec->exit_code) {
742 cme.xclient.data.l[0] = (long) g.pdm_exit_ok;
743 mess = "PDM_EXIT_OK";
745 case PDM_EXIT_CANCEL:
746 cme.xclient.data.l[0] = (long) g.pdm_exit_cancel;
747 mess = "PDM_EXIT_CANCEL";
749 case PDM_EXIT_VXAUTH:
750 cme.xclient.data.l[0] = (long) g.pdm_exit_vxauth;
751 mess = "PDM_EXIT_VXAUTH";
753 case PDM_EXIT_PXAUTH:
754 cme.xclient.data.l[0] = (long) g.pdm_exit_pxauth;
755 mess = "PDM_EXIT_PXAUTH";
759 cme.xclient.data.l[0] = (long) g.pdm_exit_error;
760 mess = "PDM_EXIT_ERROR";
765 * Try to send ClientMessage that will carry the reply.
768 g.xerrno = 0; /* Error Handler */
769 lxerrno = 0; /* XIO Error Handler */
771 if ( setjmp( xio_quickie_jmp_buf ) == 0 ) {
772 XSetIOErrorHandler( xio_quickie_handler );
774 if ( rec->seldpy_as_printdpy ) {
776 * Since the "Print" X-Server is the same as the
777 * "Selection" X-Server, against which we have an
778 * active display connection and atom values, go
781 XSendEvent( rec->selection_display, rec->print_window, False, 0L, &cme );
784 pdpy = XOpenDisplay( rec->print_display_str );
787 * The "Print" X-Server is different than the
788 * "Selection" X-Server. Map values over.
790 cme.xclient.message_type =
791 XInternAtom( pdpy, "PDM_REPLY", False );
792 cme.xclient.data.l[0] =
793 (long) XInternAtom( pdpy, mess, False );
794 XSendEvent( pdpy, rec->print_window, False, 0L, &cme );
795 XCloseDisplay( pdpy );
799 XSetIOErrorHandler( NULL );
804 XSetIOErrorHandler( NULL );
807 if ( g.xerrno || lxerrno ) {
809 * Problem - we can't get back to the requestor.
811 * This is really a PANIC situation, since the requesting
812 * client will hang around forever, waiting for this
813 * final reply. The best we can do it log an error for
814 * the sys admin, and hope they can notice.
817 sprintf( buf, PDMD_MSG_12, g.prog_name, mess );
819 sprintf( buf, PDMD_MSG_13, g.prog_name, mess );
821 rec->message_string2 = Xmalloc( strlen( buf ) + 1 );
822 strcpy( rec->message_string2 , buf );
827 Bool has_exec_token( XpPdmServiceRec *rec )
832 s1 = rec->message_string;
836 * Look for "PDM_START_*" tokens burried in the
837 * stderr output of the PDM. If found, react as
838 * required, and eliminate the token from the
841 if ( s2 = strstr( s1, "PDM_START_OK") ) {
842 rec->pdm_exec_errorcode = g.pdm_start_ok;
845 else if ( s2 = strstr( s1, "PDM_START_VXAUTH") ) {
846 rec->pdm_exec_errorcode = g.pdm_start_vxauth;
849 else if ( s2 = strstr( s1, "PDM_START_PXAUTH") ) {
850 rec->pdm_exec_errorcode = g.pdm_start_pxauth;
853 else if ( s2 = strstr( s1, "PDM_START_ERROR") ) {
854 rec->pdm_exec_errorcode = g.pdm_start_error;
860 * Compress out the token.
863 while ( *s2++ = *s3++ );
865 if ( strlen(s1) == 0 ) {
867 * The token was it - free the buffer now so that
868 * it appears no stderr was ever generated.
870 Xfree( rec->message_string );
871 rec->message_string = (char *) NULL;
873 else if ((strlen(s1) == 1) && (s1[0] == '\n')) {
875 * All but a \n remains - free the buffer.
877 Xfree( rec->message_string );
878 rec->message_string = (char *) NULL;
892 /********************************************************************
894 * Search through all the child tracking records and see if
895 * any can be shutdown.
897 void mgr_shutdown_scan(void)
902 static int errlog_problem_notice = 0;
905 for ( i = 0; i < g.serviceRecNum; i++ ) {
907 shutdown_time = False;
909 if ( (g.serviceRecs[i]->do_launch_reply) &&
910 (has_exec_token(g.serviceRecs[i]) ) ) {
912 * Need to send our PDM_START fork/exec reply still.
914 * Note that if we send pdm_start_ok, we'll need to send
915 * a pdm_exit_* code later. If we send pdm_start_vxauth,
916 * pdm_start_pxauth, or pdm_start_error, it indicates to
917 * the client a fatal condition, and they will NOT receive
918 * any more messages, including any pdm_exit_* codes. The
919 * dtpdmd will hang around however, if for no other reason
920 * than to capture stderr and log it when the child finally
923 mgr_launch_reply( g.serviceRecs[i] );
924 g.serviceRecs[i]->do_launch_reply = False; /* it has been done */
927 if ( (g.serviceRecs[i]->pdm_exec_errorcode == g.pdm_start_ok) &&
928 (g.serviceRecs[i]->exit_received) &&
929 (g.serviceRecs[i]->message_pipe[0] == -1) ) {
931 * Child is down, and since we sent a pdm_start_ok,
932 * we need to send a PDM_REPLY pdm_exit_* code too.
934 mgr_shutdown_reply( g.serviceRecs[i] );
937 if ( (g.serviceRecs[i]->exit_received) &&
938 (g.serviceRecs[i]->message_pipe[0] == -1) ) {
940 * Child is down. Log any final error messages
941 * from the child, and from the dtpdmd on behalf of
944 if ( ((g.serviceRecs[i]->message_string) ||
945 (g.serviceRecs[i]->message_string2)) && (g.log_file) ) {
946 if ((errlog = fopen(g.log_file, "a+")) != NULL) {
947 now = time((time_t)0);
949 fprintf (errlog, PDMD_MSG_14, g.prog_name, ctime(&now),
950 g.serviceRecs[i]->pdm_exec_argvs[0],
951 g.serviceRecs[i]->print_display_str,
952 g.serviceRecs[i]->video_display_str,
953 g.serviceRecs[i]->exit_code,
954 g.serviceRecs[i]->message_string);
956 if (g.serviceRecs[i]->message_string2) {
957 fprintf (errlog, PDMD_MSG_15, g.serviceRecs[i]->message_string2);
960 fprintf (errlog, "\n");
963 else if (!errlog_problem_notice) {
964 fprintf (stderr, PDMD_MSG_16, g.prog_name, g.log_file );
966 errlog_problem_notice = 1;
971 * The child is done and can be cleaned up.
973 delete_rec( g.serviceRecs[i] );