926cf50f90cee170e5d110588ce0ddee6e16eccb
[mw/micromonitor-lm32.git] / umon_main / target / misc / newlib_hooks.c
1 /* newlib_hooks.c:
2  * This is a basic set of hooks that allow some of the stuff
3  * in newlib to interface to a MicroMonitor based application.
4  *
5  * It hooks newlib to:
6  *      - uMon's console io (mon_putchar() & mon_getchar())
7  *      - uMon's file system (TFS)
8  *      - uMon's environment (mon_getenv() & mon_setenv())
9  *      - uMon's memory allocator (mon_malloc(), mon_free(), mon_realloc())
10  *
11  * The code assumes that fds 0, 1 & 2 are in/out/err and open() will
12  * not be called for these.  All fds above that are for TFS files.
13  * This version simply maps the incoming flags to as close of a fit
14  * to a corresponding TFS operation as possible.
15  *
16  * WARNING: not heavily tested.
17  *
18  * NOTE: this "may" be thread safe if tfdlock() and newlib_tfdunlock()
19  * are defined; however, I haven't looked at it very close.
20  *
21  * Author: Ed Sutter, with quite a bit of help from an article written
22  * by Bill Gatliff http://billgatliff.com/articles/newlib/newlib.
23  */
24 #include <stdio.h>
25 #include <reent.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <sys/stat.h>
29 #include <fcntl.h>
30 #include <unistd.h>
31 #include <errno.h>
32 #include "monlib.h"
33
34 #define REENT struct _reent
35 #define MAXFILESIZE 0x4000
36
37 /* NEWLIB_TRACE:
38  * When set, the shell variable "NEWLIB_TRACE" controls verbosity
39  * within this module.
40  */
41 #ifndef NEWLIB_TRACE
42 #define NEWLIB_TRACE 0
43 #endif
44
45 #define MAXTFDS 15
46
47 /* Define these for thread safety...
48  */
49 #ifndef newlib_tfdlock
50 #define newlib_tfdlock()
51 #endif
52
53 #ifndef newlib_tfdunlock
54 #define newlib_tfdunlock()
55 #endif
56
57 /* TFS file descriptor info:
58  */
59 struct tfdinfo {
60         int     inuse;
61         int     tfd;
62         char *buf;
63         char name[TFSNAMESIZE+1];
64         char info[TFSNAMESIZE+1];
65 } tfdtable[MAXTFDS];
66
67
68 static void
69 newlib_not_supported(REENT *rptr,char *hook)
70 {
71         mon_printf("NEWLIB HOOK '%s', NOT YET SUPPORTED\n",hook);
72         rptr->_errno = ENOTSUP;
73 }
74
75 /*******************************************************************
76  *******************************************************************
77  *
78  * Hooks to TFS:
79  * (read & write also hook to console IO)
80  */
81
82 /* open():
83  * Attempts to remap the incoming flags to TFS equivalent.
84  * Its not a perfect mapping, but gets pretty close.
85  * A comma-delimited path is supported to allow the user
86  * to specify TFS-stuff (flag string, info string, and a buffer).
87  * For example:
88  *      abc,e,script,0x400000
89  *      This is a file called "abc" that will have the TFS 'e' flag
90  *      and the TFS info field of "script".  The storage buffer is
91  *  supplied by the user at 0x400000.
92  */
93 int
94 _open_r(REENT *rptr, const char *path, int flags, int mode)
95 {
96         static int beenhere = 0;
97         long flagmode;
98         int     tfdidx, tfd;
99         struct tfdinfo *tip;
100         char *buf, *fstr, *istr, *bstr, pathcopy[TFSNAMESIZE*3+1];
101
102 #if NEWLIB_TRACE
103         if (mon_getenv("NEWLIB_TRACE"))
104                 mon_printf("_open_r(%s,0x%x,0x%x)\n",path,flags,mode);
105 #endif
106
107         if (!beenhere) {
108                 newlib_tfdlock();
109                 for(tfdidx=0;tfdidx<MAXTFDS;tfdidx++)
110                         tfdtable[tfdidx].inuse = 0;             
111
112                 tfdtable[0].inuse = 1;          /* fake entry for stdin */
113                 tfdtable[1].inuse = 1;          /* fake entry for stdout */
114                 tfdtable[2].inuse = 1;          /* fake entry for stderr */
115                 newlib_tfdunlock();
116                 beenhere = 1;
117         }
118
119         istr = fstr = bstr = buf = (char *)0;
120
121         /* Copy the incoming path to a local array so that we can safely
122          * modify the string...
123          */
124         if (strlen(path) > TFSNAMESIZE*3) {
125                 rptr->_errno = ENAMETOOLONG;
126                 return(-1);
127         }
128         strcpy(pathcopy,path);
129
130         /* The incoming string may have commas that are used to delimit the
131          * name from the TFS flag string, TFS info string and buffer.
132          * Check for the commas and test for maximum string length...
133          */
134         fstr = strchr(pathcopy,',');
135         if (fstr)  {
136                 *fstr++ = 0;
137                 istr = strchr(fstr,',');
138                 if (istr) {
139                         *istr++ = 0;
140                         bstr = strchr(istr,',');
141                         if (bstr)
142                                 *bstr++ = 0;
143                 }
144         }
145         if (strlen(pathcopy) > TFSNAMESIZE) {
146                 rptr->_errno = ENAMETOOLONG;
147                 return(-1);
148         }
149         if (istr) {
150                 if (strlen(istr) > TFSNAMESIZE) {
151                         rptr->_errno = ENAMETOOLONG;
152                         return(-1);
153                 }
154         }
155
156         /* If O_EXCL and O_CREAT are set, then fail if the file exists...
157          */
158         if ((flags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL)) {
159                 if (mon_tfsstat((char *)pathcopy)) {
160                         rptr->_errno = EEXIST;
161                         return(-1);
162                 }
163         }
164
165         /* Only a few flag combinations are supported...
166          * O_RDONLY                                              Simple read-only
167          * O_WRONLY | O_APPEND                   Each write starts at end of file
168          * O_WRONLY | O_TRUNC                    If file exists, truncate it
169          * O_WRONLY | O_CREAT                    Create if it doesn't exist
170          * O_WRONLY | O_CREAT | O_EXCL   Fail if file exists
171          */
172         switch(flags) {
173                 case O_RDONLY:
174                         flagmode = TFS_RDONLY;
175                         break;
176                 case O_WRONLY|O_APPEND:
177                         flagmode = TFS_APPEND;
178                         break;
179                 case O_WRONLY|O_TRUNC:
180                 case O_WRONLY|O_CREAT|O_TRUNC:
181                         mon_tfsunlink((char *)pathcopy);
182                         flagmode = TFS_CREATE|TFS_APPEND;
183                         break;
184                 case O_WRONLY|O_CREAT:
185                 case O_WRONLY|O_CREAT|O_APPEND:
186                         flagmode = TFS_CREATE|TFS_APPEND;
187                         break;
188                 case O_RDWR:
189                 case O_WRONLY|O_CREAT|O_EXCL:
190                         flagmode = TFS_CREATE|TFS_APPEND;
191                         break;
192                 default:
193                         mon_printf("_open_r(): flag 0x%x not supported\n",flags);
194                         rptr->_errno = ENOTSUP;
195                         return(-1);
196         }
197
198         /* Find an open slot in our tfd table:
199          */
200         newlib_tfdlock();
201         for(tfdidx=0;tfdidx<MAXTFDS;tfdidx++) {
202                 if (tfdtable[tfdidx].inuse == 0)
203                         break;
204         }
205         if (tfdidx == MAXTFDS) {
206                 newlib_tfdunlock();
207                 rptr->_errno = EMFILE;
208                 return(-1);
209         }
210         tip = &tfdtable[tfdidx];
211         tip->inuse = 1;
212         newlib_tfdunlock();
213
214         /* If file is opened for something other than O_RDONLY, then
215          * we need to allocate a buffer for the file..
216          * WARNING: It is the user's responsibility to make sure that
217          * the file size does not exceed this buffer.  Note that the
218          * buffer may be specified as part of the comma-delimited path.
219          */
220         if (flagmode == TFS_RDONLY) {
221                 buf = (char *)0;
222         }
223         else {
224                 if (bstr)
225                         buf = (char *)strtol(bstr,0,0);
226                 else
227                         buf = mon_malloc(MAXFILESIZE);
228                 if (!buf) {
229                         newlib_tfdlock();
230                         tip->inuse = 0; 
231                         newlib_tfdunlock();
232                         rptr->_errno = ENOMEM;
233                         return(-1);
234                 }
235         }
236
237         /* Deal with tfs flags and tfs info fields if necessary:
238          */
239         if (fstr) {
240                 long bflag;
241
242                 bflag = mon_tfsctrl(TFS_FATOB,(long)fstr,0);
243                 if (bflag == -1) {
244                         rptr->_errno = EINVAL;
245                         return(-1);
246                 }
247                 flagmode |= bflag;
248         }
249
250         if (istr) 
251                 strcpy(tip->info,istr);
252         else 
253                 tip->info[0] = 0;
254
255         tfd = mon_tfsopen((char *)pathcopy,flagmode,buf);
256         if (tfd >= 0) {
257                 tip->tfd = tfd; 
258                 tip->buf = buf; 
259                 strcpy(tip->name,pathcopy);
260                 return(tfdidx);
261         }
262         else {
263                 mon_printf("%s: %s\n",pathcopy,
264                         (char *)mon_tfsctrl(TFS_ERRMSG,tfd,0));
265         }
266
267         if (buf)
268                 mon_free(buf);
269
270         newlib_tfdlock();
271         tip->inuse = 0; 
272         newlib_tfdunlock();
273         rptr->_errno = EINVAL;
274         return(-1);
275 }
276
277 int
278 _close_r(REENT *rptr, int fd)
279 {
280         char *info;
281         struct tfdinfo *tip;
282
283 #if NEWLIB_TRACE
284         if (mon_getenv("NEWLIB_TRACE"))
285                 mon_printf("_close_r(%d)\n",fd);
286 #endif
287
288         if ((fd < 3) || (fd >= MAXTFDS)) {
289                 rptr->_errno = EBADF;
290                 return(-1);
291         }
292
293         tip = &tfdtable[fd];
294
295         if (tip->info[0])
296                 info = tip->info;
297         else
298                 info = (char *)0;
299
300         mon_tfsclose(tip->tfd,info);
301
302         if (tip->buf)
303                 mon_free(tip->buf);
304
305         newlib_tfdlock();
306         tip->inuse = 0; 
307         newlib_tfdunlock();
308         rptr->_errno = 0;
309         return(0);
310 }
311
312 _ssize_t
313 _write_r(REENT *rptr, int fd, const void *ptr, size_t len)
314 {
315         int     i, ret;
316
317 #if NEWLIB_TRACE
318         if (mon_getenv("NEWLIB_TRACE"))
319                 mon_printf("_write_r(%d,%d)\n",fd,len);
320 #endif
321
322         if ((fd == 1) || (fd == 2)) {
323                 for(i=0;i<len;i++) {
324                         if (*(char *)ptr == '\n')
325                                 mon_putchar('\r');
326                         mon_putchar(*(char *)ptr++);
327                 }
328                 return(len);
329         }
330         if ((fd <= 0) || (fd >= MAXTFDS)) {
331                 rptr->_errno = EBADF;
332                 return(-1);
333         }
334
335         ret = mon_tfswrite(tfdtable[fd].tfd,(char *)ptr,len);
336         if (ret < 0)
337                 return(-1);
338         else
339                 return(ret);
340 }
341
342 _ssize_t
343 _read_r(REENT *rptr, int fd, void *ptr, size_t len)
344 {
345         int     i, ret;
346         char c, *array;
347
348 #if NEWLIB_TRACE
349         if (mon_getenv("NEWLIB_TRACE"))
350                 mon_printf("_read_r(%d,%d)\n",fd,len);
351 #endif
352
353         array = (char *)ptr;
354
355         if (fd == 0) {                          /* stdin? */
356                 for(i=0;i<len;i++) {
357                         c = (char)mon_getchar();
358                         if (c == '\b') {
359                                 if (i > 0) {
360                                         mon_printf("\b \b");
361                                         i--;
362                                 }
363                                 i--;
364                         }
365                         else if ((c == '\r') || (c == '\n')) {
366                                 array[i] = '\n';
367                                 mon_printf("\r\n");
368                                 break;
369                         }
370                         else {
371                                 array[i] = c;
372                                 mon_putchar(c);
373                         }
374                 }
375                 ret = i+1;
376         }
377         else {
378                 if ((fd < 3) || (fd >= MAXTFDS)) {
379                         rptr->_errno = EBADF;
380                         return(-1);
381                 }
382
383                 ret = mon_tfsread(tfdtable[fd].tfd,ptr,len);
384                 if (ret == TFSERR_EOF)
385                         ret = 0;
386         }
387         return(ret);
388 }
389
390 int
391 _fstat_r(REENT *rptr, int fd, struct stat *sp)
392 {
393 #if NEWLIB_TRACE
394         if (mon_getenv("NEWLIB_TRACE"))
395                 printf("_fstat_r(%d)\n",fd);
396 #endif
397         newlib_not_supported(rptr,"_fstat_r");
398         return(-1);
399 }
400
401 int
402 stat(const char *path, struct stat *buf)
403 {
404         TFILE   stat;
405
406 #if NEWLIB_TRACE
407         if (mon_getenv("NEWLIB_TRACE"))
408                 printf("stat(%s)\n",path);
409 #endif
410
411         if (mon_tfsfstat((char *)path,&stat) == -1)
412                 return(-1);
413
414         /* Needs more work; however, in most apps, this is all
415          * stat is used for...
416          */
417         buf->st_size = stat.filsize;
418         return(0);
419 }
420
421 _off_t
422 _lseek_r(REENT *rptr, int fd, _off_t offset, int whence)
423 {
424         int ret;
425
426 #if NEWLIB_TRACE
427         if (mon_getenv("NEWLIB_TRACE"))
428                 printf("_lseek_r(%d,%ld,%d)\n",fd,offset,whence);
429 #endif
430
431         switch (whence) {
432                 case SEEK_END:
433                         mon_printf("_lseek_r() doesn't support SEEK_END\n");
434                         return(-1);
435                 case SEEK_CUR:
436                         whence = TFS_CURRENT;
437                         break;
438                 case SEEK_SET:
439                         whence = TFS_BEGIN;
440                         break;
441         }
442         ret = mon_tfsseek(tfdtable[fd].tfd,offset,whence);
443
444         if (ret < 0)
445                 return(-1);
446
447         return(ret);
448 }
449
450 int
451 _link_r(REENT *rptr, const char *oldname, const char *newname)
452 {
453 #if NEWLIB_TRACE
454         if (mon_getenv("NEWLIB_TRACE"))
455                 printf("_link_r(%s,%s)\n",oldname,newname);
456 #endif
457
458         newlib_not_supported(rptr,"_link_r");
459         return(-1);
460 }
461
462 int
463 _unlink_r(REENT *rptr, const char *name)
464 {
465 #if NEWLIB_TRACE
466         if (mon_getenv("NEWLIB_TRACE"))
467                 printf("_unlink_r(%s)\n",name);
468 #endif
469
470         mon_tfsunlink((char *)name);
471         return(-1);
472 }
473
474 int
475 unlink(const char *name)
476 {
477 #if NEWLIB_TRACE
478         if (mon_getenv("NEWLIB_TRACE"))
479                 printf("unlink(%s)\n",name);
480 #endif
481
482         mon_tfsunlink((char *)name);
483         return(0);
484 }
485
486 /*******************************************************************
487  *******************************************************************
488  *
489  * Environment variable stuff:
490  */
491 char *
492 getenv(const char *name)
493 {
494         return(mon_getenv((char *)name));
495 }
496
497 int
498 setenv(const char *name, const char *val, int clobber)
499 {
500         if (clobber && mon_getenv((char *)name))
501                 return(0);
502
503         mon_setenv((char *)name,(char *)val);
504         return(1);
505 }
506
507 int
508 putenv(const char *string)      /* takes a "name=value" string */
509 {
510         extern char *strdup(), *strchr();
511         char *copy, *eq;
512         
513         copy = strdup(string);
514         if (copy) {
515                 eq = strchr(copy,'=');
516                 if (eq) {
517                         *eq = 0;
518                         mon_setenv(copy,eq+1);
519                         mon_free(copy);
520                         return(0);              /* Succeeded */
521                 }
522                 mon_free(copy);
523         }
524         return(1);                              /* Failed */
525 }
526
527
528 /*******************************************************************
529  *******************************************************************
530  *
531  * Memory allocation stuff:
532  * The deallocXXX functions support the optional capability to have
533  * this code automatically release memory that was allocated by the
534  * application but not freed...
535  *
536  */
537 #ifdef DEALLOC_MAX
538 static void *dealloc_tbl[DEALLOC_MAX];
539 static int dealloc_hwm, dealloc_tot;
540
541 static void
542 dealloc_add(void *entry)
543 {
544         void **vptr = dealloc_tbl;
545
546         if (++dealloc_tot > dealloc_hwm)
547                 dealloc_hwm = dealloc_tot;
548
549         while (vptr < &dealloc_tbl[DEALLOC_MAX]) {
550                 if (*vptr  == 0) {
551                         *vptr = entry;
552                         return;
553                 }
554                 vptr++;
555         }
556 }
557
558 static void
559 dealloc_del(void *entry)
560 {
561         void **vptr = dealloc_tbl;
562
563         dealloc_tot--;
564         while (vptr < &dealloc_tbl[DEALLOC_MAX]) {
565                 if (*vptr  == entry) {
566                         *vptr = 0;
567                         return;
568                 }
569                 vptr++;
570         }
571 }
572
573 static void
574 deallocate(void)
575 {
576         void **vptr = dealloc_tbl;
577
578         while (vptr < &dealloc_tbl[DEALLOC_MAX]) {
579                 if (*vptr != 0) {
580                         mon_free((char *)*vptr);
581                 }
582                 vptr++;
583         }
584         if (mon_getenv("NEWLIB_TRACE")) {
585                 if (dealloc_hwm > DEALLOC_MAX) {
586                         mon_printf("Dealloc@exit high water mark = %d (max=%d)\n",
587                                 dealloc_hwm,DEALLOC_MAX);
588                 }
589                 mon_printf("Dealloc@exit released %d blocks\n",dealloc_tot);
590         }
591 }
592 #else
593 #define dealloc_add(a)
594 #define dealloc_del(a)
595 #define deallocate()
596 #endif
597
598
599 void *
600 _malloc_r(REENT *rptr, size_t size)
601 {
602         void *mem;
603
604         mem = mon_malloc(size);
605         dealloc_add(mem);
606         return(mem);
607 }
608
609 void *
610 _calloc_r(REENT *rptr, size_t nelem, size_t sizeelem)
611 {
612         char *mem;
613         int     size;
614
615         size = nelem*sizeelem;
616         mem = mon_malloc(size);
617         if (mem) {
618                 memset(mem,0,size);
619                 dealloc_add(mem);
620         }
621         return(mem);
622 }
623
624 void
625 _free_r(REENT *rptr, void *ptr)
626 {
627         dealloc_del(ptr);
628         return(mon_free(ptr));
629 }
630
631 void *
632 _realloc_r(REENT *rptr, void *ptr, size_t size)
633 {
634         void *mem;
635
636         mem = mon_realloc(ptr,size);
637         dealloc_add(mem);
638         return(mem);
639 }
640
641 void *
642 #ifdef USE_PTRDIFF
643 _sbrk_r(REENT *rptr, ptrdiff_t size)
644 #else
645 _sbrk_r(REENT *rptr, size_t size)
646 #endif
647 {
648 #if NEWLIB_TRACE
649         if (mon_getenv("NEWLIB_TRACE"))
650                 printf("_sbrk_r(%ld)\n",size);
651 #endif
652
653         newlib_not_supported(rptr,"_sbrk_r");
654         return(0);
655 }
656
657 /*******************************************************************
658  *******************************************************************
659  *
660  * Miscellaneous stuff:
661  */
662 #define MAX_EXIT_FUNCS  16
663
664 static int              exitftot;
665 static void             (*exitftbl[MAX_EXIT_FUNCS])();
666
667 void
668 _exit(int val)
669 {
670         int     idx;
671
672 #if NEWLIB_TRACE
673         if (mon_getenv("NEWLIB_TRACE"))
674                 printf("_exit(%d)\n",val);
675 #endif
676
677         newlib_tfdlock();
678         /* Ignore stdin/stdout/stderr slots: */
679         for(idx=3;idx<MAXTFDS;idx++) {
680                 if (tfdtable[idx].inuse) {
681                         mon_printf("WARNING: exit with '%s' left opened\n",
682                                 tfdtable[idx].name);
683                 }
684         }
685         newlib_tfdunlock();
686
687         /* Call each of the functions loaded by atexit()...
688          */
689         for(idx=0;idx<exitftot;idx++)
690                 exitftbl[idx]();
691
692         /* Release any memory that was allocated, but not freed by
693          * the application...
694          */
695         deallocate();
696
697         mon_appexit(val);
698 }
699
700 int
701 atexit(void (*func)(void))
702 {
703 #if NEWLIB_TRACE
704         if (mon_getenv("NEWLIB_TRACE"))
705                 printf("atexit(%lx)\n",(long)func);
706 #endif
707
708         if (exitftot >= MAX_EXIT_FUNCS) {
709                 mon_printf("atexit() overflow\n");
710                 return(-1);
711         }
712
713         exitftbl[exitftot++] = func;
714         return(0);
715 }
716
717
718 /*
719  * Allocate a file buffer, or switch to unbuffered I/O.
720  * Per the ANSI C standard, ALL tty devices default to line buffered.
721  *
722  * As a side effect, we set __SOPT or __SNPT (en/dis-able fseek
723  * optimisation) right after the fstat() that finds the buffer size.
724  */
725 void
726 __smakebuf(FILE *fp)
727 {
728         void *p;
729         int flags = __SNPT;
730         size_t size = 256;
731
732         if (fp->_flags & __SNBF) {
733                 fp->_bf._base = fp->_p = fp->_nbuf;
734                 fp->_bf._size = 1;
735                 return;
736         }
737         if ((p = malloc(size)) == NULL) {
738                 fp->_flags |= __SNBF;
739                 fp->_bf._base = fp->_p = fp->_nbuf;
740                 fp->_bf._size = 1;
741                 return;
742         }
743         flags |= __SMBF;
744         fp->_bf._base = fp->_p = p;
745         fp->_bf._size = size;
746
747         /* Not sure how to tell if the FP is a tty, so we just assume
748          * it is here (not sure if this is ok).
749          */
750 //      if (isatty(fp->_file))
751                 flags |= __SLBF;
752
753         fp->_flags |= flags;
754 }
755
756 /*
757  * system():
758  * Not really a newlib hook, but this is a convenient place for it...
759  */
760 int
761 system(const char *cmd)
762 {
763 #if NEWLIB_TRACE
764         if (mon_getenv("NEWLIB_TRACE"))
765                 printf("system(%s)\n",cmd);
766 #endif
767
768         mon_docommand((char *)cmd,0);
769         return(0);
770 };