source: asp3_tinet_ecnl_rx/trunk/ntshell/src/fdtable.c@ 374

Last change on this file since 374 was 374, checked in by coas-nagasima, 5 years ago

mbed関連を更新
シリアルドライバをmbedのHALを使うよう変更
ファイルディスクリプタの処理を更新

  • Property svn:eol-style set to native
  • Property svn:mime-type set to text/x-csrc;charset=UTF-8
File size: 16.9 KB
Line 
1/*
2 * TOPPERS ECHONET Lite Communication Middleware
3 *
4 * Copyright (C) 2017 Cores Co., Ltd. Japan
5 *
6 * 上記著作権者は,以下の(1)~(4)の条件を満たす場合に限り,本ソフトウェ
7 * ア(本ソフトウェアを改変したものを含む.以下同じ)を使用・複製・改
8 * 変・再配布(以下,利用と呼ぶ)することを無償で許諾する.
9 * (1) 本ソフトウェアをソースコードの形で利用する場合には,上記の著作
10 * 権表示,この利用条件および下記の無保証規定が,そのままの形でソー
11 * スコード中に含まれていること.
12 * (2) 本ソフトウェアを,ライブラリ形式など,他のソフトウェア開発に使
13 * 用できる形で再配布する場合には,再配布に伴うドキュメント(利用
14 * 者マニュアルなど)に,上記の著作権表示,この利用条件および下記
15 * の無保証規定を掲載すること.
16 * (3) 本ソフトウェアを,機器に組み込むなど,他のソフトウェア開発に使
17 * 用できない形で再配布する場合には,次のいずれかの条件を満たすこ
18 * と.
19 * (a) 再配布に伴うドキュメント(利用者マニュアルなど)に,上記の著
20 * 作権表示,この利用条件および下記の無保証規定を掲載すること.
21 * (b) 再配布の形態を,別に定める方法によって,TOPPERSプロジェクトに
22 * 報告すること.
23 * (4) 本ソフトウェアの利用により直接的または間接的に生じるいかなる損
24 * 害からも,上記著作権者およびTOPPERSプロジェクトを免責すること.
25 * また,本ソフトウェアのユーザまたはエンドユーザからのいかなる理
26 * 由に基づく請求からも,上記著作権者およびTOPPERSプロジェクトを
27 * 免責すること.
28 *
29 * 本ソフトウェアは,無保証で提供されているものである.上記著作権者お
30 * よびTOPPERSプロジェクトは,本ソフトウェアに関して,特定の使用目的
31 * に対する適合性も含めて,いかなる保証も行わない.また,本ソフトウェ
32 * アの利用により直接的または間接的に生じたいかなる損害に関しても,そ
33 * の責任を負わない.
34 *
35 * @(#) $Id$
36 */
37#include "shellif.h"
38#include <stdint.h>
39#include <kernel.h>
40#include <t_syslog.h>
41#include <t_stdlib.h>
42#include <sil.h>
43#include "syssvc/serial.h"
44#include "syssvc/syslog.h"
45#include "target_syssvc.h"
46#ifndef NTSHELL_NO_SOCKET
47#include <tinet_defs.h>
48#include <tinet_config.h>
49#include <net/net.h>
50#include <net/net_endian.h>
51#include <netinet/in.h>
52#include <netinet/in_itron.h>
53#include <tinet_nic_defs.h>
54#include <tinet_cfg.h>
55#include <netinet/in_var.h>
56#include <net/ethernet.h>
57#include <net/if6_var.h>
58#include <net/net.h>
59#include <net/if_var.h>
60#include <netinet/udp.h>
61#include <netinet/udp_var.h>
62#include <netinet/tcp.h>
63#include <netinet/tcp_var.h>
64#include <net/net_buf.h>
65#endif
66#include "ff.h"
67#include "socket_stub.h"
68#include "kernel_cfg.h"
69#include <string.h>
70#include "util/ntstdio.h"
71#include "hal/serial_api.h"
72
73static int stdio_close(struct SHELL_FILE *fp);
74static size_t stdio_read(struct SHELL_FILE *fp, unsigned char *data, size_t len);
75static size_t stdio_write(struct SHELL_FILE *fp, const unsigned char *data, size_t len);
76static size_t stdin_read(struct SHELL_FILE *fp, unsigned char *data, size_t len);
77static size_t stdout_write(struct SHELL_FILE *fp, const unsigned char *data, size_t len);
78static size_t stderr_write(struct SHELL_FILE *fp, const unsigned char *data, size_t len);
79static void stdio_delete(struct SHELL_FILE *fp);
80
81static int sio_close(struct SHELL_FILE *fp);
82static size_t sio_read(struct SHELL_FILE *fp, unsigned char *data, size_t len);
83static size_t sio_write(struct SHELL_FILE *fp, const unsigned char *data, size_t len);
84static off_t sio_seek(struct SHELL_FILE *fp, off_t ofs, int org);
85static int sio_ioctl(struct SHELL_FILE *fp, int req, void *arg);
86static bool_t sio_readable(struct SHELL_FILE *fp);
87static void sio_delete(struct SHELL_FILE *fp);
88
89IO_TYPE IO_TYPE_STDIN = { stdio_close, stdin_read, stdio_write, sio_seek, sio_ioctl, sio_readable, stdio_delete };
90IO_TYPE IO_TYPE_STDOUT = { stdio_close, stdio_read, stdout_write, sio_seek, sio_ioctl, sio_readable, stdio_delete };
91IO_TYPE IO_TYPE_STDERR = { stdio_close, stdio_read, stderr_write, sio_seek, sio_ioctl, sio_readable, stdio_delete };
92IO_TYPE IO_TYPE_SIO = { sio_close, sio_read, sio_write, sio_seek, sio_ioctl, sio_readable, sio_delete };
93ntstdio_t ntstdio;
94
95static struct SHELL_FILE fd_table[8 * sizeof(FLGPTN)] = {
96 { 0, &IO_TYPE_STDIN, 0, .exinf = &ntstdio },
97 { 1, &IO_TYPE_STDOUT, 0, .exinf = &ntstdio },
98 { 2, &IO_TYPE_STDERR, 0,.exinf = &ntstdio },
99};
100#define fd_table_count (sizeof(fd_table) / sizeof(fd_table[0]))
101
102extern ntstdio_t ntstdio;
103serial_t stdio_uart;
104
105unsigned char ntstdio_xi(struct ntstdio_t *handle)
106{
107 return serial_getc((serial_t *)handle->exinf);
108}
109
110void ntstdio_xo(struct ntstdio_t *handle, unsigned char c)
111{
112 serial_putc((serial_t *)handle->exinf, c);
113}
114
115void sys_init(intptr_t exinf)
116{
117 sys_tlsf_init();
118
119 serial_init(&stdio_uart, STDIO_UART_TX, STDIO_UART_RX);
120 serial_baud(&stdio_uart, UART_BAUDRATE);
121 serial_format(&stdio_uart, 8, ParityNone, 1);
122
123 ntstdio_init(&ntstdio, NTSTDIO_OPTION_LINE_ECHO | NTSTDIO_OPTION_CANON | NTSTDIO_OPTION_LF_CRLF | NTSTDIO_OPTION_LF_CR, ntstdio_xi, ntstdio_xo);
124 ntstdio.exinf = (void *)&stdio_uart;
125}
126
127int stdio_close(struct SHELL_FILE *fp)
128{
129 return -EPERM;
130}
131
132size_t stdio_read(struct SHELL_FILE *fp, unsigned char *data, size_t len)
133{
134 return -EPERM;
135}
136
137size_t stdio_write(struct SHELL_FILE *fp, const unsigned char *data, size_t len)
138{
139 return -EPERM;
140}
141
142size_t stdin_read(struct SHELL_FILE *fp, unsigned char *data, size_t len)
143{
144 int i = 0;
145 while (i < len) {
146 int c = ntstdio_getc((struct ntstdio_t *)fp->exinf);
147 data[i++] = c;
148 if ((c == EOF) || (c == '\n'))
149 break;
150 }
151 return i;
152}
153
154size_t stdout_write(struct SHELL_FILE *fp, const unsigned char *data, size_t len)
155{
156 for (int i = 0; i < len; i++) {
157 ntstdio_putc((struct ntstdio_t *)fp->exinf, data[i]);
158 }
159 return len;
160}
161
162size_t stderr_write(struct SHELL_FILE *fp, const unsigned char *data, size_t len)
163{
164 for (int i = 0; i < len; i++) {
165 ntstdio_putc((struct ntstdio_t *)fp->exinf, data[i]);
166 }
167 return len;
168}
169
170void stdio_delete(struct SHELL_FILE *fp)
171{
172}
173
174int sio_close(struct SHELL_FILE *fp)
175{
176 return -EPERM;
177}
178
179size_t sio_read(struct SHELL_FILE *fp, unsigned char *data, size_t len)
180{
181 return -EPERM;
182}
183
184size_t sio_write(struct SHELL_FILE *fp, const unsigned char *data, size_t len)
185{
186 return -EPERM;
187}
188
189off_t sio_seek(struct SHELL_FILE *fp, off_t ofs, int org)
190{
191 return -EPERM;
192}
193
194int sio_ioctl(struct SHELL_FILE *fp, int request, void *arg)
195{
196 switch (request) {
197 case TIOCGWINSZ:
198 return 0;
199 case TCGETS:
200 return sio_tcgetattr(fp->fd, (struct termios *)arg);
201 case TCSETS + TCSANOW:
202 case TCSETS + TCSADRAIN:
203 case TCSETS + TCSAFLUSH:
204 return sio_tcsetattr(fp->fd, request - TCSETS, (const struct termios *)arg);
205 }
206
207 return -EINVAL;
208}
209
210bool_t sio_readable(struct SHELL_FILE *fp)
211{
212 return fp->readevt_w != fp->readevt_r;
213}
214
215void sio_delete(struct SHELL_FILE *fp)
216{
217 free((serial_t *)((struct ntstdio_t *)fp->exinf)->exinf);
218 ((struct ntstdio_t *)fp->exinf)->exinf = NULL;
219 free((struct ntstdio_t *)fp->exinf);
220 fp->exinf = NULL;
221}
222
223struct SHELL_FILE *new_fp(IO_TYPE *type, int id, int writable)
224{
225 struct SHELL_FILE *fp = NULL;
226 ER ret;
227
228 ret = wai_sem(SEM_FILEDESC);
229 if (ret < 0) {
230 syslog(LOG_ERROR, "wai_sem => %d", ret);
231 }
232
233 for (int fd = 3; fd < fd_table_count; fd++) {
234 fp = &fd_table[fd];
235 if (fp->type != NULL)
236 continue;
237
238 memset(fp, 0, sizeof(struct SHELL_FILE));
239 fp->fd = fd;
240 fp->type = type;
241 fp->handle = id;
242 fp->writable = writable;
243 break;
244 }
245
246 ret = sig_sem(SEM_FILEDESC);
247 if (ret < 0) {
248 syslog(LOG_ERROR, "sig_sem => %d", ret);
249 }
250
251 return fp;
252}
253
254struct SHELL_FILE *id_to_fd(IO_TYPE *type, int id)
255{
256 struct SHELL_FILE *fp = NULL;
257 ER ret;
258
259 ret = wai_sem(SEM_FILEDESC);
260 if (ret < 0) {
261 syslog(LOG_ERROR, "wai_sem => %d", ret);
262 }
263
264 for (int fd = 3; fd < fd_table_count; fd++) {
265 fp = &fd_table[fd];
266 if ((fp->type == type) && (fp->handle == id))
267 break;
268 }
269
270 ret = sig_sem(SEM_FILEDESC);
271 if (ret < 0) {
272 syslog(LOG_ERROR, "sig_sem => %d", ret);
273 }
274
275 return fp;
276}
277
278int delete_fd_by_id(IO_TYPE *type, int id)
279{
280 struct SHELL_FILE *fp = id_to_fd(type, id);
281 if (fp == NULL)
282 return -EBADF;
283
284 return delete_fp(fp);
285}
286
287int delete_fp(struct SHELL_FILE *fp)
288{
289 ER ret;
290
291 fp->type->delete(fp);
292
293 ret = wai_sem(SEM_FILEDESC);
294 if (ret < 0) {
295 syslog(LOG_ERROR, "wai_sem => %d", ret);
296 }
297
298 memset(fp, 0, sizeof(struct SHELL_FILE));
299
300 ret = sig_sem(SEM_FILEDESC);
301 if (ret < 0) {
302 syslog(LOG_ERROR, "sig_sem => %d", ret);
303 }
304
305 return 0;
306}
307
308struct SHELL_FILE *fd_to_fp(int fd)
309{
310 if ((fd < 0) || (fd >= fd_table_count))
311 return NULL;
312 return &fd_table[fd];
313}
314
315void memand(void *dst, void *src, size_t len)
316{
317 uint8_t *d = (uint8_t *)dst;
318 uint8_t *s = (uint8_t *)src;
319 uint8_t *e = &s[len];
320
321 while (s < e) {
322 *d++ &= *s++;
323 }
324}
325
326struct fd_events {
327 int count;
328 fd_set readfds;
329 fd_set writefds;
330 fd_set errorfds;
331};
332
333ER shell_get_evts(struct fd_events *evts, TMO tmout);
334
335#define TMO_MAX INT_MAX
336
337int shell_select(int n, fd_set *__restrict rfds, fd_set *__restrict wfds, fd_set *__restrict efds, struct timeval *__restrict tv)
338{
339 ER ret;
340 TMO tmout = TMO_FEVR;
341 struct fd_events evts;
342
343 if (tv != NULL) {
344 if (tv->tv_sec < (TMO_MAX / 1000000))
345 tmout = tv->tv_sec * 1000000 + tv->tv_usec;
346 else
347 tmout = TMO_MAX;
348 }
349
350 if (rfds != NULL)
351 memcpy(&evts.readfds, rfds, sizeof(fd_set));
352 else
353 memset(&evts.readfds, 0, sizeof(fd_set));
354 if (wfds != NULL)
355 memcpy(&evts.writefds, wfds, sizeof(fd_set));
356 else
357 memset(&evts.writefds, 0, sizeof(fd_set));
358 if (efds != NULL)
359 memcpy(&evts.errorfds, efds, sizeof(fd_set));
360 else
361 memset(&evts.errorfds, 0, sizeof(fd_set));
362 evts.count = 0;
363
364 ret = shell_get_evts(&evts, tmout);
365 if (ret == E_OK) {
366 if (rfds != NULL)
367 memand(rfds, &evts.readfds, sizeof(fd_set));
368 if (wfds != NULL)
369 memand(wfds, &evts.writefds, sizeof(fd_set));
370 if (efds != NULL)
371 memand(efds, &evts.errorfds, sizeof(fd_set));
372 return evts.count;
373 }
374 if (ret == E_TMOUT) {
375 if (rfds != NULL)
376 memset(rfds, 0, sizeof(fd_set));
377 if (wfds != NULL)
378 memset(wfds, 0, sizeof(fd_set));
379 if (efds != NULL)
380 memset(efds, 0, sizeof(fd_set));
381 return 0;
382 }
383
384 return -EBADF;
385}
386
387int shell_poll(struct pollfd *fds, nfds_t nfds, int timeout)
388{
389 ER ret;
390 TMO tmout;
391 struct fd_events evts;
392
393 if(timeout < 0)
394 tmout = TMO_FEVR;
395 else if (timeout < (TMO_MAX / 1000))
396 tmout = timeout * 1000;
397 else
398 tmout = TMO_MAX;
399
400 memset(&evts, 0, sizeof(evts));
401
402 for (int i = 0; i < nfds; i++) {
403 struct pollfd *pfd = &fds[i];
404 int fd = pfd->fd;
405 if ((fd < 0) || (fd >= fd_table_count))
406 continue;
407
408 if (pfd->events & POLLIN)
409 FD_SET(fd, &evts.readfds);
410 if (pfd->events & POLLOUT)
411 FD_SET(fd, &evts.writefds);
412 if (pfd->events & POLLERR)
413 FD_SET(fd, &evts.errorfds);
414 pfd->revents = 0;
415 }
416
417 ret = shell_get_evts(&evts, tmout);
418 if (ret == E_OK) {
419 int result = 0;
420 for (int i = 0; i < nfds; i++) {
421 struct pollfd *pfd = &fds[i];
422 int fd = pfd->fd;
423 if ((fd < 0) || (fd >= fd_table_count))
424 continue;
425
426 if (FD_ISSET(fd, &evts.readfds))
427 pfd->revents |= POLLIN;
428 if (FD_ISSET(fd, &evts.writefds))
429 pfd->revents |= POLLOUT;
430 if (FD_ISSET(fd, &evts.errorfds))
431 pfd->revents |= POLLERR;
432 if (pfd->revents != 0)
433 result++;
434 }
435 return result;
436 }
437 if (ret == E_TMOUT) {
438 return 0;
439 }
440
441 return -EBADF;
442}
443
444/* TODO:コールバック化したい */
445void stdio_update_evts()
446{
447 int fd = STDIN_FILENO;
448 struct SHELL_FILE *fp = &fd_table[fd];
449 FLGPTN flgptn = 0;
450
451 if (serial_readable((serial_t *)((struct ntstdio_t *)fp->exinf)->exinf)) {
452 if (fp->readevt_w == fp->readevt_r) fp->readevt_w++;
453
454 FD_SET(fd, (fd_set *)&flgptn);
455 }
456 if (serial_writable((serial_t *)((struct ntstdio_t *)fp->exinf)->exinf)) {
457 if (fp->writeevt_w == fp->writeevt_r) fp->writeevt_w++;
458
459 FD_SET(fd, (fd_set *)&flgptn);
460 }
461
462 if (flgptn != 0) {
463 set_flg(FLG_SELECT_WAIT, flgptn);
464 }
465}
466
467/* TODO:コールバック化したい */
468void stdio_flgptn(FLGPTN *flgptn)
469{
470 int fd = STDIN_FILENO;
471 struct SHELL_FILE *fp = &fd_table[fd];
472 *flgptn = 0;
473
474 if (serial_readable((serial_t *)((struct ntstdio_t *)fp->exinf)->exinf)) {
475 if (fp->readevt_w == fp->readevt_r) fp->readevt_w++;
476
477 FD_SET(fd, (fd_set *)flgptn);
478 }
479 if (serial_writable((serial_t *)((struct ntstdio_t *)fp->exinf)->exinf)) {
480 if (fp->writeevt_w == fp->writeevt_r) fp->writeevt_w++;
481
482 FD_SET(fd, (fd_set *)flgptn);
483 }
484}
485
486ER shell_get_evts(struct fd_events *evts, TMO tmout)
487{
488 int count = 0;
489 SYSTIM prev, now;
490
491 get_tim(&prev);
492
493 for (;;) {
494 ER ret;
495 FLGPTN waitptn, flgptn, readfds = 0, writefds = 0;
496 struct SHELL_FILE *fp = NULL;
497
498 stdio_update_evts();
499
500#ifndef NTSHELL_NO_SOCKET
501 waitptn = *((FLGPTN *)&evts->errorfds);
502#else
503 waitptn = *((FLGPTN *)&evts->readfds) | *((FLGPTN *)&evts->errorfds);
504#endif
505 for (int fd = 0; fd < fd_table_count; fd++) {
506 fp = &fd_table[fd];
507
508#ifndef NTSHELL_NO_SOCKET
509 if (FD_ISSET(fd, &evts->readfds)) {
510 if (fp->type->readable(fp)) {
511 FD_SET(fd, (fd_set *)&readfds);
512 count++;
513 if (fp->readevt_w == fp->readevt_r) fp->readevt_r--;
514 }
515 else {
516 FD_SET(fd, (fd_set *)&waitptn);
517 }
518 }
519#endif
520 if (FD_ISSET(fd, &evts->writefds)) {
521 if (fp->writeevt_w == fp->writeevt_r) {
522 FD_SET(fd, (fd_set *)&writefds);
523 count++;
524 if (fp->writeevt_w == fp->writeevt_r) fp->writeevt_r--;
525 }
526 else {
527 FD_SET(fd, (fd_set *)&waitptn);
528 }
529 }
530 }
531 memset(evts, 0, sizeof(*evts));
532
533 if (waitptn == 0) {
534 memcpy(&evts->readfds, &readfds, sizeof(evts->readfds));
535 memcpy(&evts->writefds, &writefds, sizeof(evts->writefds));
536 evts->count = count;
537 return E_OK;
538 }
539 else if ((readfds | writefds) != 0) {
540 set_flg(FLG_SELECT_WAIT, (readfds | writefds));
541 }
542
543 /* イベント待ち */
544 flgptn = 0;
545 ret = twai_flg(FLG_SELECT_WAIT, waitptn, TWF_ORW, &flgptn, tmout);
546 if (ret != E_OK) {
547 if (ret != E_TMOUT) {
548 syslog(LOG_ERROR, "twai_flg => %d", ret);
549 return ret;
550 }
551
552 stdio_flgptn(&flgptn);
553
554 if (flgptn == 0)
555 return E_TMOUT;
556 }
557 flgptn &= waitptn;
558
559 /* 受け取ったフラグのみクリア */
560 ret = clr_flg(FLG_SELECT_WAIT, ~flgptn);
561 if (ret != E_OK) {
562 syslog(LOG_ERROR, "clr_flg => %d", ret);
563 }
564
565 count = 0;
566 for (int fd = 0; fd < fd_table_count; fd++) {
567 if (!FD_ISSET(fd, (fd_set *)&waitptn))
568 continue;
569
570 fp = &fd_table[fd];
571
572 if (fp->readevt_w != fp->readevt_r) {
573 fp->readevt_r++;
574 FD_SET(fd, &evts->readfds);
575 count++;
576 }
577 if (fp->writeevt_w != fp->writeevt_r) {
578 fp->writeevt_r++;
579 fp->writable = 1;
580 }
581 if (fp->writable) {
582 FD_SET(fd, &evts->writefds);
583 count++;
584 }
585 if (fp->errorevt_w != fp->errorevt_r) {
586 fp->errorevt_r++;
587 FD_SET(fd, &evts->errorfds);
588 count++;
589 }
590 }
591
592 if (count > 0)
593 break;
594
595 get_tim(&now);
596
597 SYSTIM elapse = now - prev;
598 if (elapse > tmout)
599 return E_TMOUT;
600
601 prev = now;
602 tmout -= elapse;
603 }
604
605 evts->count = count;
606
607 return E_OK;
608}
609
610void clean_fd()
611{
612 struct SHELL_FILE *fp = NULL;
613 for (int fd = 3; fd < fd_table_count; fd++) {
614 fp = &fd_table[fd];
615 if ((fp->type == 0) || (fp->fd == 0))
616 continue;
617
618 fp->type->close(fp);
619
620 delete_fp(fp);
621 }
622}
623
624int shell_ioctl(int fd, int request, void *arg)
625{
626 struct SHELL_FILE *fp = fd_to_fp(fd);
627 if (fp == NULL)
628 return -EBADF;
629
630 return fp->type->ioctl(fp, request, arg);
631}
632
633#ifdef NTSHELL_NO_SOCKET
634
635int shell_socket(int family, int type, int protocol)
636{
637 return -ENOMEM;
638}
639
640int shell_bind(int fd, const struct sockaddr *addr, socklen_t len)
641{
642 return -ENOMEM;
643}
644
645int shell_listen(int fd, int backlog)
646{
647 return -ENOMEM;
648}
649
650int shell_connect(int fd, const struct sockaddr *addr, socklen_t len)
651{
652 return -ENOMEM;
653}
654
655int shell_accept(int fd, struct sockaddr *__restrict addr, socklen_t *__restrict len)
656{
657 return -ENOMEM;
658}
659
660ssize_t shell_sendto(int fd, const void *buf, size_t len, int flags, const struct sockaddr *addr, socklen_t alen)
661{
662 return -ENOMEM;
663}
664
665ssize_t shell_sendmsg(int fd, const struct msghdr *msg, int flags)
666{
667 return -ENOMEM;
668}
669
670ssize_t shell_recvfrom(int fd, void *__restrict buf, size_t len, int flags, struct sockaddr *__restrict addr, socklen_t *__restrict alen)
671{
672 return -ENOMEM;
673}
674
675ssize_t shell_recvmsg(int fd, struct msghdr *msg, int flags)
676{
677 return -ENOMEM;
678}
679
680int shell_shutdown(int fd, int how)
681{
682 return -ENOMEM;
683}
684
685int shell_getsockopt(int fd, int level, int optname, void *optval, socklen_t *__restrict optlen)
686{
687 return -ENOMEM;
688}
689
690int shell_setsockopt(int fd, int level, int optname, const void *optval, socklen_t optlen)
691{
692 return -ENOMEM;
693}
694
695int shell_getpeername(int fd, struct sockaddr *restrict addr, socklen_t *restrict len)
696{
697 return -ENOMEM;
698}
699
700int shell_getsockname(int fd, struct sockaddr *restrict addr, socklen_t *restrict len)
701{
702 return -ENOMEM;
703}
704#endif
Note: See TracBrowser for help on using the repository browser.