aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2012-07-24 18:47:00 +0000
committerChristian Grothoff <christian@grothoff.org>2012-07-24 18:47:00 +0000
commitf8b1feeb7128688efe30490412c224535bc27d10 (patch)
tree5bada35de50420d8af0ad53c08faee303a0e3076
parente7292e8db0955dc14a3b3e67ae709a186ea68136 (diff)
downloadlibextractor-f8b1feeb7128688efe30490412c224535bc27d10.tar.gz
libextractor-f8b1feeb7128688efe30490412c224535bc27d10.zip
-misc
-rw-r--r--src/main/Makefile.am1
-rw-r--r--src/main/extractor.c8
-rw-r--r--src/main/extractor_common.c88
-rw-r--r--src/main/extractor_common.h55
-rw-r--r--src/main/extractor_ipc.h5
-rw-r--r--src/main/extractor_plugin_main.c253
6 files changed, 249 insertions, 161 deletions
diff --git a/src/main/Makefile.am b/src/main/Makefile.am
index 09b3d05..2e190b6 100644
--- a/src/main/Makefile.am
+++ b/src/main/Makefile.am
@@ -42,6 +42,7 @@ libextractor_la_CPPFLAGS = -DPLUGINDIR=\"@RPLUGINDIR@\" -DPLUGININSTDIR=\"${plug
42libextractor_la_SOURCES = \ 42libextractor_la_SOURCES = \
43 extractor_metatypes.c \ 43 extractor_metatypes.c \
44 extractor_print.c \ 44 extractor_print.c \
45 extractor_common.c extractor_common.h \
45 extractor_plugpath.c extractor_plugpath.h \ 46 extractor_plugpath.c extractor_plugpath.h \
46 extractor_plugins.c extractor_plugins.h \ 47 extractor_plugins.c extractor_plugins.h \
47 $(EXTRACTOR_IPC) extractor_ipc.c extractor_ipc.h \ 48 $(EXTRACTOR_IPC) extractor_ipc.c extractor_ipc.h \
diff --git a/src/main/extractor.c b/src/main/extractor.c
index e012753..12f3691 100644
--- a/src/main/extractor.c
+++ b/src/main/extractor.c
@@ -53,10 +53,10 @@
53 * @param size number of bytes to write 53 * @param size number of bytes to write
54 * @return number of bytes written (that is 'size'), or -1 on error 54 * @return number of bytes written (that is 'size'), or -1 on error
55 */ 55 */
56static int 56ssize_t
57write_all (int fd, 57EXTRACTOR_write_all_ (int fd,
58 const void *buf, 58 const void *buf,
59 size_t size) 59 size_t size)
60{ 60{
61 const char *data = buf; 61 const char *data = buf;
62 size_t off = 0; 62 size_t off = 0;
diff --git a/src/main/extractor_common.c b/src/main/extractor_common.c
new file mode 100644
index 0000000..3b7f1d7
--- /dev/null
+++ b/src/main/extractor_common.c
@@ -0,0 +1,88 @@
1/*
2 This file is part of libextractor.
3 (C) 2012 Vidyut Samanta and Christian Grothoff
4
5 libextractor is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published
7 by the Free Software Foundation; either version 3, or (at your
8 option) any later version.
9
10 libextractor is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with libextractor; see the file COPYING. If not, write to the
17 Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 Boston, MA 02111-1307, USA.
19 */
20
21#include "platform.h"
22#include "extractor_common.h"
23#include "extractor.h"
24#include <sys/types.h>
25#include <signal.h>
26
27
28/**
29 * Writes 'size' bytes from 'buf' to 'fd', returns only when
30 * writing is not possible, or when all 'size' bytes were written
31 * (never does partial writes).
32 *
33 * @param fd fd to write into
34 * @param buf buffer to read from
35 * @param size number of bytes to write
36 * @return number of bytes written (that is 'size'), or -1 on error
37 */
38ssize_t
39EXTRACTOR_write_all_ (int fd,
40 const void *buf,
41 size_t size)
42{
43 const char *data = buf;
44 size_t off = 0;
45 ssize_t ret;
46
47 while (off < size)
48 {
49 ret = write (fd, &data[off], size - off);
50 if (ret <= 0)
51 return -1;
52 off += ret;
53 }
54 return size;
55}
56
57
58/**
59 * Read a buffer from a given descriptor.
60 *
61 * @param fd descriptor to read from
62 * @param buf buffer to fill
63 * @param size number of bytes to read into 'buf'
64 * @return -1 on error, size on success
65 */
66ssize_t
67EXTRACTOR_read_all_ (int fd,
68 void *buf,
69 size_t size)
70{
71 char *data = buf;
72 size_t off = 0;
73 ssize_t ret;
74
75 while (off < size)
76 {
77 ret = read (fd, &data[off], size - off);
78 if (ret <= 0)
79 return -1;
80 off += ret;
81 }
82 return size;
83
84}
85
86
87
88/* end of extractor_common.c */
diff --git a/src/main/extractor_common.h b/src/main/extractor_common.h
new file mode 100644
index 0000000..1fb90d4
--- /dev/null
+++ b/src/main/extractor_common.h
@@ -0,0 +1,55 @@
1/*
2 This file is part of libextractor.
3 (C) 2012 Vidyut Samanta and Christian Grothoff
4
5 libextractor is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published
7 by the Free Software Foundation; either version 3, or (at your
8 option) any later version.
9
10 libextractor is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with libextractor; see the file COPYING. If not, write to the
17 Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 Boston, MA 02111-1307, USA.
19 */
20
21
22#ifndef EXTRACTOR_COMMON_H
23#define EXTRACTOR_COMMON_H
24
25/**
26 * Writes 'size' bytes from 'buf' to 'fd', returns only when
27 * writing is not possible, or when all 'size' bytes were written
28 * (never does partial writes).
29 *
30 * @param fd fd to write into
31 * @param buf buffer to read from
32 * @param size number of bytes to write
33 * @return number of bytes written (that is 'size'), or -1 on error
34 */
35ssize_t
36EXTRACTOR_write_all_ (int fd,
37 const void *buf,
38 size_t size);
39
40
41/**
42 * Read a buffer from a given descriptor.
43 *
44 * @param fd descriptor to read from
45 * @param buf buffer to fill
46 * @param size number of bytes to read into 'buf'
47 * @return -1 on error, size on success
48 */
49ssize_t
50EXTRACTOR_read_all_ (int fd,
51 void *buf,
52 size_t size);
53
54
55#endif
diff --git a/src/main/extractor_ipc.h b/src/main/extractor_ipc.h
index 212d30d..71232b8 100644
--- a/src/main/extractor_ipc.h
+++ b/src/main/extractor_ipc.h
@@ -145,6 +145,11 @@ struct UpdateMessage
145 uint32_t shm_ready_bytes; 145 uint32_t shm_ready_bytes;
146 146
147 /** 147 /**
148 * Offset of the shm in the overall file.
149 */
150 uint64_t shm_off;
151
152 /**
148 * Overall size of the file. 153 * Overall size of the file.
149 */ 154 */
150 uint64_t file_size; 155 uint64_t file_size;
diff --git a/src/main/extractor_plugin_main.c b/src/main/extractor_plugin_main.c
index 0f5b79f..60d0ad1 100644
--- a/src/main/extractor_plugin_main.c
+++ b/src/main/extractor_plugin_main.c
@@ -26,6 +26,7 @@
26#include "platform.h" 26#include "platform.h"
27#include "plibc.h" 27#include "plibc.h"
28#include "extractor.h" 28#include "extractor.h"
29#include "extractor_common.h"
29#include "extractor_datasource.h" 30#include "extractor_datasource.h"
30#include "extractor_plugins.h" 31#include "extractor_plugins.h"
31#include "extractor_ipc.h" 32#include "extractor_ipc.h"
@@ -94,7 +95,6 @@ struct ProcessingContext
94}; 95};
95 96
96 97
97
98/** 98/**
99 * Moves current absolute buffer position to @pos in @whence mode. 99 * Moves current absolute buffer position to @pos in @whence mode.
100 * Will move logical position withouth shifting the buffer, if possible. 100 * Will move logical position withouth shifting the buffer, if possible.
@@ -111,55 +111,62 @@ plugin_env_seek (void *cls,
111 int whence) 111 int whence)
112{ 112{
113 struct ProcessingContext *pc = cls; 113 struct ProcessingContext *pc = cls;
114 struct SeekRequestMessage srm;
115 struct UpdateMessage um;
116 uint64_t npos;
114 117
115 switch (whence) 118 switch (whence)
116 {
117 case SEEK_CUR:
118 if (plugin->shm_pos + pos < plugin->map_size && plugin->shm_pos + pos >= 0)
119 { 119 {
120 plugin->shm_pos += pos; 120 case SEEK_CUR:
121 return plugin->fpos + plugin->shm_pos; 121 if ( (pos < 0) && (pc->read_position < - pos) )
122 } 122 return -1;
123 if (0 != pl_pick_next_buffer_at (plugin, plugin->fpos + plugin->shm_pos + pos, 1)) 123 if ( (pos > 0) &&
124 return -1; 124 ( (pc->read_position + pos < pc->read_position) ||
125 plugin->shm_pos += pos; 125 (pc->read_position + pos > pc->file_size) ) )
126 return plugin->fpos + plugin->shm_pos; 126 return -1;
127 break; 127 npos = (uint64_t) (pc->read_position + pos);
128 case SEEK_SET: 128 break;
129 if (pos < 0) 129 case SEEK_END:
130 if (pos > 0)
131 return -1;
132 pos = (int64_t) (pc->file_size + pos);
133 /* fall-through! */
134 case SEEK_SET:
135 if ( (pos < 0) || (pc->file_size < pos) )
136 return -1;
137 npos = (uint64_t) pos;
138 break;
139 default:
130 return -1; 140 return -1;
131 if (pos >= plugin->fpos && pos < plugin->fpos + plugin->map_size)
132 {
133 plugin->shm_pos = pos - plugin->fpos;
134 return pos;
135 } 141 }
136 if (0 != pl_pick_next_buffer_at (plugin, pos, 1)) 142 if ( (pc->shm_off <= npos) &&
137 return -1; 143 (pc->shm_off + pc->shm_ready_bytes > npos) )
138 if (pos >= plugin->fpos && pos < plugin->fpos + plugin->map_size)
139 { 144 {
140 plugin->shm_pos = pos - plugin->fpos; 145 pc->read_position = npos;
141 return pos; 146 return (int64_t) npos;
142 } 147 }
148 /* need to seek */
149 srm.opcode = MESSAGE_SEEK;
150 srm.reserved = 0;
151 srm.reserved2 = 0;
152 srm.requested_bytes = pc->shm_map_size;
153 if (srm.requested_bytes > pc->file_size - npos)
154 srm.requested_bytes = pc->file_size - npos;
155 srm.file_offset = npos;
156 if (-1 == EXTRACTOR_write_all_ (pc->out, &srm, sizeof (srm)))
143 return -1; 157 return -1;
144 break; 158 if (-1 == EXTRACTOR_read_all_ (pc->in, &um, sizeof (um)))
145 case SEEK_END: 159 return -1;
146 while (plugin->fsize == -1) 160 pc->shm_off = um.shm_off;
147 { 161 pc->shm_ready_bytes = um.shm_ready_bytes;
148 pl_pick_next_buffer_at (plugin, plugin->fpos + plugin->map_size + pos, 0); 162 if ( (pc->shm_off <= npos) &&
149 } 163 (pc->shm_off + pc->shm_ready_bytes > npos) )
150 if (plugin->fsize + pos - 1 < plugin->fpos || plugin->fsize + pos - 1 > plugin->fpos + plugin->map_size)
151 { 164 {
152 if (0 != pl_pick_next_buffer_at (plugin, plugin->fsize - MAX_READ, 0)) 165 pc->read_position = npos;
153 return -1; 166 return (int64_t) npos;
154 } 167 }
155 plugin->shm_pos = plugin->fsize + pos - plugin->fpos; 168 /* oops, serious missunderstanding, we asked to seek
156 if (plugin->shm_pos < 0) 169 and then were notified about a different position!? */
157 plugin->shm_pos = 0;
158 else if (plugin->shm_pos >= plugin->map_size)
159 plugin->shm_pos = plugin->map_size - 1;
160 return plugin->fpos + plugin->shm_pos - 1;
161 break;
162 }
163 return -1; 170 return -1;
164} 171}
165 172
@@ -181,6 +188,7 @@ plugin_env_read (void *cls,
181{ 188{
182 struct ProcessingContext *pc = cls; 189 struct ProcessingContext *pc = cls;
183 190
191 // FIXME!
184 *data = NULL; 192 *data = NULL;
185 if (count > MAX_READ) 193 if (count > MAX_READ)
186 return -1; 194 return -1;
@@ -203,6 +211,12 @@ plugin_env_read (void *cls,
203} 211}
204 212
205 213
214/**
215 * Provide the overall file size to plugins.
216 *
217 * @param cls the 'struct ProcessingContext'
218 * @return overall file size of the current file
219 */
206static uint64_t 220static uint64_t
207plugin_env_get_size (void *cls) 221plugin_env_get_size (void *cls)
208{ 222{
@@ -254,17 +268,17 @@ plugin_env_send_proc (void *cls,
254 hdr.data_len = data_len; 268 hdr.data_len = data_len;
255 hdr.mime_len = mime_len; 269 hdr.mime_len = mime_len;
256 if ( (sizeof (meta_byte) != 270 if ( (sizeof (meta_byte) !=
257 write_all (*cpipe_out, 271 EXTRACTOR_write_all_ (*cpipe_out,
258 &meta_byte, sizeof (meta_byte))) || 272 &meta_byte, sizeof (meta_byte))) ||
259 (sizeof (hdr) != 273 (sizeof (hdr) !=
260 write_all (*cpipe_out, 274 EXTRACTOR_write_all_ (*cpipe_out,
261 &hdr, sizeof (hdr))) || 275 &hdr, sizeof (hdr))) ||
262 (mime_len != 276 (mime_len !=
263 write_all (*cpipe_out, 277 EXTRACTOR_write_all_ (*cpipe_out,
264 data_mime_type, mime_len)) || 278 data_mime_type, mime_len)) ||
265 (data_len != 279 (data_len !=
266 write_all (*cpipe_out, 280 EXTRACTOR_write_all_ (*cpipe_out,
267 data, data_len)) ) 281 data, data_len)) )
268 return 1; 282 return 1;
269 return 0; 283 return 0;
270} 284}
@@ -284,9 +298,9 @@ handle_init_message (struct ProcessingContext *pc)
284 if (NULL != pc->shm) 298 if (NULL != pc->shm)
285 return -1; 299 return -1;
286 if (sizeof (struct InitMessage) - 1 300 if (sizeof (struct InitMessage) - 1
287 != read (pc->in, 301 != EXTRACTOR_read_all_ (pc->in,
288 &init.reserved, 302 &init.reserved,
289 sizeof (struct InitMessage) - 1)) 303 sizeof (struct InitMessage) - 1))
290 return -1; 304 return -1;
291 if (init.shm_name_length > MAX_SHM_NAME) 305 if (init.shm_name_length > MAX_SHM_NAME)
292 return -1; 306 return -1;
@@ -294,9 +308,9 @@ handle_init_message (struct ProcessingContext *pc)
294 char shm_name[init.shm_name_length + 1]; 308 char shm_name[init.shm_name_length + 1];
295 309
296 if (init.shm_name_length 310 if (init.shm_name_length
297 != read (pc->in, 311 != EXTRACTOR_read_all_ (pc->in,
298 shm_name, 312 shm_name,
299 init.shm_name_length)) 313 init.shm_name_length))
300 return -1; 314 return -1;
301 shm_name[init.shm_name_length] = '\0'; 315 shm_name[init.shm_name_length] = '\0';
302 316
@@ -333,11 +347,12 @@ handle_start_message (struct ProcessingContext *pc)
333{ 347{
334 struct StartMessage start; 348 struct StartMessage start;
335 struct EXTRACTOR_ExtractContext ec; 349 struct EXTRACTOR_ExtractContext ec;
350 char done;
336 351
337 if (sizeof (struct StartMessage) - 1 352 if (sizeof (struct StartMessage) - 1
338 != read (pc->in, 353 != EXTRACTOR_read_all_ (pc->in,
339 &start.reserved, 354 &start.reserved,
340 sizeof (struct StartMessage) - 1)) 355 sizeof (struct StartMessage) - 1))
341 return -1; 356 return -1;
342 pc->shm_ready_bytes = start.shm_ready_bytes; 357 pc->shm_ready_bytes = start.shm_ready_bytes;
343 pc->file_size = start.shm_file_size; 358 pc->file_size = start.shm_file_size;
@@ -350,6 +365,22 @@ handle_start_message (struct ProcessingContext *pc)
350 ec.get_size = &plugin_env_get_size; 365 ec.get_size = &plugin_env_get_size;
351 ec.proc = &plugin_env_send_proc; 366 ec.proc = &plugin_env_send_proc;
352 pc->plugin->extract_method (&ec); 367 pc->plugin->extract_method (&ec);
368 done = MESSAGE_DONE;
369 if (-1 == send_to_master (pc, &done, sizeof (done)))
370 return -1;
371 if ( (NULL != pc->plugin->specials) &&
372 (NULL != strstr (pc->plugin->specials, "force-kill")) )
373 {
374 /* we're required to die after each file since this
375 plugin only supports a single file at a time */
376#if !WINDOWS
377 fsync (pc->out);
378#else
379 _commit (pc->out);
380#endif
381 _exit (0);
382 }
383 return 0;
353} 384}
354 385
355 386
@@ -366,7 +397,7 @@ process_requests (struct ProcessingContext *pc)
366 { 397 {
367 unsigned char code; 398 unsigned char code;
368 399
369 if (1 != read (pc->in, &code, 1)) 400 if (1 != EXTRACTOR_read_all_ (pc->in, &code, 1))
370 break; 401 break;
371 switch (code) 402 switch (code)
372 { 403 {
@@ -390,99 +421,6 @@ process_requests (struct ProcessingContext *pc)
390 } 421 }
391} 422}
392 423
393#if 0
394 case MESSAGE_UPDATED_SHM:
395 if (plugin->operation_mode == OPMODE_DECOMPRESS)
396 {
397 read_result2 = read (in, &plugin->fpos, sizeof (int64_t));
398 read_result3 = read (in, &plugin->map_size, sizeof (size_t));
399 read_result4 = read (in, &plugin->fsize, sizeof (int64_t));
400 if ((read_result2 < sizeof (int64_t)) || (read_result3 < sizeof (size_t)) ||
401 plugin->fpos < 0 || (plugin->operation_mode != OPMODE_DECOMPRESS && (plugin->fsize <= 0 || plugin->fpos >= plugin->fsize)))
402 {
403 do_break = 1;
404 break;
405 }
406 /* FIXME: also check mapped region size (lseek for *nix, VirtualQuery for W32) */
407 /* Re-map the shm */
408#if !WINDOWS
409 if ((-1 == plugin->shm_id) ||
410 (NULL == (plugin->shm_ptr = mmap (NULL, plugin->map_size, PROT_READ, MAP_SHARED, plugin->shm_id, 0))) ||
411 (plugin->shm_ptr == (void *) -1))
412 {
413 do_break = 1;
414 break;
415 }
416#else
417 if ((plugin->map_handle == 0) ||
418 (NULL == (plugin->shm_ptr =
419 {
420 do_break = 1;
421 break;
422 }
423#endif
424 if (plugin->waiting_for_update == 1)
425 {
426 /* We were only waiting for this one message */
427 do_break = 1;
428 plugin->waiting_for_update = 2;
429 break;
430 }
431 /* Run extractor on mapped region (recursive call doesn't reach this
432 * point and breaks out earlier.
433 */
434 extract_reply = plugin->extract_method (plugin, transmit_reply, &out);
435 /* Unmap the shm */
436#if !WINDOWS
437 if ((plugin->shm_ptr != NULL) &&
438 (plugin->shm_ptr != (void*) -1) )
439 munmap (plugin->shm_ptr, plugin->map_size);
440#else
441 if (plugin->shm_ptr != NULL)
442 UnmapViewOfFile (plugin->shm_ptr);
443#endif
444 plugin->shm_ptr = NULL;
445 if (extract_reply == 1)
446 {
447 /* Tell LE that we're done */
448 unsigned char done_byte = MESSAGE_DONE;
449 if (write (out, &done_byte, 1) != 1)
450 {
451 do_break = 1;
452 break;
453 }
454 if ((plugin->specials != NULL) &&
455 (NULL != strstr (plugin->specials, "force-kill")))
456 {
457 /* we're required to die after each file since this
458 plugin only supports a single file at a time */
459#if !WINDOWS
460 fsync (out);
461#else
462 _commit (out);
463#endif
464 _exit (0);
465 }
466 }
467 else
468 {
469 /* Tell LE that we're not done, and we need to seek */
470 unsigned char seek_byte = MESSAGE_SEEK;
471 if (write (out, &seek_byte, 1) != 1)
472 {
473 do_break = 1;
474 break;
475 }
476 if (write (out, &plugin->seek_request, sizeof (int64_t)) != sizeof (int64_t))
477 {
478 do_break = 1;
479 break;
480 }
481 }
482 }
483}
484#endif
485
486 424
487#ifndef WINDOWS 425#ifndef WINDOWS
488/** 426/**
@@ -587,28 +525,29 @@ read_plugin_data (int fd)
587 SYSTEM_INFO si; 525 SYSTEM_INFO si;
588 size_t i; 526 size_t i;
589 527
528 // FIXME: check for errors from 'EXTRACTOR_read_all_'!
590 if (NULL == (ret = malloc (sizeof (struct EXTRACTOR_PluginList)))) 529 if (NULL == (ret = malloc (sizeof (struct EXTRACTOR_PluginList))))
591 return NULL; 530 return NULL;
592 GetSystemInfo (&si); 531 GetSystemInfo (&si);
593 ret->allocation_granularity = si.dwAllocationGranularity; 532 ret->allocation_granularity = si.dwAllocationGranularity;
594 read (fd, &i, sizeof (size_t)); 533 EXTRACTOR_read_all_ (fd, &i, sizeof (size_t));
595 if (NULL == (ret->libname = malloc (i))) 534 if (NULL == (ret->libname = malloc (i)))
596 { 535 {
597 free (ret); 536 free (ret);
598 return NULL; 537 return NULL;
599 } 538 }
600 read (fd, ret->libname, i); 539 EXTRACTOR_read_all_ (fd, ret->libname, i);
601 ret->libname[i - 1] = '\0'; 540 ret->libname[i - 1] = '\0';
602 read (fd, &i, sizeof (size_t)); 541 EXTRACTOR_read_all_ (fd, &i, sizeof (size_t));
603 if (NULL == (ret->short_libname = malloc (i))) 542 if (NULL == (ret->short_libname = malloc (i)))
604 { 543 {
605 free (ret->libname); 544 free (ret->libname);
606 free (ret); 545 free (ret);
607 return NULL; 546 return NULL;
608 } 547 }
609 read (fd, ret->short_libname, i); 548 EXTRACTOR_read_all_ (fd, ret->short_libname, i);
610 ret->short_libname[i - 1] = '\0'; 549 ret->short_libname[i - 1] = '\0';
611 read (fd, &i, sizeof (size_t)); 550 EXTRACTOR_read_all_ (fd, &i, sizeof (size_t));
612 if (0 == i) 551 if (0 == i)
613 { 552 {
614 ret->plugin_options = NULL; 553 ret->plugin_options = NULL;
@@ -621,7 +560,7 @@ read_plugin_data (int fd)
621 free (ret); 560 free (ret);
622 return NULL; 561 return NULL;
623 } 562 }
624 read (fd, ret->plugin_options, i); 563 EXTRACTOR_read_all_ (fd, ret->plugin_options, i);
625 ret->plugin_options[i - 1] = '\0'; 564 ret->plugin_options[i - 1] = '\0';
626 return ret; 565 return ret;
627} 566}