]> granicus.if.org Git - postgresql/blob - src/backend/libpq/pqcomm.c
Remove archive stuff.
[postgresql] / src / backend / libpq / pqcomm.c
1  /*-------------------------------------------------------------------------
2  *
3  * pqcomm.c--
4  *        Communication functions between the Frontend and the Backend
5  *
6  * Copyright (c) 1994, Regents of the University of California
7  *
8  *
9  * IDENTIFICATION
10  *        $Header: /cvsroot/pgsql/src/backend/libpq/pqcomm.c,v 1.30 1997/11/21 18:10:15 momjian Exp $
11  *
12  *-------------------------------------------------------------------------
13  */
14 /*
15  * INTERFACE ROUTINES
16  *              pq_gettty               - return the name of the tty in the given buffer
17  *              pq_getport              - return the PGPORT setting
18  *              pq_close                - close input / output connections
19  *              pq_flush                - flush pending output
20  *              pq_getstr               - get a null terminated string from connection
21  *              pq_getnchar             - get n characters from connection
22  *              pq_getint               - get an integer from connection
23  *              pq_putstr               - send a null terminated string to connection
24  *              pq_putnchar             - send n characters to connection
25  *              pq_putint               - send an integer to connection
26  *              pq_getinaddr    - initialize address from host and port number
27  *              pq_getinserv    - initialize address from host and service name
28  *              pq_connect              - create remote input / output connection
29  *              pq_accept               - accept remote input / output connection
30  *              pq_async_notify - receive notification from backend.
31  *
32  * NOTES
33  *              These functions are used by both frontend applications and
34  *              the postgres backend.
35  *
36  */
37 #include <stdio.h>
38 #include <string.h>
39 #include <signal.h>
40 #include <errno.h>
41 #include <fcntl.h>
42 #include <unistd.h>                             /* for ttyname() */
43 #include <sys/types.h>
44 #include <sys/stat.h>
45 #include <sys/socket.h>
46 #include <netdb.h>
47 #include <netinet/in.h>
48 #include <netinet/tcp.h>
49 #include <arpa/inet.h>
50
51 #if defined(linux)
52 #ifndef SOMAXCONN
53 #define SOMAXCONN 5                             /* from Linux listen(2) man page */
54 #endif                                                  /* SOMAXCONN */
55 #endif                                                  /* linux */
56
57 #include <postgres.h>
58
59 #include <libpq/pqsignal.h>
60 #include <libpq/auth.h>
61 #include <libpq/libpq.h>                /* where the declarations go */
62 #include <storage/ipc.h>
63
64 /* ----------------
65  *              declarations
66  * ----------------
67  */
68 FILE       *Pfout,
69                    *Pfin;
70 FILE       *Pfdebug;                    /* debugging libpq */
71 int                     PQAsyncNotifyWaiting;           /* for async. notification */
72
73 /* --------------------------------
74  *              pq_init - open portal file descriptors
75  * --------------------------------
76  */
77 void
78 pq_init(int fd)
79 {
80         Pfin = fdopen(fd, "r");
81         Pfout = fdopen(dup(fd), "w");
82         if (!Pfin || !Pfout)
83                 elog(FATAL, "pq_init: Couldn't initialize socket connection");
84         PQnotifies_init();
85         if (getenv("LIBPQ_DEBUG"))
86         {
87                 Pfdebug = stderr;
88         }
89         else
90         {
91                 Pfdebug = NULL;
92         }
93 }
94
95 /* -------------------------
96  *       pq_getc(File* fin)
97  *
98  *       get a character from the input file,
99  *
100  *       if Pfdebug is set, also echo the character fetched into Pfdebug
101  *
102  *       used for debugging libpq
103  */
104 static int
105 pq_getc(FILE *fin)
106 {
107         int                     c;
108
109         c = getc(fin);
110         if (Pfdebug && c != EOF)
111                 putc(c, Pfdebug);
112         return c;
113 }
114
115 /* --------------------------------
116  *              pq_gettty - return the name of the tty in the given buffer
117  * --------------------------------
118  */
119 void
120 pq_gettty(char *tp)
121 {
122         strncpy(tp, ttyname(0), 19);
123 }
124
125 /* --------------------------------
126  *              pq_getport - return the PGPORT setting
127  * --------------------------------
128  */
129 int
130 pq_getport()
131 {
132         char       *envport = getenv("PGPORT");
133
134         if (envport)
135                 return (atoi(envport));
136         return (atoi(DEF_PGPORT));
137 }
138
139 /* --------------------------------
140  *              pq_close - close input / output connections
141  * --------------------------------
142  */
143 void
144 pq_close()
145 {
146         if (Pfin)
147         {
148                 fclose(Pfin);
149                 Pfin = NULL;
150         }
151         if (Pfout)
152         {
153                 fclose(Pfout);
154                 Pfout = NULL;
155         }
156         PQAsyncNotifyWaiting = 0;
157         PQnotifies_init();
158         pq_unregoob();
159 }
160
161 /* --------------------------------
162  *              pq_flush - flush pending output
163  * --------------------------------
164  */
165 void
166 pq_flush()
167 {
168         if (Pfout)
169                 fflush(Pfout);
170 }
171
172 /* --------------------------------
173  *              pq_getstr - get a null terminated string from connection
174  * --------------------------------
175  */
176 int
177 pq_getstr(char *s, int maxlen)
178 {
179         int                     c = '\0';
180
181         if (Pfin == (FILE *) NULL)
182         {
183 /*              elog(DEBUG, "Input descriptor is null"); */
184                 return (EOF);
185         }
186
187         while (maxlen-- && (c = pq_getc(Pfin)) != EOF && c)
188                 *s++ = c;
189         *s = '\0';
190
191         /* -----------------
192          *         If EOF reached let caller know.
193          *         (This will only happen if we hit EOF before the string
194          *         delimiter is reached.)
195          * -----------------
196          */
197         if (c == EOF)
198                 return (EOF);
199         return (!EOF);
200 }
201
202 /*
203  * USER FUNCTION - gets a newline-terminated string from the backend.
204  *
205  * Chiefly here so that applications can use "COPY <rel> to stdout"
206  * and read the output string.  Returns a null-terminated string in s.
207  *
208  * PQgetline reads up to maxlen-1 characters (like fgets(3)) but strips
209  * the terminating \n (like gets(3)).
210  *
211  * RETURNS:
212  *              EOF if it is detected or invalid arguments are given
213  *              0 if EOL is reached (i.e., \n has been read)
214  *                              (this is required for backward-compatibility -- this
215  *                               routine used to always return EOF or 0, assuming that
216  *                               the line ended within maxlen bytes.)
217  *              1 in other cases
218  */
219 int
220 PQgetline(char *s, int maxlen)
221 {
222         if (!Pfin || !s || maxlen <= 1)
223                 return (EOF);
224
225         if (fgets(s, maxlen - 1, Pfin) == NULL)
226         {
227                 return feof(Pfin) ? EOF : 1;
228         }
229         else
230         {
231                 for (; *s; s++)
232                 {
233                         if (*s == '\n')
234                         {
235                                 *s = '\0';
236                                 break;
237                         }
238                 }
239         }
240
241         return 0;
242 }
243
244 /*
245  * USER FUNCTION - sends a string to the backend.
246  *
247  * Chiefly here so that applications can use "COPY <rel> from stdin".
248  *
249  * RETURNS:
250  *              0 in all cases.
251  */
252 int
253 PQputline(char *s)
254 {
255         if (Pfout)
256         {
257                 fputs(s, Pfout);
258                 fflush(Pfout);
259         }
260         return (0);
261 }
262
263 /* --------------------------------
264  *              pq_getnchar - get n characters from connection
265  * --------------------------------
266  */
267 int
268 pq_getnchar(char *s, int off, int maxlen)
269 {
270         return pqGetNBytes(s + off, maxlen, Pfin);
271
272 #if 0
273         int                     c = '\0';
274
275         if (Pfin == (FILE *) NULL)
276         {
277 /*              elog(DEBUG, "Input descriptor is null"); */
278                 return (EOF);
279         }
280
281         s += off;
282         while (maxlen-- && (c = pq_getc(Pfin)) != EOF)
283                 *s++ = c;
284
285         /* -----------------
286          *         If EOF reached let caller know
287          * -----------------
288          */
289         if (c == EOF)
290                 return (EOF);
291         return (!EOF);
292 #endif
293 }
294
295 /* --------------------------------
296  *              pq_getint - get an integer from connection
297  *       we receive an integer a byte at a type and reconstruct it so that
298  *       machines with different ENDIAN representations can talk to each
299  *       other
300  * --------------------------------
301  */
302 int
303 pq_getint(int b)
304 {
305         int                     n,
306                                 status = 1;
307
308         if (!Pfin)
309                 return EOF;
310
311         /*
312          * mjl: Seems inconsisten w/ return value of pq_putint (void). Also,
313          * EOF is a valid return value for an int! XXX
314          */
315
316         switch (b)
317         {
318                 case 1:
319                         status = ((n = fgetc(Pfin)) == EOF);
320                         break;
321                 case 2:
322                         status = pqGetShort(&n, Pfin);
323                         break;
324                 case 4:
325                         status = pqGetLong(&n, Pfin);
326                         break;
327                 default:
328                         fprintf(stderr, "** Unsupported size %d\n", b);
329         }
330
331         if (status)
332         {
333                 sprintf(PQerrormsg,
334                                 "FATAL: pq_getint failed: errno=%d\n", errno);
335                 fputs(PQerrormsg, stderr);
336                 pqdebug("%s", PQerrormsg);
337                 n = 0;
338         }
339
340         return n;
341 }
342
343 /* --------------------------------
344  *              pq_putstr - send a null terminated string to connection
345  * --------------------------------
346  */
347 void
348 pq_putstr(char *s)
349 {
350         if (pqPutString(s, Pfout))
351         {
352                 sprintf(PQerrormsg,
353                                 "FATAL: pq_putstr: fputs() failed: errno=%d\n", errno);
354                 fputs(PQerrormsg, stderr);
355                 pqdebug("%s", PQerrormsg);
356         }
357 }
358
359 /* --------------------------------
360  *              pq_putnchar - send n characters to connection
361  * --------------------------------
362  */
363 void
364 pq_putnchar(char *s, int n)
365 {
366         if (pqPutNBytes(s, n, Pfout))
367         {
368                 sprintf(PQerrormsg,
369                                 "FATAL: pq_putnchar: fputc() failed: errno=%d\n",
370                                 errno);
371                 fputs(PQerrormsg, stderr);
372                 pqdebug("%s", PQerrormsg);
373         }
374 }
375
376 /* --------------------------------
377  *              pq_putint - send an integer to connection
378  *       we chop an integer into bytes and send individual bytes
379  *       machines with different ENDIAN representations can still talk to each
380  *       other
381  * --------------------------------
382  */
383 void
384 pq_putint(int i, int b)
385 {
386         int                     status;
387
388         if (!Pfout)
389                 return;
390
391         status = 1;
392         switch (b)
393         {
394                 case 1:
395                         status = (fputc(i, Pfout) == EOF);
396                         break;
397                 case 2:
398                         status = pqPutShort(i, Pfout);
399                         break;
400                 case 4:
401                         status = pqPutLong(i, Pfout);
402                         break;
403                 default:
404                         fprintf(stderr, "** Unsupported size %d\n", b);
405         }
406
407         if (status)
408         {
409                 sprintf(PQerrormsg,
410                                 "FATAL: pq_putint failed: errno=%d\n", errno);
411                 fputs(PQerrormsg, stderr);
412                 pqdebug("%s", PQerrormsg);
413         }
414 }
415
416 /* ---
417  *         pq_sendoob - send a string over the out-of-band channel
418  *         pq_recvoob - receive a string over the oob channel
419  *      NB: Fortunately, the out-of-band channel doesn't conflict with
420  *              buffered I/O because it is separate from regular com. channel.
421  * ---
422  */
423 int
424 pq_sendoob(char *msg, int len)
425 {
426         int                     fd = fileno(Pfout);
427
428         return (send(fd, msg, len, MSG_OOB));
429 }
430
431 int
432 pq_recvoob(char *msgPtr, int *lenPtr)
433 {
434         int                     fd = fileno(Pfout);
435         int                     len = 0;
436
437         len = recv(fd, msgPtr + len, *lenPtr, MSG_OOB);
438         *lenPtr = len;
439         return (len);
440 }
441
442 /* --------------------------------
443  *              pq_getinaddr - initialize address from host and port number
444  * --------------------------------
445  */
446 int
447 pq_getinaddr(struct sockaddr_in * sin,
448                          char *host,
449                          int port)
450 {
451         struct hostent *hs;
452
453         MemSet((char *) sin, 0, sizeof(*sin));
454
455         if (host)
456         {
457                 if (*host >= '0' && *host <= '9')
458                         sin->sin_addr.s_addr = inet_addr(host);
459                 else
460                 {
461                         if (!(hs = gethostbyname(host)))
462                         {
463                                 perror(host);
464                                 return (1);
465                         }
466                         if (hs->h_addrtype != AF_INET)
467                         {
468                                 sprintf(PQerrormsg,
469                                                 "FATAL: pq_getinaddr: %s not on Internet\n",
470                                                 host);
471                                 fputs(PQerrormsg, stderr);
472                                 pqdebug("%s", PQerrormsg);
473                                 return (1);
474                         }
475                         memmove((char *) &sin->sin_addr,
476                                         hs->h_addr,
477                                         hs->h_length);
478                 }
479         }
480         sin->sin_family = AF_INET;
481         sin->sin_port = htons(port);
482         return (0);
483 }
484
485 /* --------------------------------
486  *              pq_getinserv - initialize address from host and servive name
487  * --------------------------------
488  */
489 int
490 pq_getinserv(struct sockaddr_in * sin, char *host, char *serv)
491 {
492         struct servent *ss;
493
494         if (*serv >= '0' && *serv <= '9')
495                 return (pq_getinaddr(sin, host, atoi(serv)));
496         if (!(ss = getservbyname(serv, NULL)))
497         {
498                 sprintf(PQerrormsg,
499                                 "FATAL: pq_getinserv: unknown service: %s\n",
500                                 serv);
501                 fputs(PQerrormsg, stderr);
502                 pqdebug("%s", PQerrormsg);
503                 return (1);
504         }
505         return (pq_getinaddr(sin, host, ntohs(ss->s_port)));
506 }
507
508 /*
509  * register an out-of-band listener proc--at most one allowed.
510  * This is used for receiving async. notification from the backend.
511  */
512 void
513 pq_regoob(void (*fptr) ())
514 {
515         int                     fd = fileno(Pfout);
516
517 #if defined(hpux)
518         ioctl(fd, FIOSSAIOOWN, getpid());
519 #elif defined(sco)
520         ioctl(fd, SIOCSPGRP, getpid());
521 #else
522         fcntl(fd, F_SETOWN, getpid());
523 #endif                                                  /* hpux */
524         pqsignal(SIGURG, fptr);
525 }
526
527 void
528 pq_unregoob()
529 {
530         pqsignal(SIGURG, SIG_DFL);
531 }
532
533
534 void
535 pq_async_notify()
536 {
537         char            msg[20];
538
539         /* int len = sizeof(msg); */
540         int                     len = 20;
541
542         if (pq_recvoob(msg, &len) >= 0)
543         {
544                 /* debugging */
545                 printf("received notification: %s\n", msg);
546                 PQAsyncNotifyWaiting = 1;
547                 /* PQappendNotify(msg+1); */
548         }
549         else
550         {
551                 extern int      errno;
552
553                 printf("SIGURG but no data: len = %d, err=%d\n", len, errno);
554         }
555 }
556
557 /*
558  * Streams -- wrapper around Unix socket system calls
559  *
560  *
561  *              Stream functions are used for vanilla TCP connection protocol.
562  */
563
564 /*
565  * StreamServerPort -- open a sock stream "listening" port.
566  *
567  * This initializes the Postmaster's connection
568  *              accepting port.
569  *
570  * ASSUME: that this doesn't need to be non-blocking because
571  *              the Postmaster uses select() to tell when the socket
572  *              is ready.
573  *
574  * RETURNS: STATUS_OK or STATUS_ERROR
575  */
576
577 static char sock_path[100] = "";
578
579 static void
580 do_unlink()
581 {
582         if (sock_path[0])
583                 unlink(sock_path);
584 }
585
586 int
587 StreamServerPort(char *hostName, short portName, int *fdP)
588 {
589         union
590         {
591                 struct sockaddr_in in;
592                 struct sockaddr_un un;
593         }                       saddr;
594         int                     fd,
595                                 err,
596                                 family;
597         size_t          len;
598         int                     one = 1;
599
600         family = ((hostName != NULL) ? AF_INET : AF_UNIX);
601
602         if ((fd = socket(family, SOCK_STREAM, 0)) < 0)
603         {
604                 sprintf(PQerrormsg,
605                                 "FATAL: StreamServerPort: socket() failed: errno=%d\n",
606                                 errno);
607                 fputs(PQerrormsg, stderr);
608                 pqdebug("%s", PQerrormsg);
609                 return (STATUS_ERROR);
610         }
611         if ((setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &one,
612                                         sizeof(one))) == -1)
613         {
614                 sprintf(PQerrormsg,
615                                 "FATAL: StreamServerPort: setsockopt (SO_REUSEADDR) failed: errno=%d\n",
616                                 errno);
617                 fputs(PQerrormsg, stderr);
618                 pqdebug("%s", PQerrormsg);
619                 return (STATUS_ERROR);
620         }
621         bzero(&saddr, sizeof(saddr));
622         if (family == AF_UNIX)
623         {
624                 saddr.un.sun_family = family;
625                 len = UNIXSOCK_PATH(saddr.un, portName);
626                 strcpy(sock_path, saddr.un.sun_path);
627         }
628         else
629         {
630                 saddr.in.sin_family = family;
631                 saddr.in.sin_addr.s_addr = htonl(INADDR_ANY);
632                 saddr.in.sin_port = htons(portName);
633                 len = sizeof saddr.in;
634         }
635         err = bind(fd, (struct sockaddr *) & saddr, len);
636         if (err < 0)
637         {
638           sprintf(PQerrormsg,
639                   "FATAL: StreamServerPort: bind() failed: errno=%d\n",
640                   errno);
641           pqdebug("%s", PQerrormsg);
642           strcat(PQerrormsg, "\tIs another postmaster already running on that port?\n");
643           if (family == AF_UNIX)
644             strcat(PQerrormsg, "\tIf not, remove socket node (/tmp/.s.PGSQL.<portnr>)and retry.\n");
645           else
646             strcat(PQerrormsg, "\tIf not, wait a few seconds and retry.\n");
647           fputs(PQerrormsg, stderr);
648           return (STATUS_ERROR);
649         }
650
651         listen(fd, SOMAXCONN);
652
653         /*
654          * MS: I took this code from Dillon's version.  It makes the listening
655          * port non-blocking.  That is not necessary (and may tickle kernel
656          * bugs).
657          *
658          * fcntl(fd, F_SETFD, 1); fcntl(fd, F_SETFL, FNDELAY);
659          */
660
661         *fdP = fd;
662         if (family == AF_UNIX)
663           {
664             chmod(sock_path, 0777);
665             atexit(do_unlink);
666           }
667         return (STATUS_OK);
668 }
669
670 /*
671  * StreamConnection -- create a new connection with client using
672  *              server port.
673  *
674  * This one should be non-blocking.
675  *
676  * RETURNS: STATUS_OK or STATUS_ERROR
677  */
678 int
679 StreamConnection(int server_fd, Port *port)
680 {
681         int                     len,
682                                 addrlen;
683         int                     family = port->raddr.in.sin_family;
684
685         /* accept connection (and fill in the client (remote) address) */
686         len = family == AF_INET ?
687                 sizeof(struct sockaddr_in) : sizeof(struct sockaddr_un);
688         addrlen = len;
689         if ((port->sock = accept(server_fd,
690                                                          (struct sockaddr *) & port->raddr,
691                                                          &addrlen)) < 0)
692         {
693                 elog(WARN, "postmaster: StreamConnection: accept: %m");
694                 return (STATUS_ERROR);
695         }
696
697         /* fill in the server (local) address */
698         addrlen = len;
699         if (getsockname(port->sock, (struct sockaddr *) & port->laddr,
700                                         &addrlen) < 0)
701         {
702                 elog(WARN, "postmaster: StreamConnection: getsockname: %m");
703                 return (STATUS_ERROR);
704         }
705         if (family == AF_INET)
706         {
707                 struct protoent *pe;
708                 int                     on = 1;
709
710                 pe = getprotobyname("TCP");
711                 if (pe == NULL)
712                 {
713                         elog(WARN, "postmaster: getprotobyname failed");
714                         return (STATUS_ERROR);
715                 }
716                 if (setsockopt(port->sock, pe->p_proto, TCP_NODELAY,
717                                            &on, sizeof(on)) < 0)
718                 {
719                         elog(WARN, "postmaster: setsockopt failed");
720                         return (STATUS_ERROR);
721                 }
722         }
723
724         port->mask = 1 << port->sock;
725
726         /* reset to non-blocking */
727         fcntl(port->sock, F_SETFL, 1);
728
729         return (STATUS_OK);
730 }
731
732 /*
733  * StreamClose -- close a client/backend connection
734  */
735 void
736 StreamClose(int sock)
737 {
738         close(sock);
739 }
740
741 /* ---------------------------
742  * StreamOpen -- From client, initiate a connection with the
743  *              server (Postmaster).
744  *
745  * RETURNS: STATUS_OK or STATUS_ERROR
746  *
747  * NOTE: connection is NOT established just because this
748  *              routine exits.  Local state is ok, but we haven't
749  *              spoken to the postmaster yet.
750  * ---------------------------
751  */
752 int
753 StreamOpen(char *hostName, short portName, Port *port)
754 {
755         int                     len,
756                                 err;
757         struct hostent *hp;
758         extern int      errno;
759
760         /* set up the server (remote) address */
761         MemSet((char *) &port->raddr, 0, sizeof(port->raddr));
762         if (hostName)
763         {
764                 if (!(hp = gethostbyname(hostName)) || hp->h_addrtype != AF_INET)
765                 {
766                         sprintf(PQerrormsg,
767                                         "FATAL: StreamOpen: unknown hostname: %s\n",
768                                         hostName);
769                         fputs(PQerrormsg, stderr);
770                         pqdebug("%s", PQerrormsg);
771                         return (STATUS_ERROR);
772                 }
773                 memmove((char *) &(port->raddr.in.sin_addr),
774                                 (char *) hp->h_addr,
775                                 hp->h_length);
776                 port->raddr.in.sin_family = AF_INET;
777                 port->raddr.in.sin_port = htons(portName);
778                 len = sizeof(struct sockaddr_in);
779         }
780         else
781         {
782                 port->raddr.un.sun_family = AF_UNIX;
783                 len = UNIXSOCK_PATH(port->raddr.un, portName);
784         }
785         /* connect to the server */
786         if ((port->sock = socket(port->raddr.in.sin_family, SOCK_STREAM, 0)) < 0)
787         {
788                 sprintf(PQerrormsg,
789                                 "FATAL: StreamOpen: socket() failed: errno=%d\n",
790                                 errno);
791                 fputs(PQerrormsg, stderr);
792                 pqdebug("%s", PQerrormsg);
793                 return (STATUS_ERROR);
794         }
795         err = connect(port->sock, (struct sockaddr *) & port->raddr, len);
796         if (err < 0)
797         {
798                 sprintf(PQerrormsg,
799                                 "FATAL: StreamOpen: connect() failed: errno=%d\n",
800                                 errno);
801                 fputs(PQerrormsg, stderr);
802                 pqdebug("%s", PQerrormsg);
803                 return (STATUS_ERROR);
804         }
805
806         /* fill in the client address */
807         if (getsockname(port->sock, (struct sockaddr *) & port->laddr,
808                                         &len) < 0)
809         {
810                 sprintf(PQerrormsg,
811                                 "FATAL: StreamOpen: getsockname() failed: errno=%d\n",
812                                 errno);
813                 fputs(PQerrormsg, stderr);
814                 pqdebug("%s", PQerrormsg);
815                 return (STATUS_ERROR);
816         }
817
818         return (STATUS_OK);
819 }
820
821 static char *authentication_type_name[] = {
822         0, 0, 0, 0, 0, 0, 0,
823         "the default authentication type",
824         0, 0,
825         "Kerberos v4",
826         "Kerberos v5",
827         "host-based authentication",
828         "unauthenication",
829         "plaintext password authentication"
830 };
831
832 char       *
833 name_of_authentication_type(int type)
834 {
835         char       *result = 0;
836
837         if (type >= 1 && type <= LAST_AUTHENTICATION_TYPE)
838         {
839                 result = authentication_type_name[type];
840         }
841
842         if (result == 0)
843         {
844                 result = "<unknown authentication type>";
845         }
846
847         return result;
848 }