767b73814e51c159038ed431292422991bfef824
[platform/core/system/swap-probe.git] / helper / libdaprobe.c
1 /*
2  *  DA probe
3  *
4  * Copyright (c) 2000 - 2013 Samsung Electronics Co., Ltd. All rights reserved.
5  *
6  * Contact:
7  *
8  * Jaewon Lim <jaewon81.lim@samsung.com>
9  * Woojin Jung <woojin2.jung@samsung.com>
10  * Juyoung Kim <j0.kim@samsung.com>
11  * Nikita Kalyazin <n.kalyazin@samsung.com>
12  * Anastasia Lyupa <a.lyupa@samsung.com>
13  *
14  * This library is free software; you can redistribute it and/or modify it under
15  * the terms of the GNU Lesser General Public License as published by the
16  * Free Software Foundation; either version 2.1 of the License, or (at your option)
17  * any later version.
18  *
19  * This library is distributed in the hope that it will be useful, but WITHOUT ANY
20  * WARRANTY; without even the implied warranty of MERCHANTABILITY or
21  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
22  * License for more details.
23  *
24  * You should have received a copy of the GNU Lesser General Public License
25  * along with this library; if not, write to the Free Software Foundation, Inc., 51
26  * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
27  *
28  * Contributors:
29  * - S-Core Co., Ltd
30  * - Samsung RnD Institute Russia
31  *
32  */
33
34 #include <stdio.h>                      // for sprintf
35 #include <stdlib.h>                     // for getenv
36 #include <string.h>                     // for strstr
37 #include <stdbool.h>            // for bool
38 #include <stdint.h>                     // fot uint32_t,uint64_t
39 #include <stdarg.h>                     // for va_list, va_arg(__appendTypeLog)
40 #include <execinfo.h>           // for backtrace, backtrace_symbols
41 #include <unistd.h>                     // for write, alarm function, syscall
42 #include <pthread.h>            // for pthread_mutex_lock
43 #include <signal.h>
44 #include <stdint.h>
45
46 #include <sys/syscall.h>        // for syscall
47 #include <sys/time.h>           // for gettimeofday
48 #include <sys/socket.h>         // for socket, connect
49 #include <sys/un.h>                     // for sockaddr_un
50 #include <sys/timerfd.h>        // for timerfd
51
52 #include <app.h>
53 #include "probeinfo.h"
54 #include "dautil.h"
55 #include "dahelper.h"
56 #include "dacollection.h"
57 #include "da_sync.h"
58 #include "daprobe.h"
59
60 #include "binproto.h"
61
62 #define APP_INSTALL_PATH                "/opt/apps"
63 #define TISEN_APP_POSTFIX                       ".exe"
64 #define UDS_NAME                                "/tmp/da.socket"
65 #define TIMERFD_INTERVAL                100000000               // 0.1 sec
66
67 __thread int            gProbeBlockCount = 0;
68 __thread int            gProbeDepth = 0;
69 __thread pid_t          gTid = -1;
70
71 int                     g_timerfd = 0;
72 long            g_total_alloc_size = 0;
73 pthread_t       g_recvthread_id;
74
75 int log_fd = 0;
76
77 int getExecutableMappingAddress();
78
79 bool printLog(log_t* log, int msgType);
80 /******************************************************************************
81  * internal functions
82    (this means that these functions do not need to set enter/exit flag)
83  ******************************************************************************/
84
85 // runtime configure the probe option
86 static void _configure(char* configstr)
87 {
88         char buf[64];
89         gTraceInfo.optionflag = atoll(configstr);
90
91         sprintf(buf, "configure in probe : %s, %lx\n", configstr, gTraceInfo.optionflag);
92         PRINTMSG(buf);
93 }
94
95 // create socket to daemon and connect
96 static int createSocket(void)
97 {
98         ssize_t recvlen;
99         int clientLen, ret = 0;
100         struct sockaddr_un clientAddr;
101         char buf[16];
102         log_t log;
103
104         if((gTraceInfo.socket.daemonSock = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0)) != -1)
105         {
106                 bzero(&clientAddr, sizeof(clientAddr));
107                 clientAddr.sun_family = AF_UNIX;
108                 sprintf(clientAddr.sun_path, "%s", UDS_NAME);
109
110                 clientLen = sizeof(clientAddr);
111                 if(connect(gTraceInfo.socket.daemonSock, (struct sockaddr *)&clientAddr, clientLen) >= 0)
112                 {
113                         // recv initial configuration value
114                         recvlen = recv(gTraceInfo.socket.daemonSock, &log,
115                                         sizeof(log.type) + sizeof(log.length), MSG_WAITALL);
116
117                         if(recvlen > 0) // recv succeed
118                         {
119                                 if(log.length > 0)
120                                 {
121                                         if(log.length >= DA_LOG_MAX)
122                                                 log.length = DA_LOG_MAX - 1;
123                                         recvlen = recv(gTraceInfo.socket.daemonSock, log.data,
124                                                 log.length, MSG_WAITALL);
125                                 }
126                                 else
127                                 {
128                                         log.length = 0;
129                                 }
130
131                                 log.data[log.length] = '\0';
132
133                                 if(log.type == MSG_CONFIG)
134                                 {
135                                         _configure(log.data);
136                                 }
137                                 else
138                                 {
139                                         // unexpected case
140                                 }
141                         }
142                         else if(recvlen < 0)
143                         {
144                                 char buf[64];
145                                 sprintf(buf, "recv failed in socket creation with error(%d)\n", recvlen);
146                         }
147                         else    // closed by other peer
148                         {
149
150                         }
151                         sprintf(buf, "%d|%llu", getpid(), gTraceInfo.app.startTime);
152                         printLogStr(buf, MSG_PID);
153                         PRINTMSG("createSocket connect() success\n");
154                 }
155                 else
156                 {
157                         close(gTraceInfo.socket.daemonSock);
158                         gTraceInfo.socket.daemonSock = -1;
159                         ret = -1;
160                 }
161         }
162         else
163         {
164                 ret = -1;
165         }
166
167         return ret;
168 }
169
170
171 // parse backtrace string and find out the caller of probed api function
172 // return 0 if caller is user binary, otherwise return 1
173 static int determineCaller(char* tracestring)
174 {
175         char *substr;
176
177         // determine whether saveptr (caller binary name) is user binary or not
178         substr = strstr(tracestring, APP_INSTALL_PATH);
179
180         if(substr == NULL)      // not user binary
181         {
182                 return 1;
183         }
184         else                            // user binary
185         {
186 #ifdef TISENAPP
187                 substr = strstr(tracestring, TISEN_APP_POSTFIX);
188                 if(substr == NULL)
189                         return 1;
190 #endif
191                 return 0;
192         }
193 }
194
195 // return current thread id
196 static pid_t _gettid()
197 {
198         if(gTid == -1)
199                 gTid = syscall(__NR_gettid);    // syscall is very expensive
200         return gTid;
201 }
202
203 static void* recvThread(void* data)
204 {
205         fd_set readfds, workfds;
206         int maxfd = 0, rc;
207         uint64_t xtime;
208         ssize_t recvlen;
209         log_t log;
210         sigset_t profsigmask;
211
212         if(gTraceInfo.socket.daemonSock == -1)
213                 return NULL;
214
215         probeBlockStart();
216
217         sigemptyset(&profsigmask);
218         sigaddset(&profsigmask, SIGPROF);
219         pthread_sigmask(SIG_BLOCK, &profsigmask, NULL);
220
221         FD_ZERO(&readfds);
222         if(g_timerfd > 0)
223         {
224                 maxfd = g_timerfd;
225                 FD_SET(g_timerfd, &readfds);
226         }
227         if(maxfd < gTraceInfo.socket.daemonSock)
228                 maxfd = gTraceInfo.socket.daemonSock;
229         FD_SET(gTraceInfo.socket.daemonSock, &readfds);
230
231         while(1)
232         {
233                 workfds = readfds;
234                 rc = select(maxfd + 1, &workfds, NULL, NULL, NULL);
235                 if(rc < 0)
236                 {
237                         continue;
238                 }
239
240                 if(g_timerfd > 0 && FD_ISSET(g_timerfd, &workfds))
241                 {
242                         recvlen = read(g_timerfd, &xtime, sizeof(xtime));
243                         if(recvlen > 0)
244                         {
245                                 log.length = sprintf(log.data, "%ld", g_total_alloc_size);
246                                 printLog(&log, MSG_ALLOC);
247                         }
248                         else
249                         {
250                                 // read failed
251                         }
252                         continue;
253                 }
254                 else if(FD_ISSET(gTraceInfo.socket.daemonSock, &workfds))
255                 {
256                         recvlen = recv(gTraceInfo.socket.daemonSock, &log,
257                                         sizeof(log.type) + sizeof(log.length), MSG_WAITALL);
258
259                         if(recvlen > 0) // recv succeed
260                         {
261                                 if(log.length > 0)
262                                 {
263                                         if(log.length >= DA_LOG_MAX)
264                                                 log.length = DA_LOG_MAX - 1;
265                                         recvlen = recv(gTraceInfo.socket.daemonSock, log.data,
266                                                 log.length, MSG_WAITALL);
267                                 }
268                                 else
269                                 {
270                                         log.length = 0;
271                                 }
272
273                                 log.data[log.length] = '\0';
274
275                                 if(log.type == MSG_CONFIG)
276                                 {
277                                         _configure(log.data);
278                                 }
279                                 else if(log.type == MSG_STOP)
280                                 {
281                                         app_efl_exit();
282                                 }
283                                 else
284                                 {
285                                         char buf[64];
286                                         sprintf(buf, "recv unknown message(%d)\n", log.type);
287                                         continue;
288                                 }
289                         }
290                         else if(recvlen == 0)   // closed by other peer
291                         {
292                                 close(gTraceInfo.socket.daemonSock);
293                                 gTraceInfo.socket.daemonSock = -1;
294                                 break;
295                         }
296                         else    // recv error
297                         {
298                                 char buf[64];
299                                 sprintf(buf, "recv failed in recv thread with error(%d)\n", recvlen);
300                                 continue;
301                         }
302                 }
303                 else    // unknown case
304                 {
305                         continue;
306                 }
307         }
308
309         probeBlockEnd();
310         return NULL;
311 }
312
313 /*****************************************************************************
314  * initialize / finalize function
315  *****************************************************************************/
316
317 void __attribute__((constructor)) _init_probe()
318 {
319         struct timeval ttime;
320         struct itimerspec ctime;
321
322         probeBlockStart();
323
324         initialize_hash_table();
325
326         initialize_screencapture();
327
328         initialize_event();
329
330         getExecutableMappingAddress();
331
332         // get app start time
333         gettimeofday(&ttime, NULL);
334         gTraceInfo.app.startTime = (uint64_t)ttime.tv_sec * 1000 * 1000
335                 + ttime.tv_usec;
336
337         // create socket for communication with da_daemon
338         if(createSocket() == 0)
339         {
340                 // create timerfd
341                 g_timerfd = timerfd_create(CLOCK_REALTIME, TFD_CLOEXEC);
342                 if(g_timerfd > 0)
343                 {
344                         ctime.it_value.tv_sec = 0;
345                         ctime.it_value.tv_nsec = TIMERFD_INTERVAL;
346                         ctime.it_interval.tv_sec = 0;
347                         ctime.it_interval.tv_nsec = TIMERFD_INTERVAL;
348                         if(0 > timerfd_settime(g_timerfd, 0, &ctime, NULL))
349                         {
350                                 PRINTMSG("failed to set timerfd\n");
351                                 close(g_timerfd);
352                                 g_timerfd = 0;
353                         }
354                 }
355                 else
356                 {
357                         PRINTMSG("failed to create timerdf\n");
358                 }
359
360                 // create recv Thread
361                 if(pthread_create(&g_recvthread_id, NULL, recvThread, NULL) < 0)        // thread creation failed
362                 {
363                         PRINTMSG("failed to crate recv thread\n");
364                 }
365                 update_heap_memory_size(true, 0);
366         }
367         else
368         {
369
370         }
371
372         PRINTMSG("dynamic analyzer probe helper so loading...\n");
373
374         gTraceInfo.init_complete = 1;
375         probeBlockEnd();
376 }
377
378 void __attribute__((destructor)) _fini_probe()
379 {
380         int i;
381         probeBlockStart();
382
383         gTraceInfo.init_complete = -1;
384         PRINTMSG("dynamic analyzer probe helper so unloading...\n");
385
386         remove_all_glist();
387
388         // close timerfd
389         if(g_timerfd > 0)
390                 close(g_timerfd);
391
392         // close socket
393         if(gTraceInfo.socket.daemonSock != -1)
394         {
395                 printLogStr(NULL, MSG_TERMINATE);
396                 close(gTraceInfo.socket.daemonSock);
397                 gTraceInfo.socket.daemonSock = -1;
398         }
399
400         finalize_event();
401
402         finalize_screencapture();
403
404         finalize_hash_table();
405
406         for(i = 0; i < NUM_ORIGINAL_LIBRARY; i++)
407         {
408                 if(lib_handle[i] != NULL)
409                 {
410                         dlclose(lib_handle[i]);
411                 }
412         }
413
414         probeBlockEnd();
415 }
416
417
418 /**************************************************************************
419  * Helper APIs
420  **************************************************************************/
421
422 /************************************************************************
423  * manipulate and print log functions
424  ************************************************************************/
425 bool printLog(log_t *log, int msgType)
426 {
427         int res;
428         if(unlikely(gTraceInfo.socket.daemonSock == -1))
429                 return false;
430
431         if(unlikely(log == NULL))
432                 return false;
433
434         probeBlockStart();
435         log->type = msgType;
436         real_pthread_mutex_lock(&(gTraceInfo.socket.sockMutex));
437         res = send(gTraceInfo.socket.daemonSock, log, sizeof(log->type) + sizeof(log->length) + log->length, 0);
438         real_pthread_mutex_unlock(&(gTraceInfo.socket.sockMutex));
439         probeBlockEnd();
440
441         return true;
442 }
443
444 bool printLogStr(const char* str, int msgType)
445 {
446         int res;
447         log_t log;
448
449         if(unlikely(gTraceInfo.socket.daemonSock == -1))
450                 return false;
451
452         probeBlockStart();
453
454         log.type = msgType;
455         if(str)
456         {
457                 sprintf(log.data, "%s", str);
458                 log.length = strlen(str);
459         }
460         else
461         {
462                 log.length = 0;
463         }
464
465         real_pthread_mutex_lock(&(gTraceInfo.socket.sockMutex));
466         res = send(gTraceInfo.socket.daemonSock, &log, sizeof(log.type) + sizeof(log.length) + log.length, MSG_DONTWAIT);
467         real_pthread_mutex_unlock(&(gTraceInfo.socket.sockMutex));
468
469         probeBlockEnd();
470
471         return true;
472 }
473
474 // return 0 for successful case
475 // return non-zero for error
476 // if token is NULL then use DEFAULT TOKEN "`,"
477 // if token is not NULL then insert DEFAULT TOKEN before append input
478 int __appendTypeLog(log_t* log, int nInput, char* token, ...)
479 {
480         static char* default_token = DEFAULT_TOKEN;
481         va_list p_arg;
482         int i, type;
483         char* seperator = default_token;
484
485         if(nInput <= 0 || log == NULL)
486                 return -1;
487
488         probeBlockStart();
489
490         va_start(p_arg, token);
491
492         if(token != NULL)
493                 seperator = token;
494
495         for(i = 0; i < nInput; i++)
496         {
497                 type = va_arg(p_arg, int);
498
499                 if(likely(log->length > 0))     // append token or default token
500                 {
501                         if(unlikely(i == 0))
502                                 log->length += sprintf(log->data + log->length, "%s", default_token);
503                         else
504                                 log->length += sprintf(log->data + log->length, "%s", seperator);
505                 }
506
507                 switch(type)
508                 {
509                 case VT_INT:
510                         log->length += sprintf(log->data + log->length, "%d", va_arg(p_arg, int));
511                         break;
512                 case VT_UINT:
513                         log->length += sprintf(log->data + log->length, "%u", va_arg(p_arg, unsigned int));
514                         break;
515                 case VT_LONG:
516                         log->length += sprintf(log->data + log->length, "%ld", va_arg(p_arg, long));
517                         break;
518                 case VT_ULONG:
519                         log->length += sprintf(log->data + log->length, "%lu", va_arg(p_arg, unsigned long));
520                         break;
521                 case VT_STR:
522                         log->length += sprintf(log->data + log->length, "%s", va_arg(p_arg, char*));
523                         break;
524                 case VT_CHAR:   // 'char' is promoted to 'int' when passed through '...'
525                         log->length += sprintf(log->data + log->length, "%c", va_arg(p_arg, int));
526                         break;
527                 case VT_PTR:
528                         log->length += sprintf(log->data + log->length, "%p", va_arg(p_arg, void*));
529                         break;
530                 case VT_NULL:
531                         va_arg(p_arg, unsigned int);
532                         break;
533                 case VT_OFF_T:
534                         log->length += sprintf(log->data + log->length, "%ld", va_arg(p_arg, off_t));
535                         break;
536                 case VT_SIZE_T:
537                         log->length += sprintf(log->data + log->length, "%u", va_arg(p_arg, size_t));
538                         break;
539                 case VT_SSIZE_T:
540                         log->length += sprintf(log->data + log->length, "%d", va_arg(p_arg, ssize_t));
541                         break;
542                 case VT_SOCKLEN_T:
543                         log->length += sprintf(log->data + log->length, "%u", va_arg(p_arg, socklen_t));
544                         break;
545                 case VT_UINT16_T:       // 'uint16_t' is promoted to 'int' when passed through '...'
546                         log->length += sprintf(log->data + log->length, "%u", va_arg(p_arg, int));
547                         break;
548                 case VT_UINT32_T:
549                         log->length += sprintf(log->data + log->length, "%u", va_arg(p_arg, uint32_t));
550                         break;
551                 case VT_UINT64_T:
552                         log->length += sprintf(log->data + log->length, "%llu", va_arg(p_arg, uint64_t));
553                         break;
554                 case VT_MODE_T:
555                         log->length += sprintf(log->data + log->length, "%u", va_arg(p_arg, mode_t));
556                         break;
557 /*              case VT_DEV_T:
558                         log->length += sprintf(log->data + log->length, "%lu", va_arg(p_arg, dev_t));
559                         break;
560                 case VT_NFDS_T:
561                         log->length += sprintf(log->data + log->length, "%lu", va_arg(p_arg, nfds_t));
562                         break;*/
563                 default:
564                         va_end(p_arg);
565                         probeBlockEnd();
566                         return -1;
567                 }
568         }
569
570         va_end(p_arg);
571
572         probeBlockEnd();
573         return 0;
574 }
575
576 // get backtrace string
577 // return stack depth if succeed, otherwise return 0
578 // parameter 'log' cannot be null
579 int getBacktraceString(log_t* log, int bufsize)
580 {
581         void* array[MAX_STACK_DEPTH];
582         char** strings = NULL;
583         size_t i, size;
584         int initsize;
585         int stringlen;
586
587         if(log == NULL)
588                 return 0;
589
590         probeBlockStart();
591
592         initsize = log->length;
593         log->data[log->length] = '\0';  // is this necessary ?
594         size = backtrace(array, MAX_STACK_DEPTH);
595         if(likely(size > TRIM_STACK_DEPTH))
596         {
597                 strings = BACKTRACE_SYMBOLS(array + TRIM_STACK_DEPTH, size - TRIM_STACK_DEPTH);
598
599                 if(likely(strings != NULL))
600                 {
601                         for(i = TRIM_STACK_DEPTH; i < size; i++)
602                         {
603                                 stringlen = strlen(strings[i - TRIM_STACK_DEPTH]) + 14;
604                                 if(log->length + stringlen >= bufsize + initsize)
605                                         break;
606
607                                 log->length += sprintf(log->data + log->length, "%010u`,%s`,", (unsigned int)(array[i]), strings[i - TRIM_STACK_DEPTH]);
608                         }
609                         log->data[log->length-2] = '\0';
610                         log->length -= 2;
611                         free(strings);
612                 }
613                 else    // failed to get backtrace symbols
614                 {
615                         // just print trace address
616                         for(i = TRIM_STACK_DEPTH; i < size; i++)
617                         {
618                                 stringlen = 23;
619                                 if(log->length + stringlen >= bufsize + initsize)
620                                         break;
621
622                                 log->length += sprintf(log->data + log->length, "%010u`,(unknown)`,", (unsigned int)(array[i]));
623                         }
624                         log->data[log->length-2] = '\0';
625                         log->length -= 2;
626                 }
627
628                 probeBlockEnd();
629                 return (int)(size - TRIM_STACK_DEPTH);
630         }
631         else
632         {
633                 probeBlockEnd();
634                 return 0;
635         }
636 }
637
638 /*************************************************************************
639  * probe block control functions
640  *************************************************************************/
641 int preBlockBegin(void* caller, bool bFiltering, enum DaOptions option)
642 {
643         bool user = false;
644         void* tarray[1];
645         char** strings;
646
647         if(gProbeBlockCount != 0 || gProbeDepth != 0)
648                 return 0;
649
650         if(gTraceInfo.init_complete <= 0)
651                 return 0;
652
653         if((gTraceInfo.optionflag & option) == 0)
654                 return 0;
655
656         probeBlockStart();
657
658         if(gTraceInfo.exec_map.map_start != NULL)
659         {
660                 // address comparison
661                 if(caller >= gTraceInfo.exec_map.map_start &&
662                                 caller <= gTraceInfo.exec_map.map_end)
663                 {
664                         user = true;
665                 }
666                 else
667                 {
668                         // nothing to do
669                 }
670         }
671         else
672         {
673                 // backtrace for filtering
674                 tarray[0] = caller;
675                 strings = BACKTRACE_SYMBOLS(tarray, 1);
676                 if(strings != NULL)
677                 {
678                         if((determineCaller(strings[0]) == 0))
679                                 user = true;
680                         free(strings);
681                 }
682                 else
683                 {
684                         // nothing to do
685                 }
686         }
687
688         if(user)
689         {
690                 probingStart();
691                 return 2;       // user call
692         }
693         else
694         {
695                 if(bFiltering)
696                 {
697                         probeBlockEnd();
698                         return 0;       // not probing
699                 }
700                 else
701                 {
702                         probingStart();
703                         return 1;       // internal call
704                 }
705         }
706 }
707
708 int postBlockBegin(int preresult)
709 {
710         if(preresult)
711         {
712                 probeBlockStart();
713         }
714
715         return preresult;
716 }
717
718 void preBlockEnd()
719 {
720         probeBlockEnd();
721 }
722
723 void postBlockEnd()
724 {
725         probingEnd();
726         probeBlockEnd();
727 }
728
729 /*************************************************************************
730  * helper info getter functions
731  *************************************************************************/
732 // return current time in 1/10000 sec unit
733 unsigned long getCurrentTime()
734 {
735         struct timeval cTime;
736
737         gettimeofday(&cTime, NULL);
738
739         return (unsigned long)((cTime.tv_sec * 10000 + (cTime.tv_usec/100)));
740 }
741
742 unsigned int getCurrentEventIndex()
743 {
744         return gTraceInfo.index.eventIndex;
745 }
746
747 uint64_t get_current_nsec(void)
748 {
749         struct timeval tv;
750         gettimeofday(&tv, NULL);
751         return (uint64_t)tv.tv_sec * 1000 * 1000 * 1000 + tv.tv_usec * 1000;
752 }
753
754 /************************************************************************
755  * probe functions
756  ************************************************************************/
757 bool setProbePoint(probeInfo_t* iProbe)
758 {
759         if(unlikely(iProbe == NULL))
760         {
761                 return false;
762         }
763
764         probeBlockStart();
765
766         // atomic operaion(gcc builtin) is more expensive then pthread_mutex
767         real_pthread_mutex_lock(&(gTraceInfo.index.eventMutex));
768         iProbe->eventIndex = gTraceInfo.index.eventIndex++;
769         real_pthread_mutex_unlock(&(gTraceInfo.index.eventMutex));
770
771         iProbe->currentTime = getCurrentTime();
772         iProbe->pID = getpid();
773         iProbe->tID = _gettid();
774         iProbe->callDepth = gProbeDepth;
775
776         probeBlockEnd();
777         return true;
778 }
779
780 // update heap memory size through socket
781 // return 0 if size is updated through socket
782 // return 1 if size is updated into global variable
783 int update_heap_memory_size(bool isAdd, size_t size)
784 {
785         long tmp;
786         if(isAdd)
787         {
788                 tmp = __sync_add_and_fetch(&g_total_alloc_size, (long)size);
789         }
790         else
791         {
792                 tmp = __sync_sub_and_fetch(&g_total_alloc_size, (long)size);
793         }
794
795         return 0;
796 }
797
798