* New version 2.26
[alpine.git] / pith / osdep / pipe.c
blobc1b4647662ca556c9313ea42d87ef7bb9a877d26
1 /*
2 * ========================================================================
3 * Copyright 2013-2022 Eduardo Chappa
4 * Copyright 2006-2008 University of Washington
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * ========================================================================
15 #include <system.h>
17 #include "err_desc.h"
19 #include "canaccess.h"
20 #include "temp_nam.h"
21 #include "forkwait.h"
22 #include "pipe.h"
23 #include "../charconv/utf8.h"
24 #include "../charconv/filesys.h"
25 #include "../debug.h"
27 #ifdef _WINDOWS
28 #include "../../pico/osdep/mswin.h"
29 #endif
33 /*======================================================================
34 pipe
36 Initiate I/O to and from a process. These functions used to be
37 similar to popen and pclose, but both an incoming stream and an
38 output file are provided.
40 ====*/
45 * Global's to helpsignal handler tell us child's status has changed...
47 static pid_t child_pid;
51 * Internal Protos
53 void zot_pipe(PIPE_S **);
54 #ifdef _WINDOWS
55 int pipe_mswin_exec_wrapper(char *, PIPE_S *, unsigned,
56 void (*)(PIPE_S *, int, void *),
57 void (*)(char *));
58 #else /* UNIX */
59 char *pipe_error_msg(char *, char *, char *);
60 RETSIGTYPE pipe_alarm(int);
61 #endif /* UNIX */
66 /*----------------------------------------------------------------------
67 Spawn a child process and optionally connect read/write pipes to it
69 Args: command -- string to hand the shell
70 outfile -- address of pointer containing file to receive output
71 errfile -- address of pointer containing file to receive error output
72 mode -- mode for type of shell, signal protection etc...
73 Returns: pointer to alloc'd PIPE_S on success, NULL otherwise
75 The outfile is either NULL, a pointer to a NULL value, or a pointer
76 to the requested name for the output file. In the pointer-to-NULL case
77 the caller doesn't care about the name, but wants to see the pipe's
78 results so we make one up. It's up to the caller to make sure the
79 free storage containing the name is cleaned up.
81 Mode bits serve several purposes.
82 PIPE_WRITE tells us we need to open a pipe to write the child's
83 stdin.
84 PIPE_READ tells us we need to open a pipe to read from the child's
85 stdout/stderr. *NOTE* Having neither of the above set means
86 we're not setting up any pipes, just forking the child and exec'ing
87 the command. Also, this takes precedence over any named outfile.
88 PIPE_STDERR means we're to tie the child's stderr to the same place
89 stdout is going. *NOTE* This only makes sense then if PIPE_READ
90 or an outfile is provided. Also, this takes precedence over any
91 named errfile.
92 PIPE_RESET means we reset the terminal mode to what it was before
93 we started pine and then exec the command. In PC-Pine, _RESET
94 was a shortcut for just executing a command. We'll try to pay
95 attention to the above flags to make sure we do the right thing.
96 PIPE_PROT means to protect the child from the usual nasty signals
97 that might cause premature death. Otherwise, the default signals are
98 set so the child can deal with the nasty signals in its own way.
99 NOT USED UNDER WINDOWS
100 PIPE_NOSHELL means we're to exec the command without the aid of
101 a system shell. *NOTE* This negates the affect of PIPE_USER.
102 NOT USED UNDER WINDOWS
103 PIPE_USER means we're to try executing the command in the user's
104 shell. Right now we only look in the environment, but that may get
105 more sophisticated later.
106 NOT USED UNDER WINDOWS
107 PIPE_RUNNOW was added for WINDOWS for the case pipe is called to run
108 a shell program (like for url viewing). This is the only option
109 where we don't wait for child termination, and is only obeyed if
110 PIPE_WRITE and PIPE_READ aren't set
111 ----*/
112 PIPE_S *
113 open_system_pipe(char *command, char **outfile, char **errfile, int mode,
114 int timeout, void (*pipecb_f)(PIPE_S *, int, void *),
115 void (*piperr_f)(char *))
117 PIPE_S *syspipe = NULL;
118 #ifdef _WINDOWS
119 int exit_code = 0;
120 char cmdbuf[1024];
121 unsigned flags = 0;
122 #else
123 char shellpath[MAXPATH+1], *shell;
124 int p[2], oparentd = -1, ochildd = -1, iparentd = -1, ichildd = -1;
125 #endif
127 #ifdef _WINDOWS
128 if(mode & PIPE_STDERR)
129 flags |= MSWIN_EAW_CAPT_STDERR;
131 * It'll be a lot more difficult to support READing and WRITing.
132 * This was never supported, and there don't look to be any cases
133 * that set both of these flags anymore for win32.
135 * errfile could probably be supported pretty easily
138 if(errfile){
139 if(piperr_f)
140 (*piperr_f)("Pipe arg not yet supported: Error File");
142 return(NULL);
146 if((mode & PIPE_RUNNOW)
147 && !(mode & (PIPE_WRITE | PIPE_READ | PIPE_STDERR))){
148 if(mswin_shell_exec(command, NULL) == 0
149 && (syspipe = (PIPE_S *) malloc(sizeof(PIPE_S))) != NULL){
150 memset(syspipe, 0, sizeof(PIPE_S));
151 return(syspipe);
154 return(NULL);
157 strncpy(cmdbuf, command, sizeof(cmdbuf));
158 cmdbuf[sizeof(cmdbuf)-1] = '\0';
160 if((syspipe = (PIPE_S *) malloc(sizeof(PIPE_S))) == NULL)
161 return(NULL);
163 memset(syspipe, 0, sizeof(PIPE_S));
164 syspipe->mode = mode;
165 if(!outfile){
166 syspipe->deloutfile = 1;
167 if(mode & PIPE_READ){
168 syspipe->outfile = temp_nam(NULL, "po");
169 our_unlink(syspipe->outfile);
172 else{
173 if(!*outfile) /* asked for, but not named? */
174 *outfile = temp_nam(NULL, "po");
176 our_unlink(*outfile);
177 syspipe->outfile = (char *) malloc((strlen(*outfile)+1)*sizeof(char));
178 snprintf(syspipe->outfile, strlen(*outfile)+1, "%s", *outfile);
181 if(mode & PIPE_WRITE){
183 * Create tmp file to write, spawn child in close_pipe
184 * after tmp file's written...
186 syspipe->infile = temp_nam(NULL, "pw");
187 syspipe->out.f = our_fopen(syspipe->infile, "wb");
188 syspipe->command = (char *) malloc((strlen(cmdbuf)+1)*sizeof(char));
189 snprintf(syspipe->command, strlen(cmdbuf)+1, "%s", cmdbuf);
190 dprint((1, "pipe write: %s", cmdbuf));
192 else if(mode & PIPE_READ){
194 * Create a tmp file for command result, exec the command
195 * here into temp file, and return file pointer to it...
197 syspipe->command = (char *) malloc((strlen(cmdbuf)+1)*sizeof(char));
198 snprintf(syspipe->command, strlen(cmdbuf)+1, "%s", cmdbuf);
199 dprint((1, "pipe read: %s", cmdbuf));
200 if(pipe_mswin_exec_wrapper("pipe command", syspipe,
201 flags, pipecb_f, piperr_f)){
202 if(syspipe->outfile){
203 free((void *) syspipe->outfile);
204 syspipe->outfile = NULL;
207 zot_pipe(&syspipe);
209 else{
210 syspipe->in.f = our_fopen(syspipe->outfile, "rb");
211 syspipe->exit_code = exit_code;
214 else{
215 /* we just run the command taking outfile into account */
216 syspipe->command = (char *) malloc((strlen(cmdbuf)+1)*sizeof(char));
217 snprintf(syspipe->command, strlen(cmdbuf)+1, "%s", cmdbuf);
218 if(pipe_mswin_exec_wrapper("pipe command", syspipe,
219 flags, pipecb_f, piperr_f)){
220 if(syspipe->outfile){
221 free((void *) syspipe->outfile);
222 syspipe->outfile = NULL;
225 zot_pipe(&syspipe);
227 else
228 syspipe->exit_code = exit_code;
231 #else /* !_WINDOWS */
233 if((syspipe = (PIPE_S *) malloc(sizeof(PIPE_S))) == NULL)
234 return(NULL);
236 memset(syspipe, 0, sizeof(PIPE_S));
238 syspipe->mode = mode;
241 * If we're not using the shell's command parsing smarts, build
242 * argv by hand...
244 if(mode & PIPE_NOSHELL){
245 char **ap, *p;
246 size_t n;
248 /* parse the arguments into argv */
249 for(p = command; *p && isspace((unsigned char)(*p)); p++)
250 ; /* swallow leading ws */
252 if(*p){
253 int l = strlen(p);
255 if((syspipe->args = (char *) malloc((l + 1) * sizeof(char))) != NULL){
256 strncpy(syspipe->args, p, l);
257 syspipe->args[l] = '\0';
259 else{
260 if(piperr_f)
261 (*piperr_f)(pipe_error_msg("<null>", "execute",
262 "Can't allocate command string"));
263 zot_pipe(&syspipe);
264 return(NULL);
267 else{
268 if(piperr_f)
269 (*piperr_f)(pipe_error_msg("<null>", "execute",
270 "No command name found"));
271 zot_pipe(&syspipe);
272 return(NULL);
275 for(p = syspipe->args, n = 2; *p; p++) /* count the args */
276 if(isspace((unsigned char)(*p))
277 && *(p+1) && !isspace((unsigned char)(*(p+1))))
278 n++;
280 if ((syspipe->argv = ap = (char **)malloc(n * sizeof(char *))) == NULL){
281 zot_pipe(&syspipe);
282 return(NULL);
285 memset(syspipe->argv, 0, n * sizeof(char *));
287 for(p = syspipe->args; *p; ){ /* collect args */
288 while(*p && isspace((unsigned char)(*p)))
289 *p++ = '\0';
291 *ap++ = (*p) ? p : NULL;
292 while(*p && !isspace((unsigned char)(*p)))
293 p++;
296 /* make sure argv[0] exists in $PATH */
297 if(can_access_in_path(getenv("PATH"), syspipe->argv[0],
298 EXECUTE_ACCESS) < 0){
299 if(piperr_f)
300 (*piperr_f)(pipe_error_msg(syspipe->argv[0], "access",
301 error_description(errno)));
302 zot_pipe(&syspipe);
303 return(NULL);
307 /* fill in any output filenames */
308 if(!(mode & PIPE_READ)){
309 if(outfile && !*outfile)
310 *outfile = temp_nam(NULL, "pine_p"); /* asked for, but not named? */
312 if(errfile && !*errfile)
313 *errfile = temp_nam(NULL, "pine_p"); /* ditto */
316 /* create pipes */
317 if(mode & (PIPE_WRITE | PIPE_READ)){
318 if(mode & PIPE_WRITE){
319 pipe(p); /* alloc pipe to write child */
320 oparentd = p[STDOUT_FILENO];
321 ichildd = p[STDIN_FILENO];
324 if(mode & PIPE_READ){
325 pipe(p); /* alloc pipe to read child */
326 iparentd = p[STDIN_FILENO];
327 ochildd = p[STDOUT_FILENO];
331 if(pipecb_f) /* let caller prep display */
332 (*pipecb_f)(syspipe, OSB_PRE_OPEN, NULL);
335 if((syspipe->pid = vfork()) == 0){
336 /* reset child's handlers in requested fashion... */
337 (void)signal(SIGINT, (mode & PIPE_PROT) ? SIG_IGN : SIG_DFL);
338 (void)signal(SIGQUIT, (mode & PIPE_PROT) ? SIG_IGN : SIG_DFL);
339 (void)signal(SIGHUP, (mode & PIPE_PROT) ? SIG_IGN : SIG_DFL);
340 #ifdef SIGCHLD
341 (void) signal(SIGCHLD, SIG_DFL);
342 #endif
344 /* if parent isn't reading, and we have a filename to write */
345 if(!(mode & PIPE_READ) && outfile){ /* connect output to file */
346 int output = our_creat(*outfile, 0600);
347 dup2(output, STDOUT_FILENO);
348 if(mode & PIPE_STDERR)
349 dup2(output, STDERR_FILENO);
350 else if(errfile)
351 dup2(our_creat(*errfile, 0600), STDERR_FILENO);
354 if(mode & PIPE_WRITE){ /* connect process input */
355 close(oparentd);
356 dup2(ichildd, STDIN_FILENO); /* tie stdin to pipe */
357 close(ichildd);
360 if(mode & PIPE_READ){ /* connect process output */
361 close(iparentd);
362 dup2(ochildd, STDOUT_FILENO); /* tie std{out,err} to pipe */
363 if(mode & PIPE_STDERR)
364 dup2(ochildd, STDERR_FILENO);
365 else if(errfile)
366 dup2(our_creat(*errfile, 0600), STDERR_FILENO);
368 close(ochildd);
371 if(mode & PIPE_NOSHELL){
372 execvp(syspipe->argv[0], syspipe->argv);
374 else{
375 if(mode & PIPE_USER){
376 char *env, *sh;
377 if((env = getenv("SHELL")) && (sh = strrchr(env, '/'))){
378 shell = sh + 1;
379 strncpy(shellpath, env, sizeof(shellpath)-1);
380 shellpath[sizeof(shellpath)-1] = '\0';
382 else{
383 shell = "csh";
384 strncpy(shellpath, "/bin/csh", sizeof(shellpath)-1);
385 shellpath[sizeof(shellpath)-1] = '\0';
388 else{
389 shell = "sh";
390 strncpy(shellpath, "/bin/sh", sizeof(shellpath)-1);
391 shellpath[sizeof(shellpath)-1] = '\0';
394 execl(shellpath, shell, command ? "-c" : (char *)NULL, fname_to_locale(command), (char *)NULL);
397 fprintf(stderr, "Can't exec %s\nReason: %s",
398 command, error_description(errno));
399 _exit(-1);
402 if((child_pid = syspipe->pid) > 0){
403 syspipe->isig = signal(SIGINT, SIG_IGN); /* Reset handlers to make */
404 syspipe->qsig = signal(SIGQUIT, SIG_IGN); /* sure we don't come to */
405 syspipe->hsig = signal(SIGHUP, SIG_IGN); /* a premature end... */
406 if((syspipe->timeout = timeout) != 0){
407 syspipe->alrm = signal(SIGALRM, pipe_alarm);
408 syspipe->old_timeo = alarm(timeout);
411 if(mode & PIPE_WRITE){
412 close(ichildd);
413 if(mode & PIPE_DESC)
414 syspipe->out.d = oparentd;
415 else
416 syspipe->out.f = fdopen(oparentd, "w");
419 if(mode & PIPE_READ){
420 close(ochildd);
421 if(mode & PIPE_DESC)
422 syspipe->in.d = iparentd;
423 else
424 syspipe->in.f = fdopen(iparentd, "r");
427 else{
428 if(mode & (PIPE_WRITE | PIPE_READ)){
429 if(mode & PIPE_WRITE){
430 close(oparentd);
431 close(ichildd);
434 if(mode & PIPE_READ){
435 close(iparentd);
436 close(ochildd);
440 if(pipecb_f) /* let caller fixup display */
441 (*pipecb_f)(syspipe, OSB_POST_OPEN, NULL);
443 if(outfile && *outfile){
444 our_unlink(*outfile);
445 free((void *) *outfile);
446 *outfile = NULL;
449 if(errfile && *errfile){
450 our_unlink(*errfile);
451 free((void *) *errfile);
452 *errfile = NULL;
455 if(piperr_f)
456 (*piperr_f)(pipe_error_msg(command, "fork",
457 error_description(errno)));
458 zot_pipe(&syspipe);
461 #endif /* UNIX */
463 return(syspipe);
468 #ifndef _WINDOWS
469 /*----------------------------------------------------------------------
470 Return appropriate error message
472 Args: cmd -- command we were trying to exec
473 op -- operation leading up to the exec
474 res -- result of that operation
476 ----*/
477 char *
478 pipe_error_msg(char *cmd, char *op, char *res)
480 static char ebuf[512 + 16 + 1];
482 snprintf(ebuf, sizeof(ebuf), "Pipe can't %.256s \"%.32sb\": %.223s",
483 op ? op : "?", cmd ? cmd : "?", res ? res : "?");
484 ebuf[sizeof(ebuf) - 1] = '\0';
486 return(ebuf);
488 #endif /* !_WINDOWS */
491 /*----------------------------------------------------------------------
492 Free resources associated with the given pipe struct
494 Args: syspipe -- address of pointer to struct to clean up
496 ----*/
497 void
498 zot_pipe(PIPE_S **syspipe)
500 if((*syspipe)->args){
501 free((void *) (*syspipe)->args);
502 (*syspipe)->args = NULL;
505 if((*syspipe)->argv){
506 free((void *) (*syspipe)->argv);
507 (*syspipe)->argv = NULL;
510 if((*syspipe)->tmp){
511 free((void *) (*syspipe)->tmp);
512 (*syspipe)->tmp = NULL;
515 #ifdef _WINDOWS
517 if((*syspipe)->outfile){
518 free((void *) (*syspipe)->outfile);
519 (*syspipe)->outfile = NULL;
522 if((*syspipe)->command){
523 free((void *) (*syspipe)->command);
524 (*syspipe)->command = NULL;
527 #endif /* _WINDOWS */
529 free((void *) *syspipe);
530 *syspipe = NULL;
536 * Returns: 0 if all went well, -1 otherwise
539 pipe_close_write(PIPE_S *syspipe)
541 int rv = 0;
543 if(!syspipe || !syspipe->out.f)
544 return -1;
546 #ifdef _WINDOWS
549 unsigned flags = 0;
551 if(syspipe->mode & PIPE_STDERR)
552 flags |= MSWIN_EAW_CAPT_STDERR;
554 rv = fclose(syspipe->out.f);
555 syspipe->out.f = NULL;
556 if(syspipe->mode & PIPE_WRITE){
558 * PIPE_WRITE should always be set if we're trying to close
559 * the write end.
560 * PIPE_WRITE can't start process till now, all the others
561 * will have already run
563 if(pipe_mswin_exec_wrapper("pipe command", syspipe,
564 flags, NULL, NULL))
565 /* some horrible error just occurred */
566 rv = -1;
567 else
568 syspipe->in.f = our_fopen(syspipe->outfile, "rb");
570 else
571 rv = -1;
574 #else /* UNIX */
576 rv = fclose(syspipe->out.f) ? -1 : 0;
577 syspipe->out.f = NULL;
579 #endif
580 return(rv);
585 /*----------------------------------------------------------------------
586 Close pipe previously allocated and wait for child's death
588 Args: syspipe -- address of pointer to struct returned by open_system_pipe
589 exitval -- return exit status here.
591 Returns:
592 Two modes of return values for backcompat.
593 If exitval == NULL
594 returns exit status of child or -1 if invalid syspipe
595 If exitval != NULL
596 returns -1 if invalid syspipe or 0 if ok. In that case, exitval
597 of child is returned in exitval
598 ----*/
600 close_system_pipe(PIPE_S **syspipe, int *exitval, void (*pipecb_f) (PIPE_S *, int, void *))
602 #ifdef _WINDOWS
603 int rv = 0;
604 unsigned flags = 0;
606 if(!(syspipe && *syspipe))
607 return(-1);
609 if((*syspipe)->mode & PIPE_STDERR)
610 flags |= MSWIN_EAW_CAPT_STDERR;
612 if(((*syspipe)->mode & PIPE_WRITE) && (*syspipe)->out.f){
613 fclose((*syspipe)->out.f);
615 * PIPE_WRITE can't start process till now, all the others
616 * will have already run
618 if(pipe_mswin_exec_wrapper("pipe command", (*syspipe),
619 flags, pipecb_f, NULL))
620 /* some horrible error just occurred */
621 rv = -1;
623 else if((*syspipe)->mode & PIPE_READ)
624 if((*syspipe)->in.f)
625 fclose((*syspipe)->in.f);
627 if(exitval){
628 *exitval = (*syspipe)->exit_code;
629 dprint((5, "Closed pipe: exitval=%d\n", *exitval));
632 if((*syspipe)->infile)
633 our_unlink((*syspipe)->infile);
635 if((*syspipe)->outfile && (*syspipe)->deloutfile)
636 our_unlink((*syspipe)->outfile);
638 if(rv != -1 && !exitval)
639 rv = (*syspipe)->exit_code;
641 zot_pipe(syspipe);
643 #ifdef DEBUG
644 if(!exitval){
645 dprint((5, "Closed pipe: rv=%d\n", rv));
647 #endif /* DEBUG */
649 return(rv);
651 #else /* UNIX */
652 int status;
654 if(!(syspipe && *syspipe))
655 return -1;
657 if(((*syspipe)->mode) & PIPE_WRITE){
658 if(((*syspipe)->mode) & PIPE_DESC){
659 if((*syspipe)->out.d >= 0)
660 close((*syspipe)->out.d);
662 else if((*syspipe)->out.f)
663 fclose((*syspipe)->out.f);
666 if(((*syspipe)->mode) & PIPE_READ){
667 if(((*syspipe)->mode) & PIPE_DESC){
668 if((*syspipe)->in.d >= 0)
669 close((*syspipe)->in.d);
671 else if((*syspipe)->in.f)
672 fclose((*syspipe)->in.f);
675 if(pipecb_f)
676 (*pipecb_f)(*syspipe, OSB_PRE_CLOSE, NULL);
678 /* wait on the child */
679 (void) process_reap((*syspipe)->pid, &status, PR_NONE);
681 /* restore original handlers... */
682 (void) signal(SIGINT, (*syspipe)->isig);
683 (void) signal(SIGHUP, (*syspipe)->hsig);
684 (void) signal(SIGQUIT, (*syspipe)->qsig);
686 if((*syspipe)->timeout){
687 (void)signal(SIGALRM, (*syspipe)->alrm);
688 alarm((*syspipe)->old_timeo);
689 child_pid = 0;
692 if(pipecb_f)
693 (*pipecb_f)(*syspipe, OSB_POST_CLOSE, NULL);
695 zot_pipe(syspipe);
697 if(exitval){
698 *exitval = status;
699 return 0;
701 else{
702 return(status);
704 #endif /* UNIX */
709 * process_reap - manage child demise and return exit status
711 * Args: pid -- id of process to reap
712 * esp -- pointer to exist status
713 * flags -- special reaping considerations
715 * Returns:
716 * < 0 -- if there's a problem
717 * 0 -- if no child to reap
718 * > 0 -- process id of the child
720 pid_t
721 process_reap(pid_t pid, int *esp, int flags)
723 #ifdef _WINDOWS
725 return 0;
727 #else /* UNIX */
728 WAITSTATUS_T wstatus;
729 pid_t rv;
730 int wflags;
732 #if HAVE_WAITPID
734 wflags = 0;
736 #ifdef WNOHANG
737 if(flags & PR_NOHANG)
738 wflags |= WNOHANG;
739 #endif
741 while (((rv = waitpid(pid, &wstatus, wflags)) < 0) && (errno != ECHILD));
743 #elif HAVE_WAIT4
745 wflags = 0;
747 #ifdef WNOHANG
748 if(flags & PR_NOHANG)
749 wflags |= WNOHANG;
750 #endif
752 while (((rv = wait4(pid,&wstatus,wflags,NULL)) < 0) && (errno != ECHILD));
754 #elif HAVE_WAIT
756 while (((rv = wait(&wstatus)) != pid) && ((rv > 0) || (errno != ECHILD)));
758 #else
760 /* BUG: BAIL */
762 #endif
764 if(rv > 0)
765 *esp = (WIFEXITED(wstatus)) ? (int) WEXITSTATUS(wstatus) : -1;
767 return(rv);
768 #endif /* UNIX */
772 #ifndef _WINDOWS
773 RETSIGTYPE
774 pipe_alarm(int sig)
776 if(child_pid)
777 kill(child_pid, SIGINT);
779 #endif /* !_WINDOWS */
782 #ifdef _WINDOWS
784 * Wrapper around mswin_exec_and_wait()
787 pipe_mswin_exec_wrapper(char *whatsit,
788 PIPE_S *syspipe, unsigned flags,
789 void (*pipecb_f)(PIPE_S *, int, void *),
790 void (*piperr_f)(char *))
792 int rv;
794 flags |= MSWIN_EAW_CTRL_C_CANCELS;
796 if(pipecb_f)
797 (*pipecb_f)(syspipe, OSB_PRE_OPEN, NULL);
799 rv = mswin_exec_and_wait(whatsit, syspipe->command,
800 syspipe->infile, syspipe->outfile,
801 &syspipe->exit_code, flags);
803 if(pipecb_f)
804 (*pipecb_f)(syspipe, OSB_POST_OPEN, (void *)rv);
806 return rv;
808 #endif