]> granicus.if.org Git - postgresql/blob - src/interfaces/libpq/fe-lobj.c
7f8f7d7939013145db349dab822b6ca95553a04d
[postgresql] / src / interfaces / libpq / fe-lobj.c
1 /*-------------------------------------------------------------------------
2  *
3  * fe-lobj.c
4  *        Front-end large object interface
5  *
6  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
7  * Portions Copyright (c) 1994, Regents of the University of California
8  *
9  *
10  * IDENTIFICATION
11  *        $PostgreSQL: pgsql/src/interfaces/libpq/fe-lobj.c,v 1.66 2008/04/10 15:20:52 mha Exp $
12  *
13  *-------------------------------------------------------------------------
14  */
15
16 #ifdef WIN32
17 /*
18  *      As unlink/rename are #define'd in port.h (via postgres_fe.h), io.h
19  *      must be included first on MS C.  Might as well do it for all WIN32's
20  *      here.
21  */
22 #include <io.h>
23 #endif
24
25 #include "postgres_fe.h"
26
27 #ifdef WIN32
28 #include "win32.h"
29 #else
30 #include <unistd.h>
31 #endif
32
33 #include <fcntl.h>
34 #include <sys/stat.h>
35
36 #include "libpq-fe.h"
37 #include "libpq-int.h"
38 #include "libpq/libpq-fs.h"             /* must come after sys/stat.h */
39
40 #define LO_BUFSIZE                8192
41
42 static int      lo_initialize(PGconn *conn);
43
44 static Oid
45 lo_import_internal(PGconn *conn, const char *filename, const Oid oid);
46
47 /*
48  * lo_open
49  *        opens an existing large object
50  *
51  * returns the file descriptor for use in later lo_* calls
52  * return -1 upon failure.
53  */
54 int
55 lo_open(PGconn *conn, Oid lobjId, int mode)
56 {
57         int                     fd;
58         int                     result_len;
59         PQArgBlock      argv[2];
60         PGresult   *res;
61
62         if (conn->lobjfuncs == NULL)
63         {
64                 if (lo_initialize(conn) < 0)
65                         return -1;
66         }
67
68         argv[0].isint = 1;
69         argv[0].len = 4;
70         argv[0].u.integer = lobjId;
71
72         argv[1].isint = 1;
73         argv[1].len = 4;
74         argv[1].u.integer = mode;
75
76         res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2);
77         if (PQresultStatus(res) == PGRES_COMMAND_OK)
78         {
79                 PQclear(res);
80                 return fd;
81         }
82         else
83         {
84                 PQclear(res);
85                 return -1;
86         }
87 }
88
89 /*
90  * lo_close
91  *        closes an existing large object
92  *
93  * returns 0 upon success
94  * returns -1 upon failure.
95  */
96 int
97 lo_close(PGconn *conn, int fd)
98 {
99         PQArgBlock      argv[1];
100         PGresult   *res;
101         int                     retval;
102         int                     result_len;
103
104         if (conn->lobjfuncs == NULL)
105         {
106                 if (lo_initialize(conn) < 0)
107                         return -1;
108         }
109
110         argv[0].isint = 1;
111         argv[0].len = 4;
112         argv[0].u.integer = fd;
113         res = PQfn(conn, conn->lobjfuncs->fn_lo_close,
114                            &retval, &result_len, 1, argv, 1);
115         if (PQresultStatus(res) == PGRES_COMMAND_OK)
116         {
117                 PQclear(res);
118                 return retval;
119         }
120         else
121         {
122                 PQclear(res);
123                 return -1;
124         }
125 }
126
127 /*
128  * lo_truncate
129  *        truncates an existing large object to the given size
130  *
131  * returns 0 upon success
132  * returns -1 upon failure
133  */
134 int
135 lo_truncate(PGconn *conn, int fd, size_t len)
136 {
137         PQArgBlock      argv[2];
138         PGresult   *res;
139         int                     retval;
140         int                     result_len;
141
142         if (conn->lobjfuncs == NULL)
143         {
144                 if (lo_initialize(conn) < 0)
145                         return -1;
146         }
147
148         /* Must check this on-the-fly because it's not there pre-8.3 */
149         if (conn->lobjfuncs->fn_lo_truncate == 0)
150         {
151                 printfPQExpBuffer(&conn->errorMessage,
152                         libpq_gettext("cannot determine OID of function lo_truncate\n"));
153                 return -1;
154         }
155
156         argv[0].isint = 1;
157         argv[0].len = 4;
158         argv[0].u.integer = fd;
159
160         argv[1].isint = 1;
161         argv[1].len = 4;
162         argv[1].u.integer = len;
163
164         res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate,
165                            &retval, &result_len, 1, argv, 2);
166
167         if (PQresultStatus(res) == PGRES_COMMAND_OK)
168         {
169                 PQclear(res);
170                 return retval;
171         }
172         else
173         {
174                 PQclear(res);
175                 return -1;
176         }
177 }
178
179
180 /*
181  * lo_read
182  *        read len bytes of the large object into buf
183  *
184  * returns the number of bytes read, or -1 on failure.
185  * the CALLER must have allocated enough space to hold the result returned
186  */
187
188 int
189 lo_read(PGconn *conn, int fd, char *buf, size_t len)
190 {
191         PQArgBlock      argv[2];
192         PGresult   *res;
193         int                     result_len;
194
195         if (conn->lobjfuncs == NULL)
196         {
197                 if (lo_initialize(conn) < 0)
198                         return -1;
199         }
200
201         argv[0].isint = 1;
202         argv[0].len = 4;
203         argv[0].u.integer = fd;
204
205         argv[1].isint = 1;
206         argv[1].len = 4;
207         argv[1].u.integer = len;
208
209         res = PQfn(conn, conn->lobjfuncs->fn_lo_read,
210                            (int *) buf, &result_len, 0, argv, 2);
211         if (PQresultStatus(res) == PGRES_COMMAND_OK)
212         {
213                 PQclear(res);
214                 return result_len;
215         }
216         else
217         {
218                 PQclear(res);
219                 return -1;
220         }
221 }
222
223 /*
224  * lo_write
225  *        write len bytes of buf into the large object fd
226  *
227  * returns the number of bytes written, or -1 on failure.
228  */
229 int
230 lo_write(PGconn *conn, int fd, const char *buf, size_t len)
231 {
232         PQArgBlock      argv[2];
233         PGresult   *res;
234         int                     result_len;
235         int                     retval;
236
237         if (conn->lobjfuncs == NULL)
238         {
239                 if (lo_initialize(conn) < 0)
240                         return -1;
241         }
242
243         if (len <= 0)
244                 return 0;
245
246         argv[0].isint = 1;
247         argv[0].len = 4;
248         argv[0].u.integer = fd;
249
250         argv[1].isint = 0;
251         argv[1].len = len;
252         argv[1].u.ptr = (int *) buf;
253
254         res = PQfn(conn, conn->lobjfuncs->fn_lo_write,
255                            &retval, &result_len, 1, argv, 2);
256         if (PQresultStatus(res) == PGRES_COMMAND_OK)
257         {
258                 PQclear(res);
259                 return retval;
260         }
261         else
262         {
263                 PQclear(res);
264                 return -1;
265         }
266 }
267
268 /*
269  * lo_lseek
270  *        change the current read or write location on a large object
271  * currently, only L_SET is a legal value for whence
272  *
273  */
274
275 int
276 lo_lseek(PGconn *conn, int fd, int offset, int whence)
277 {
278         PQArgBlock      argv[3];
279         PGresult   *res;
280         int                     retval;
281         int                     result_len;
282
283         if (conn->lobjfuncs == NULL)
284         {
285                 if (lo_initialize(conn) < 0)
286                         return -1;
287         }
288
289         argv[0].isint = 1;
290         argv[0].len = 4;
291         argv[0].u.integer = fd;
292
293         argv[1].isint = 1;
294         argv[1].len = 4;
295         argv[1].u.integer = offset;
296
297         argv[2].isint = 1;
298         argv[2].len = 4;
299         argv[2].u.integer = whence;
300
301         res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek,
302                            &retval, &result_len, 1, argv, 3);
303         if (PQresultStatus(res) == PGRES_COMMAND_OK)
304         {
305                 PQclear(res);
306                 return retval;
307         }
308         else
309         {
310                 PQclear(res);
311                 return -1;
312         }
313 }
314
315 /*
316  * lo_creat
317  *        create a new large object
318  * the mode is ignored (once upon a time it had a use)
319  *
320  * returns the oid of the large object created or
321  * InvalidOid upon failure
322  */
323 Oid
324 lo_creat(PGconn *conn, int mode)
325 {
326         PQArgBlock      argv[1];
327         PGresult   *res;
328         int                     retval;
329         int                     result_len;
330
331         if (conn->lobjfuncs == NULL)
332         {
333                 if (lo_initialize(conn) < 0)
334                         return InvalidOid;
335         }
336
337         argv[0].isint = 1;
338         argv[0].len = 4;
339         argv[0].u.integer = mode;
340         res = PQfn(conn, conn->lobjfuncs->fn_lo_creat,
341                            &retval, &result_len, 1, argv, 1);
342         if (PQresultStatus(res) == PGRES_COMMAND_OK)
343         {
344                 PQclear(res);
345                 return (Oid) retval;
346         }
347         else
348         {
349                 PQclear(res);
350                 return InvalidOid;
351         }
352 }
353
354 /*
355  * lo_create
356  *        create a new large object
357  * if lobjId isn't InvalidOid, it specifies the OID to (attempt to) create
358  *
359  * returns the oid of the large object created or
360  * InvalidOid upon failure
361  */
362 Oid
363 lo_create(PGconn *conn, Oid lobjId)
364 {
365         PQArgBlock      argv[1];
366         PGresult   *res;
367         int                     retval;
368         int                     result_len;
369
370         if (conn->lobjfuncs == NULL)
371         {
372                 if (lo_initialize(conn) < 0)
373                         return InvalidOid;
374         }
375
376         /* Must check this on-the-fly because it's not there pre-8.1 */
377         if (conn->lobjfuncs->fn_lo_create == 0)
378         {
379                 printfPQExpBuffer(&conn->errorMessage,
380                           libpq_gettext("cannot determine OID of function lo_create\n"));
381                 return InvalidOid;
382         }
383
384         argv[0].isint = 1;
385         argv[0].len = 4;
386         argv[0].u.integer = lobjId;
387         res = PQfn(conn, conn->lobjfuncs->fn_lo_create,
388                            &retval, &result_len, 1, argv, 1);
389         if (PQresultStatus(res) == PGRES_COMMAND_OK)
390         {
391                 PQclear(res);
392                 return (Oid) retval;
393         }
394         else
395         {
396                 PQclear(res);
397                 return InvalidOid;
398         }
399 }
400
401
402 /*
403  * lo_tell
404  *        returns the current seek location of the large object
405  *
406  */
407
408 int
409 lo_tell(PGconn *conn, int fd)
410 {
411         int                     retval;
412         PQArgBlock      argv[1];
413         PGresult   *res;
414         int                     result_len;
415
416         if (conn->lobjfuncs == NULL)
417         {
418                 if (lo_initialize(conn) < 0)
419                         return -1;
420         }
421
422         argv[0].isint = 1;
423         argv[0].len = 4;
424         argv[0].u.integer = fd;
425
426         res = PQfn(conn, conn->lobjfuncs->fn_lo_tell,
427                            &retval, &result_len, 1, argv, 1);
428         if (PQresultStatus(res) == PGRES_COMMAND_OK)
429         {
430                 PQclear(res);
431                 return retval;
432         }
433         else
434         {
435                 PQclear(res);
436                 return -1;
437         }
438 }
439
440 /*
441  * lo_unlink
442  *        delete a file
443  *
444  */
445
446 int
447 lo_unlink(PGconn *conn, Oid lobjId)
448 {
449         PQArgBlock      argv[1];
450         PGresult   *res;
451         int                     result_len;
452         int                     retval;
453
454         if (conn->lobjfuncs == NULL)
455         {
456                 if (lo_initialize(conn) < 0)
457                         return -1;
458         }
459
460         argv[0].isint = 1;
461         argv[0].len = 4;
462         argv[0].u.integer = lobjId;
463
464         res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink,
465                            &retval, &result_len, 1, argv, 1);
466         if (PQresultStatus(res) == PGRES_COMMAND_OK)
467         {
468                 PQclear(res);
469                 return retval;
470         }
471         else
472         {
473                 PQclear(res);
474                 return -1;
475         }
476 }
477
478 /*
479  * lo_import -
480  *        imports a file as an (inversion) large object.
481  *
482  * returns the oid of that object upon success,
483  * returns InvalidOid upon failure
484  */
485
486 Oid
487 lo_import(PGconn *conn, const char *filename)
488 {
489         return lo_import_internal(conn, filename, InvalidOid);
490 }
491
492 /*
493  * lo_import_with_oid -
494  *        imports a file as an (inversion) large object.
495  *        large object id can be specified.
496  *
497  * returns the oid of that object upon success,
498  * returns InvalidOid upon failure
499  */
500
501 Oid
502 lo_import_with_oid(PGconn *conn, const char *filename, Oid lobjId)
503 {
504         return lo_import_internal(conn, filename, lobjId);
505 }
506
507 static Oid
508 lo_import_internal(PGconn *conn, const char *filename, const Oid oid)
509 {
510         int                     fd;
511         int                     nbytes,
512                                 tmp;
513         char            buf[LO_BUFSIZE];
514         Oid                     lobjOid;
515         int                     lobj;
516         char            sebuf[256];
517
518         /*
519          * open the file to be read in
520          */
521         fd = open(filename, O_RDONLY | PG_BINARY, 0666);
522         if (fd < 0)
523         {                                                       /* error */
524                 printfPQExpBuffer(&conn->errorMessage,
525                                                   libpq_gettext("could not open file \"%s\": %s\n"),
526                                                   filename, pqStrerror(errno, sebuf, sizeof(sebuf)));
527                 return InvalidOid;
528         }
529
530         /*
531          * create an inversion object
532          */
533         if (oid == InvalidOid)
534                 lobjOid = lo_creat(conn, INV_READ | INV_WRITE);
535         else
536                 lobjOid = lo_create(conn, oid);
537
538         if (lobjOid == InvalidOid)
539         {
540                 /* we assume lo_create() already set a suitable error message */
541                 (void) close(fd);
542                 return InvalidOid;
543         }
544
545         lobj = lo_open(conn, lobjOid, INV_WRITE);
546         if (lobj == -1)
547         {
548                 /* we assume lo_open() already set a suitable error message */
549                 (void) close(fd);
550                 return InvalidOid;
551         }
552
553         /*
554          * read in from the file and write to the large object
555          */
556         while ((nbytes = read(fd, buf, LO_BUFSIZE)) > 0)
557         {
558                 tmp = lo_write(conn, lobj, buf, nbytes);
559                 if (tmp != nbytes)
560                 {
561                         /*
562                          * If lo_write() failed, we are now in an aborted transaction so
563                          * there's no need for lo_close(); furthermore, if we tried it
564                          * we'd overwrite the useful error result with a useless one. So
565                          * just nail the doors shut and get out of town.
566                          */
567                         (void) close(fd);
568                         return InvalidOid;
569                 }
570         }
571
572         if (nbytes < 0)
573         {
574                 printfPQExpBuffer(&conn->errorMessage,
575                                           libpq_gettext("could not read from file \"%s\": %s\n"),
576                                                   filename, pqStrerror(errno, sebuf, sizeof(sebuf)));
577                 lobjOid = InvalidOid;
578         }
579
580         (void) close(fd);
581
582         if (lo_close(conn, lobj) != 0)
583         {
584                 /* we assume lo_close() already set a suitable error message */
585                 return InvalidOid;
586         }
587
588         return lobjOid;
589 }
590
591 /*
592  * lo_export -
593  *        exports an (inversion) large object.
594  * returns -1 upon failure, 1 if OK
595  */
596 int
597 lo_export(PGconn *conn, Oid lobjId, const char *filename)
598 {
599         int                     result = 1;
600         int                     fd;
601         int                     nbytes,
602                                 tmp;
603         char            buf[LO_BUFSIZE];
604         int                     lobj;
605         char            sebuf[256];
606
607         /*
608          * open the large object.
609          */
610         lobj = lo_open(conn, lobjId, INV_READ);
611         if (lobj == -1)
612         {
613                 /* we assume lo_open() already set a suitable error message */
614                 return -1;
615         }
616
617         /*
618          * create the file to be written to
619          */
620         fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC | PG_BINARY, 0666);
621         if (fd < 0)
622         {                                                       /* error */
623                 printfPQExpBuffer(&conn->errorMessage,
624                                                   libpq_gettext("could not open file \"%s\": %s\n"),
625                                                   filename, pqStrerror(errno, sebuf, sizeof(sebuf)));
626                 (void) lo_close(conn, lobj);
627                 return -1;
628         }
629
630         /*
631          * read in from the large object and write to the file
632          */
633         while ((nbytes = lo_read(conn, lobj, buf, LO_BUFSIZE)) > 0)
634         {
635                 tmp = write(fd, buf, nbytes);
636                 if (tmp != nbytes)
637                 {
638                         printfPQExpBuffer(&conn->errorMessage,
639                                            libpq_gettext("could not write to file \"%s\": %s\n"),
640                                                   filename, pqStrerror(errno, sebuf, sizeof(sebuf)));
641                         (void) lo_close(conn, lobj);
642                         (void) close(fd);
643                         return -1;
644                 }
645         }
646
647         /*
648          * If lo_read() failed, we are now in an aborted transaction so there's no
649          * need for lo_close(); furthermore, if we tried it we'd overwrite the
650          * useful error result with a useless one. So skip lo_close() if we got a
651          * failure result.
652          */
653         if (nbytes < 0 ||
654                 lo_close(conn, lobj) != 0)
655         {
656                 /* assume lo_read() or lo_close() left a suitable error message */
657                 result = -1;
658         }
659
660         if (close(fd))
661         {
662                 printfPQExpBuffer(&conn->errorMessage,
663                                            libpq_gettext("could not write to file \"%s\": %s\n"),
664                                                   filename, pqStrerror(errno, sebuf, sizeof(sebuf)));
665                 result = -1;
666         }
667
668         return result;
669 }
670
671
672 /*
673  * lo_initialize
674  *
675  * Initialize the large object interface for an existing connection.
676  * We ask the backend about the functions OID's in pg_proc for all
677  * functions that are required for large object operations.
678  */
679 static int
680 lo_initialize(PGconn *conn)
681 {
682         PGresult   *res;
683         PGlobjfuncs *lobjfuncs;
684         int                     n;
685         const char *query;
686         const char *fname;
687         Oid                     foid;
688
689         /*
690          * Allocate the structure to hold the functions OID's
691          */
692         lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs));
693         if (lobjfuncs == NULL)
694         {
695                 printfPQExpBuffer(&conn->errorMessage,
696                                                   libpq_gettext("out of memory\n"));
697                 return -1;
698         }
699         MemSet((char *) lobjfuncs, 0, sizeof(PGlobjfuncs));
700
701         /*
702          * Execute the query to get all the functions at once.  In 7.3 and later
703          * we need to be schema-safe.  lo_create only exists in 8.1 and up.
704          * lo_truncate only exists in 8.3 and up.
705          */
706         if (conn->sversion >= 70300)
707                 query = "select proname, oid from pg_catalog.pg_proc "
708                         "where proname in ("
709                         "'lo_open', "
710                         "'lo_close', "
711                         "'lo_creat', "
712                         "'lo_create', "
713                         "'lo_unlink', "
714                         "'lo_lseek', "
715                         "'lo_tell', "
716                         "'lo_truncate', "
717                         "'loread', "
718                         "'lowrite') "
719                         "and pronamespace = (select oid from pg_catalog.pg_namespace "
720                         "where nspname = 'pg_catalog')";
721         else
722                 query = "select proname, oid from pg_proc "
723                         "where proname = 'lo_open' "
724                         "or proname = 'lo_close' "
725                         "or proname = 'lo_creat' "
726                         "or proname = 'lo_unlink' "
727                         "or proname = 'lo_lseek' "
728                         "or proname = 'lo_tell' "
729                         "or proname = 'loread' "
730                         "or proname = 'lowrite'";
731
732         res = PQexec(conn, query);
733         if (res == NULL)
734         {
735                 free(lobjfuncs);
736                 return -1;
737         }
738
739         if (res->resultStatus != PGRES_TUPLES_OK)
740         {
741                 free(lobjfuncs);
742                 PQclear(res);
743                 printfPQExpBuffer(&conn->errorMessage,
744                                                   libpq_gettext("query to initialize large object functions did not return data\n"));
745                 return -1;
746         }
747
748         /*
749          * Examine the result and put the OID's into the struct
750          */
751         for (n = 0; n < PQntuples(res); n++)
752         {
753                 fname = PQgetvalue(res, n, 0);
754                 foid = (Oid) atoi(PQgetvalue(res, n, 1));
755                 if (!strcmp(fname, "lo_open"))
756                         lobjfuncs->fn_lo_open = foid;
757                 else if (!strcmp(fname, "lo_close"))
758                         lobjfuncs->fn_lo_close = foid;
759                 else if (!strcmp(fname, "lo_creat"))
760                         lobjfuncs->fn_lo_creat = foid;
761                 else if (!strcmp(fname, "lo_create"))
762                         lobjfuncs->fn_lo_create = foid;
763                 else if (!strcmp(fname, "lo_unlink"))
764                         lobjfuncs->fn_lo_unlink = foid;
765                 else if (!strcmp(fname, "lo_lseek"))
766                         lobjfuncs->fn_lo_lseek = foid;
767                 else if (!strcmp(fname, "lo_tell"))
768                         lobjfuncs->fn_lo_tell = foid;
769                 else if (!strcmp(fname, "lo_truncate"))
770                         lobjfuncs->fn_lo_truncate = foid;
771                 else if (!strcmp(fname, "loread"))
772                         lobjfuncs->fn_lo_read = foid;
773                 else if (!strcmp(fname, "lowrite"))
774                         lobjfuncs->fn_lo_write = foid;
775         }
776
777         PQclear(res);
778
779         /*
780          * Finally check that we really got all large object interface functions
781          */
782         if (lobjfuncs->fn_lo_open == 0)
783         {
784                 printfPQExpBuffer(&conn->errorMessage,
785                                 libpq_gettext("cannot determine OID of function lo_open\n"));
786                 free(lobjfuncs);
787                 return -1;
788         }
789         if (lobjfuncs->fn_lo_close == 0)
790         {
791                 printfPQExpBuffer(&conn->errorMessage,
792                            libpq_gettext("cannot determine OID of function lo_close\n"));
793                 free(lobjfuncs);
794                 return -1;
795         }
796         if (lobjfuncs->fn_lo_creat == 0)
797         {
798                 printfPQExpBuffer(&conn->errorMessage,
799                            libpq_gettext("cannot determine OID of function lo_creat\n"));
800                 free(lobjfuncs);
801                 return -1;
802         }
803         if (lobjfuncs->fn_lo_unlink == 0)
804         {
805                 printfPQExpBuffer(&conn->errorMessage,
806                           libpq_gettext("cannot determine OID of function lo_unlink\n"));
807                 free(lobjfuncs);
808                 return -1;
809         }
810         if (lobjfuncs->fn_lo_lseek == 0)
811         {
812                 printfPQExpBuffer(&conn->errorMessage,
813                            libpq_gettext("cannot determine OID of function lo_lseek\n"));
814                 free(lobjfuncs);
815                 return -1;
816         }
817         if (lobjfuncs->fn_lo_tell == 0)
818         {
819                 printfPQExpBuffer(&conn->errorMessage,
820                                 libpq_gettext("cannot determine OID of function lo_tell\n"));
821                 free(lobjfuncs);
822                 return -1;
823         }
824         if (lobjfuncs->fn_lo_read == 0)
825         {
826                 printfPQExpBuffer(&conn->errorMessage,
827                                  libpq_gettext("cannot determine OID of function loread\n"));
828                 free(lobjfuncs);
829                 return -1;
830         }
831         if (lobjfuncs->fn_lo_write == 0)
832         {
833                 printfPQExpBuffer(&conn->errorMessage,
834                                 libpq_gettext("cannot determine OID of function lowrite\n"));
835                 free(lobjfuncs);
836                 return -1;
837         }
838
839         /*
840          * Put the structure into the connection control
841          */
842         conn->lobjfuncs = lobjfuncs;
843         return 0;
844 }