diff options
author | Marcus Brinkmann <[email protected]> | 2006-09-19 14:01:54 +0000 |
---|---|---|
committer | Marcus Brinkmann <[email protected]> | 2006-09-19 14:01:54 +0000 |
commit | 9e09d93de83fe1160689acb36b082c02ccb52716 (patch) | |
tree | 807126be9dd89fe2850ad748de978de0c5d3b340 /assuan/assuan-pipe-connect.c | |
parent | 2006-07-29 Marcus Brinkmann <[email protected]> (diff) | |
download | gpgme-9e09d93de83fe1160689acb36b082c02ccb52716.tar.gz gpgme-9e09d93de83fe1160689acb36b082c02ccb52716.zip |
assuan/
Update to current version.
2006-09-19 Marcus Brinkmann <[email protected]>
* configure.ac: Turn stpcpy into a replacement function.
Check for unistd.h and add setenv as replacement function.
gpgme/
2006-09-19 Marcus Brinkmann <[email protected]>
* setenv.c: New file.
Diffstat (limited to '')
-rw-r--r-- | assuan/assuan-pipe-connect.c | 702 |
1 files changed, 486 insertions, 216 deletions
diff --git a/assuan/assuan-pipe-connect.c b/assuan/assuan-pipe-connect.c index ecedb166..8ee9c748 100644 --- a/assuan/assuan-pipe-connect.c +++ b/assuan/assuan-pipe-connect.c @@ -1,5 +1,5 @@ /* assuan-pipe-connect.c - Establish a pipe connection (client) - * Copyright (C) 2001, 2002, 2003, 2005 Free Software Foundation, Inc. + * Copyright (C) 2001, 2002, 2003, 2005, 2006 Free Software Foundation, Inc. * * This file is part of Assuan. * @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifdef HAVE_CONFIG_H @@ -38,6 +39,19 @@ #include "assuan-defs.h" +/* Hacks for Slowaris. */ +#ifndef PF_LOCAL +# ifdef PF_UNIX +# define PF_LOCAL PF_UNIX +# else +# define PF_LOCAL AF_UNIX +# endif +#endif +#ifndef AF_LOCAL +# define AF_LOCAL AF_UNIX +#endif + + #ifdef _POSIX_OPEN_MAX #define MAX_OPEN_FDS _POSIX_OPEN_MAX #else @@ -111,6 +125,8 @@ do_finish (assuan_context_t ctx) if (ctx->inbound.fd != -1) { _assuan_close (ctx->inbound.fd); + if (ctx->inbound.fd == ctx->outbound.fd) + ctx->outbound.fd = -1; ctx->inbound.fd = -1; } if (ctx->outbound.fd != -1) @@ -123,7 +139,7 @@ do_finish (assuan_context_t ctx) #ifndef HAVE_W32_SYSTEM #ifndef _ASSUAN_USE_DOUBLE_FORK if (!ctx->flags.no_waitpid) - waitpid (ctx->pid, NULL, 0); + _assuan_waitpid (ctx->pid, NULL, 0); ctx->pid = -1; #endif #endif /*!HAVE_W32_SYSTEM*/ @@ -138,6 +154,402 @@ do_deinit (assuan_context_t ctx) } +/* Helper for pipe_connect. */ +static assuan_error_t +initial_handshake (assuan_context_t *ctx) +{ + int okay, off; + assuan_error_t err; + + err = _assuan_read_from_server (*ctx, &okay, &off); + if (err) + _assuan_log_printf ("can't connect server: %s\n", + assuan_strerror (err)); + else if (okay != 1) + { + _assuan_log_printf ("can't connect server: `%s'\n", + (*ctx)->inbound.line); + err = _assuan_error (ASSUAN_Connect_Failed); + } + + if (err) + { + assuan_disconnect (*ctx); + *ctx = NULL; + } + return err; +} + + +#ifndef HAVE_W32_SYSTEM +#define pipe_connect pipe_connect_unix +/* Unix version of the pipe connection code. We use an extra macro to + make ChangeLog entries easier. */ +static assuan_error_t +pipe_connect_unix (assuan_context_t *ctx, + const char *name, const char *const argv[], + int *fd_child_list, + void (*atfork) (void *opaque, int reserved), + void *atforkvalue) +{ + assuan_error_t err; + int rp[2]; + int wp[2]; + char mypidstr[50]; + + if (!ctx || !name || !argv || !argv[0]) + return _assuan_error (ASSUAN_Invalid_Value); + + fix_signals (); + + sprintf (mypidstr, "%lu", (unsigned long)getpid ()); + + if (pipe (rp) < 0) + return _assuan_error (ASSUAN_General_Error); + + if (pipe (wp) < 0) + { + close (rp[0]); + close (rp[1]); + return _assuan_error (ASSUAN_General_Error); + } + + err = _assuan_new_context (ctx); + if (err) + { + close (rp[0]); + close (rp[1]); + close (wp[0]); + close (wp[1]); + return err; + } + (*ctx)->pipe_mode = 1; + (*ctx)->inbound.fd = rp[0]; /* Our inbound is read end of read pipe. */ + (*ctx)->outbound.fd = wp[1]; /* Our outbound is write end of write pipe. */ + (*ctx)->deinit_handler = do_deinit; + (*ctx)->finish_handler = do_finish; + + /* FIXME: For GPGME we should better use _gpgme_io_spawn. The PID + stored here is actually soon useless. */ + (*ctx)->pid = fork (); + if ((*ctx)->pid < 0) + { + close (rp[0]); + close (rp[1]); + close (wp[0]); + close (wp[1]); + _assuan_release_context (*ctx); + return _assuan_error (ASSUAN_General_Error); + } + + if ((*ctx)->pid == 0) + { +#ifdef _ASSUAN_USE_DOUBLE_FORK + pid_t pid; + + if ((pid = fork ()) == 0) +#endif + { + int i, n; + char errbuf[512]; + int *fdp; + + if (atfork) + atfork (atforkvalue, 0); + + /* Dup handles to stdin/stdout. */ + if (rp[1] != STDOUT_FILENO) + { + if (dup2 (rp[1], STDOUT_FILENO) == -1) + { + _assuan_log_printf ("dup2 failed in child: %s\n", + strerror (errno)); + _exit (4); + } + } + if (wp[0] != STDIN_FILENO) + { + if (dup2 (wp[0], STDIN_FILENO) == -1) + { + _assuan_log_printf ("dup2 failed in child: %s\n", + strerror (errno)); + _exit (4); + } + } + + /* Dup stderr to /dev/null unless it is in the list of FDs to be + passed to the child. */ + fdp = fd_child_list; + if (fdp) + { + for (; *fdp != -1 && *fdp != STDERR_FILENO; fdp++) + ; + } + if (!fdp || *fdp == -1) + { + int fd = open ("/dev/null", O_WRONLY); + if (fd == -1) + { + _assuan_log_printf ("can't open `/dev/null': %s\n", + strerror (errno)); + _exit (4); + } + if (dup2 (fd, STDERR_FILENO) == -1) + { + _assuan_log_printf ("dup2(dev/null, 2) failed: %s\n", + strerror (errno)); + _exit (4); + } + } + + + /* Close all files which will not be duped and are not in the + fd_child_list. */ + n = sysconf (_SC_OPEN_MAX); + if (n < 0) + n = MAX_OPEN_FDS; + for (i=0; i < n; i++) + { + if ( i == STDIN_FILENO || i == STDOUT_FILENO + || i == STDERR_FILENO) + continue; + fdp = fd_child_list; + if (fdp) + { + while (*fdp != -1 && *fdp != i) + fdp++; + } + + if (!(fdp && *fdp != -1)) + close(i); + } + errno = 0; + + /* We store our parents pid in the environment so that the + execed assuan server is able to read the actual pid of the + client. The server can't use getppid because it might have + been double forked before the assuan server has been + initialized. */ + setenv ("_assuan_pipe_connect_pid", mypidstr, 1); + + /* Make sure that we never pass a connection fd variable + when using a simple pipe. */ + unsetenv ("_assuan_connection_fd"); + + execv (name, (char *const *) argv); + /* oops - use the pipe to tell the parent about it */ + snprintf (errbuf, sizeof(errbuf)-1, + "ERR %d can't exec `%s': %.50s\n", + _assuan_error (ASSUAN_Problem_Starting_Server), + name, strerror (errno)); + errbuf[sizeof(errbuf)-1] = 0; + writen (1, errbuf, strlen (errbuf)); + _exit (4); + } +#ifdef _ASSUAN_USE_DOUBLE_FORK + if (pid == -1) + _exit (1); + else + _exit (0); +#endif + } + +#ifdef _ASSUAN_USE_DOUBLE_FORK + _assuan_waitpid ((*ctx)->pid, NULL, 0); + (*ctx)->pid = -1; +#endif + + close (rp[1]); + close (wp[0]); + + return initial_handshake (ctx); +} +#endif /*!HAVE_W32_SYSTEM*/ + + +#ifndef HAVE_W32_SYSTEM +/* This function is similar to pipe_connect but uses a socketpair and + sets the I/O up to use sendmsg/recvmsg. */ +static assuan_error_t +socketpair_connect (assuan_context_t *ctx, + const char *name, const char *const argv[], + int *fd_child_list, + void (*atfork) (void *opaque, int reserved), + void *atforkvalue) +{ + assuan_error_t err; + int fds[2]; + char mypidstr[50]; + + if (!ctx + || (name && (!argv || !argv[0])) + || (!name && argv)) + return _assuan_error (ASSUAN_Invalid_Value); + + fix_signals (); + + sprintf (mypidstr, "%lu", (unsigned long)getpid ()); + + if ( socketpair (AF_LOCAL, SOCK_STREAM, 0, fds) ) + { + _assuan_log_printf ("socketpair failed: %s\n", strerror (errno)); + return _assuan_error (ASSUAN_General_Error); + } + + err = _assuan_new_context (ctx); + if (err) + { + close (fds[0]); + close (fds[1]); + return err; + } + (*ctx)->pipe_mode = 1; + (*ctx)->inbound.fd = fds[0]; + (*ctx)->outbound.fd = fds[0]; + _assuan_init_uds_io (*ctx); + (*ctx)->deinit_handler = _assuan_uds_deinit; + (*ctx)->finish_handler = do_finish; + + (*ctx)->pid = fork (); + if ((*ctx)->pid < 0) + { + close (fds[0]); + close (fds[1]); + _assuan_release_context (*ctx); + *ctx = NULL; + return _assuan_error (ASSUAN_General_Error); + } + + if ((*ctx)->pid == 0) + { +#ifdef _ASSUAN_USE_DOUBLE_FORK + pid_t pid; + + if ((pid = fork ()) == 0) +#endif + { + int fd, i, n; + char errbuf[512]; + int *fdp; + + if (atfork) + atfork (atforkvalue, 0); + + /* Connect stdin and stdout to /dev/null. */ + fd = open ("/dev/null", O_RDONLY); + if (fd == -1 || dup2 (fd, STDIN_FILENO) == -1) + { + _assuan_log_printf ("dup2(dev/null) failed: %s\n", + strerror (errno)); + _exit (4); + } + fd = open ("/dev/null", O_WRONLY); + if (fd == -1 || dup2 (fd, STDOUT_FILENO) == -1) + { + _assuan_log_printf ("dup2(dev/null) failed: %s\n", + strerror (errno)); + _exit (4); + } + + /* Dup stderr to /dev/null unless it is in the list of FDs to be + passed to the child. */ + fdp = fd_child_list; + if (fdp) + { + for (; *fdp != -1 && *fdp != STDERR_FILENO; fdp++) + ; + } + if (!fdp || *fdp == -1) + { + fd = open ("/dev/null", O_WRONLY); + if (fd == -1 || dup2 (fd, STDERR_FILENO) == -1) + { + _assuan_log_printf ("dup2(dev/null) failed: %s\n", + strerror (errno)); + _exit (4); + } + } + + + /* Close all files which will not be duped, are not in the + fd_child_list and are not the connection fd. */ + n = sysconf (_SC_OPEN_MAX); + if (n < 0) + n = MAX_OPEN_FDS; + for (i=0; i < n; i++) + { + if ( i == STDIN_FILENO || i == STDOUT_FILENO + || i == STDERR_FILENO || i == fds[1]) + continue; + fdp = fd_child_list; + if (fdp) + { + while (*fdp != -1 && *fdp != i) + fdp++; + } + + if (!(fdp && *fdp != -1)) + close(i); + } + errno = 0; + + /* We store our parents pid in the environment so that the + execed assuan server is able to read the actual pid of the + client. The server can't use getppid becuase it might have + been double forked before the assuan server has been + initialized. */ + setenv ("_assuan_pipe_connect_pid", mypidstr, 1); + + /* Now set the environment variable used to convey the + connection's file descriptor. */ + sprintf (mypidstr, "%d", fds[1]); + if (setenv ("_assuan_connection_fd", mypidstr, 1)) + { + _assuan_log_printf ("setenv failed: %s\n", strerror (errno)); + _exit (4); + } + + if (!name && !argv) + { + /* No name and no args given, thus we don't do an exec + but continue the forked process. */ + _assuan_release_context (*ctx); + *ctx = NULL; + return 0; + } + + execv (name, (char *const *) argv); + /* oops - use the pipe to tell the parent about it */ + snprintf (errbuf, sizeof(errbuf)-1, + "ERR %d can't exec `%s': %.50s\n", + _assuan_error (ASSUAN_Problem_Starting_Server), + name, strerror (errno)); + errbuf[sizeof(errbuf)-1] = 0; + writen (fds[1], errbuf, strlen (errbuf)); + _exit (4); + } +#ifdef _ASSUAN_USE_DOUBLE_FORK + if (pid == -1) + _exit (1); + else + _exit (0); +#endif + } + + +#ifdef _ASSUAN_USE_DOUBLE_FORK + _assuan_waitpid ((*ctx)->pid, NULL, 0); + (*ctx)->pid = -1; +#endif + + close (fds[1]); + + return initial_handshake (ctx); +} +#endif /*!HAVE_W32_SYSTEM*/ + + + #ifdef HAVE_W32_SYSTEM /* Build a command line for use with W32's CreateProcess. On success CMDLINE gets the address of a newly allocated string. */ @@ -236,21 +648,16 @@ create_inheritable_pipe (int filedes[2], int for_write) #endif /*HAVE_W32_SYSTEM*/ -/* Connect to a server over a pipe, creating the assuan context and - returning it in CTX. The server filename is NAME, the argument - vector in ARGV. FD_CHILD_LIST is a -1 terminated list of file - descriptors not to close in the child. ATFORK is called in the - child right after the fork; ATFORKVALUE is passed as the first - argument and 0 is passed as the second argument. The ATFORK - function should only act if the second value is 0. */ -assuan_error_t -assuan_pipe_connect2 (assuan_context_t *ctx, - const char *name, const char *const argv[], - int *fd_child_list, - void (*atfork) (void *opaque, int reserved), - void *atforkvalue) -{ #ifdef HAVE_W32_SYSTEM +#define pipe_connect pipe_connect_w32 +/* W32 version of the pipe connection code. */ +static assuan_error_t +pipe_connect_w32 (assuan_context_t *ctx, + const char *name, const char *const argv[], + int *fd_child_list, + void (*atfork) (void *opaque, int reserved), + void *atforkvalue) +{ assuan_error_t err; int rp[2]; int wp[2]; @@ -269,7 +676,7 @@ assuan_pipe_connect2 (assuan_context_t *ctx, HANDLE nullfd = INVALID_HANDLE_VALUE; if (!ctx || !name || !argv || !argv[0]) - return ASSUAN_Invalid_Value; + return _assuan_error (ASSUAN_Invalid_Value); fix_signals (); @@ -277,13 +684,13 @@ assuan_pipe_connect2 (assuan_context_t *ctx, /* Build the command line. */ if (build_w32_commandline (argv, &cmdline)) - return ASSUAN_Out_Of_Core; + return _assuan_error (ASSUAN_Out_Of_Core); /* Create thew two pipes. */ if (create_inheritable_pipe (rp, 0)) { xfree (cmdline); - return ASSUAN_General_Error; + return _assuan_error (ASSUAN_General_Error); } if (create_inheritable_pipe (wp, 1)) @@ -291,7 +698,7 @@ assuan_pipe_connect2 (assuan_context_t *ctx, CloseHandle (fd_to_handle (rp[0])); CloseHandle (fd_to_handle (rp[1])); xfree (cmdline); - return ASSUAN_General_Error; + return _assuan_error (ASSUAN_General_Error); } @@ -303,7 +710,7 @@ assuan_pipe_connect2 (assuan_context_t *ctx, CloseHandle (fd_to_handle (wp[0])); CloseHandle (fd_to_handle (wp[1])); xfree (cmdline); - return ASSUAN_General_Error; + return _assuan_error (ASSUAN_General_Error); } (*ctx)->pipe_mode = 1; @@ -390,7 +797,7 @@ assuan_pipe_connect2 (assuan_context_t *ctx, CloseHandle (nullfd); xfree (cmdline); _assuan_release_context (*ctx); - return ASSUAN_General_Error; + return _assuan_error (ASSUAN_General_Error); } xfree (cmdline); cmdline = NULL; @@ -413,200 +820,11 @@ assuan_pipe_connect2 (assuan_context_t *ctx, (*ctx)->pid = 0; /* We don't use the PID. */ CloseHandle (pi.hProcess); /* We don't need to wait for the process. */ -#else /*!HAVE_W32_SYSTEM*/ - assuan_error_t err; - int rp[2]; - int wp[2]; - char mypidstr[50]; - - if (!ctx || !name || !argv || !argv[0]) - return ASSUAN_Invalid_Value; - - fix_signals (); - - sprintf (mypidstr, "%lu", (unsigned long)getpid ()); - - if (pipe (rp) < 0) - return ASSUAN_General_Error; - - if (pipe (wp) < 0) - { - close (rp[0]); - close (rp[1]); - return ASSUAN_General_Error; - } - - err = _assuan_new_context (ctx); - if (err) - { - close (rp[0]); - close (rp[1]); - close (wp[0]); - close (wp[1]); - return err; - } - (*ctx)->pipe_mode = 1; - (*ctx)->inbound.fd = rp[0]; /* Our inbound is read end of read pipe. */ - (*ctx)->outbound.fd = wp[1]; /* Our outbound is write end of write pipe. */ - (*ctx)->deinit_handler = do_deinit; - (*ctx)->finish_handler = do_finish; - - /* FIXME: For GPGME we should better use _gpgme_io_spawn. The PID - stored here is actually soon useless. */ - (*ctx)->pid = fork (); - if ((*ctx)->pid < 0) - { - close (rp[0]); - close (rp[1]); - close (wp[0]); - close (wp[1]); - _assuan_release_context (*ctx); - return ASSUAN_General_Error; - } - - if ((*ctx)->pid == 0) - { -#ifdef _ASSUAN_USE_DOUBLE_FORK - pid_t pid; - - if ((pid = fork ()) == 0) -#endif - { - int i, n; - char errbuf[512]; - int *fdp; - - if (atfork) - atfork (atforkvalue, 0); - - /* Dup handles to stdin/stdout. */ - if (rp[1] != STDOUT_FILENO) - { - if (dup2 (rp[1], STDOUT_FILENO) == -1) - { - _assuan_log_printf ("dup2 failed in child: %s\n", - strerror (errno)); - _exit (4); - } - } - if (wp[0] != STDIN_FILENO) - { - if (dup2 (wp[0], STDIN_FILENO) == -1) - { - _assuan_log_printf ("dup2 failed in child: %s\n", - strerror (errno)); - _exit (4); - } - } - - /* Dup stderr to /dev/null unless it is in the list of FDs to be - passed to the child. */ - fdp = fd_child_list; - if (fdp) - { - for (; *fdp != -1 && *fdp != STDERR_FILENO; fdp++) - ; - } - if (!fdp || *fdp == -1) - { - int fd = open ("/dev/null", O_WRONLY); - if (fd == -1) - { - _assuan_log_printf ("can't open `/dev/null': %s\n", - strerror (errno)); - _exit (4); - } - if (dup2 (fd, STDERR_FILENO) == -1) - { - _assuan_log_printf ("dup2(dev/null, 2) failed: %s\n", - strerror (errno)); - _exit (4); - } - } - - - /* Close all files which will not be duped and are not in the - fd_child_list. */ - n = sysconf (_SC_OPEN_MAX); - if (n < 0) - n = MAX_OPEN_FDS; - for (i=0; i < n; i++) - { - if ( i == STDIN_FILENO || i == STDOUT_FILENO - || i == STDERR_FILENO) - continue; - fdp = fd_child_list; - if (fdp) - { - while (*fdp != -1 && *fdp != i) - fdp++; - } - - if (!(fdp && *fdp != -1)) - close(i); - } - errno = 0; - - /* We store our parents pid in the environment so that the - execed assuan server is able to read the actual pid of the - client. The server can't use getppid becuase it might have - been double forked before the assuan server has been - initialized. */ - setenv ("_assuan_pipe_connect_pid", mypidstr, 1); - - execv (name, (char *const *) argv); - /* oops - use the pipe to tell the parent about it */ - snprintf (errbuf, sizeof(errbuf)-1, - "ERR %d can't exec `%s': %.50s\n", - ASSUAN_Problem_Starting_Server, name, strerror (errno)); - errbuf[sizeof(errbuf)-1] = 0; - writen (1, errbuf, strlen (errbuf)); - _exit (4); - } -#ifdef _ASSUAN_USE_DOUBLE_FORK - if (pid == -1) - _exit (1); - else - _exit (0); -#endif - } - -#ifdef _ASSUAN_USE_DOUBLE_FORK - waitpid ((*ctx)->pid, NULL, 0); - (*ctx)->pid = -1; -#endif - - close (rp[1]); - close (wp[0]); - -#endif /*!HAVE_W32_SYSTEM*/ - - /* initial handshake */ - { - int okay, off; - - err = _assuan_read_from_server (*ctx, &okay, &off); - if (err) - _assuan_log_printf ("can't connect server: %s\n", - assuan_strerror (err)); - else if (okay != 1) - { - _assuan_log_printf ("can't connect server: `%s'\n", - (*ctx)->inbound.line); - err = ASSUAN_Connect_Failed; - } - } - - if (err) - { - assuan_disconnect (*ctx); - *ctx = NULL; - } - - return err; + return initial_handshake (ctx); } +#endif /*HAVE_W32_SYSTEM*/ - + /* Connect to a server over a pipe, creating the assuan context and returning it in CTX. The server filename is NAME, the argument vector in ARGV. FD_CHILD_LIST is a -1 terminated list of file @@ -615,5 +833,57 @@ assuan_error_t assuan_pipe_connect (assuan_context_t *ctx, const char *name, const char *const argv[], int *fd_child_list) { - return assuan_pipe_connect2 (ctx, name, argv, fd_child_list, NULL, NULL); + return pipe_connect (ctx, name, argv, fd_child_list, NULL, NULL); +} + + + +assuan_error_t +assuan_pipe_connect2 (assuan_context_t *ctx, + const char *name, const char *const argv[], + int *fd_child_list, + void (*atfork) (void *opaque, int reserved), + void *atforkvalue) +{ + return pipe_connect (ctx, name, argv, fd_child_list, atfork, atforkvalue); +} + + +/* Connect to a server over a full-duplex socket (i.e. created by + socketpair), creating the assuan context and returning it in CTX. + The server filename is NAME, the argument vector in ARGV. + FD_CHILD_LIST is a -1 terminated list of file descriptors not to + close in the child. ATFORK is called in the child right after the + fork; ATFORKVALUE is passed as the first argument and 0 is passed + as the second argument. The ATFORK function should only act if the + second value is 0. + + For now FLAGS may either take the value 0 to behave like + assuan_pipe_connect2 or 1 to enable the described full-duplex + socket behaviour. + + If NAME as well as ARGV are NULL, no exec is done but the same + process is continued. However all file descriptors are closed and + some special environment variables are set. To let the caller + detect whether the child or the parent continues, the child returns + a CTX of NULL. */ +assuan_error_t +assuan_pipe_connect_ext (assuan_context_t *ctx, + const char *name, const char *const argv[], + int *fd_child_list, + void (*atfork) (void *opaque, int reserved), + void *atforkvalue, unsigned int flags) +{ + if ((flags & 1)) + { +#ifdef HAVE_W32_SYSTEM + return _assuan_error (ASSUAN_Not_Implemented); +#else + return socketpair_connect (ctx, name, argv, fd_child_list, + atfork, atforkvalue); +#endif + } + else + return pipe_connect (ctx, name, argv, fd_child_list, atfork, atforkvalue); } + |