source: abuse/branches/lol/src/imlib/specs.cpp @ 732

Last change on this file since 732 was 732, checked in by Sam Hocevar, 8 years ago

build: SDL2 compilation fixes.

File size: 18.7 KB
Line 
1/*
2 *  Abuse - dark 2D side-scrolling platform game
3 *  Copyright (c) 1995 Crack dot Com
4 *  Copyright (c) 2005-2013 Sam Hocevar <sam@hocevar.net>
5 *
6 *  This software was released into the Public Domain. As with most public
7 *  domain software, no warranty is made or implied by Crack dot Com, by
8 *  Jonathan Clark, or by Sam Hocevar.
9 */
10
11#if HAVE_CONFIG_H
12#   include "config.h"
13#endif
14
15#include <stdio.h>
16#include <stdlib.h>
17#include <stdint.h>
18#include <ctype.h>
19#include <fcntl.h>
20#include <math.h>
21
22#if defined HAVE_UNISTD_H
23#   include <unistd.h>
24#endif
25
26#include <sys/types.h>
27#include <sys/stat.h>
28
29#include "common.h"
30
31#include "imlib/image.h"
32#include "imlib/palette.h"
33#include "imlib/specs.h"
34#include "imlib/dprint.h"
35
36char const *spec_types[] =
37{
38    "Invalid type", // 0
39    "Color table",  // 1
40    "Palette",      // 2
41    "Invalid Type", // 3
42    "AImage",        // 4
43    "Fore Tile",
44    "Back Tile",
45    "Character",
46    "8 Morph",
47    "16 Morph",
48    "Grue objs",
49    "Extern WAV",
50    "DMX MUS",
51    "Patched morph",
52    "Normal file",
53    "Compress1 file",
54    "Vector AImage",
55    "Light list",
56    "Grue fgmap",
57    "Grue bgmap",
58    "Data array",
59    "Character2",
60    "Particle",
61    "Extern lcache"
62};
63
64
65int total_files_open=0;
66char spec_main_file[100];
67
68static char *spec_prefix=NULL;
69static char *save_spec_prefix=NULL;
70
71static jFILE spec_main_jfile((FILE*)0);
72static int spec_main_fd = -1;
73static long spec_main_offset = -1;
74static SpecDir spec_main_sd;
75
76void set_filename_prefix(char const *prefix)
77{
78    if( spec_prefix )
79    {
80        free( spec_prefix );
81    }
82
83    if( prefix )
84    {
85        spec_prefix = strcpy( (char *)malloc( strlen( prefix ) + 2 ), prefix );
86        int len = strlen( prefix );
87        if( prefix[len - 1] != '\\' && prefix[len - 1] != '/')
88        {
89            spec_prefix[len] = '/';
90            spec_prefix[len + 1] = 0;
91        }
92    }
93    else
94    {
95        spec_prefix = NULL;
96    }
97}
98
99char *get_filename_prefix()
100{
101    return spec_prefix;
102}
103
104
105void set_save_filename_prefix(char const *save_prefix)
106{
107    if( save_spec_prefix )
108    {
109        free( save_spec_prefix );
110    }
111
112    if( save_prefix )
113    {
114        int len = strlen( save_prefix );
115        save_spec_prefix = (char *)malloc( len + 1 );
116        strcpy( save_spec_prefix, save_prefix );
117/* AK - Commented this out as it may cause problems
118        if( save_prefix[len - 1] != '\\' && save_prefix[len - 1] != '/' )
119        {
120            save_spec_prefix[len] = '/';
121            save_spec_prefix[len + 1] = '\0';
122        } */
123    }
124    else
125    {
126        save_spec_prefix = NULL;
127    }
128}
129
130char *get_save_filename_prefix()
131{
132  return save_spec_prefix;
133}
134
135int search_order=SPEC_SEARCH_OUTSIDE_INSIDE;
136
137static void (*no_space_handle_fun)()=NULL;
138
139void set_no_space_handler(void (*handle_fun)())
140{
141  no_space_handle_fun=handle_fun;
142}
143
144
145bFILE::bFILE()
146{
147  rbuf_size=8192;
148  rbuf=(unsigned char *)malloc(rbuf_size);
149  rbuf_start=rbuf_end=0;
150
151  wbuf_size=8192;
152  wbuf=(unsigned char *)malloc(wbuf_size);
153  wbuf_end=0;
154}
155
156bFILE::~bFILE()
157{
158  if (rbuf) free(rbuf);
159  flush_writes();
160  if (wbuf) free(wbuf);
161}
162
163int bFILE::flush_writes()
164{
165  if (wbuf_end!=0)
166  {
167    unsigned long ret=unbuffered_write(wbuf,wbuf_end);
168    if (ret!=wbuf_end && no_space_handle_fun)
169      no_space_handle_fun();
170
171    wbuf_end=0;
172    return ret;
173  }
174  return 0;
175}
176
177int bFILE::read(void *buf, size_t count)       // returns number of bytes read, calls unbuffer_read
178{
179  if (!allow_read_buffering())
180    return unbuffered_read(buf,count);
181
182  int total_read=0,error=0;
183  if (!count) return 0;
184  while (count && !error)
185  {
186    if (rbuf_start<rbuf_end)
187    {
188      unsigned int avail_size=rbuf_end-rbuf_start;
189      int copy_size=avail_size>count ? count : avail_size;
190      memcpy(buf,rbuf+rbuf_start,copy_size);
191      buf=(void *)(((unsigned char *)buf)+copy_size);
192      rbuf_start+=copy_size;
193      if (rbuf_start>=rbuf_end)
194      {
195                if (rbuf_end!=rbuf_size)  // buffer wasn't full before so there is no way we can complete read
196                  error=1;
197                rbuf_start=rbuf_end=0;
198      }
199      total_read+=copy_size;
200      count-=copy_size;
201    } else
202    {
203      rbuf_end=unbuffered_read(rbuf,rbuf_size);
204      if (rbuf_end==0) error=1;
205      rbuf_start=0;
206    }
207  }
208  return total_read;
209}
210
211
212int bFILE::write(void const *buf, size_t count)      // returns number of bytes written
213{
214  if (allow_write_buffering())
215  {
216    int total_written=0;
217    while (count)
218    {
219      int copy_size=wbuf_end+count<=wbuf_size ? count :  wbuf_size-wbuf_end;
220      memcpy(wbuf+wbuf_end,buf,copy_size);
221      wbuf_end+=copy_size;
222      count-=copy_size;
223      buf=(void *)(((char *)buf)+copy_size);
224      if (wbuf_end==wbuf_size)
225        if ((unsigned int)flush_writes()!=wbuf_size)
226      return total_written;
227
228      total_written+=copy_size;
229    }
230    return total_written;
231  } else
232  {
233    unsigned long ret=unbuffered_write(buf,count);
234    if (ret!=count && no_space_handle_fun)
235      no_space_handle_fun();
236  }
237  return 0;
238}
239
240int bFILE::seek(long offset, int whence) // whence=SEEK_SET, SEEK_CUR, SEEK_END, ret=0=success
241{
242//    rbuf_start=rbuf_end=0;
243//    unbuffered_seek(offset,SEEK_SET);
244
245  long realpos=unbuffered_tell();
246  long curpos=realpos-rbuf_end+rbuf_start;
247  if (whence==SEEK_CUR) offset+=curpos;
248  else if (whence==SEEK_END) offset=file_size()-offset;
249
250  if (offset<realpos-(long)rbuf_end || offset>=realpos)
251  {
252    rbuf_start=rbuf_end=0;
253    unbuffered_seek(offset,SEEK_SET);
254  } else
255    rbuf_start=rbuf_end-(realpos-offset);
256  return 1;
257}
258
259int bFILE::tell()
260{
261  return unbuffered_tell()-rbuf_end+rbuf_start+
262         wbuf_end;    // if this a write file, add on how much we've written
263}
264
265int bFILE::allow_read_buffering() { return 1; }
266int bFILE::allow_write_buffering() { return 1; }
267
268void set_spec_main_file(char const *filename, int Search_order)
269{
270  dprintf("Specs : main file set to %s\n",filename);
271  strcpy(spec_main_file,filename);
272  search_order=Search_order;
273
274#if (defined(__APPLE__) && !defined(__MACH__))
275  spec_main_jfile.open_external(filename,"rb",O_BINARY|O_RDONLY);
276#else
277  spec_main_jfile.open_external(filename,"rb",O_RDONLY);
278#endif
279  spec_main_fd = spec_main_jfile.get_fd();
280  if (spec_main_fd==-1)
281    return;
282  spec_main_sd.startup(&spec_main_jfile);
283}
284
285jFILE::jFILE(FILE *file_pointer)                       // assumes fp is at begining of file
286{
287  access=0;
288  fd=-1;
289  file_length=0;
290  start_offset=0;
291  flags=JFILE_CLONED;
292}
293
294void jFILE::open_external(char const *filename, char const *mode, int flags)
295{
296  int skip_size=0;
297  char tmp_name[200];
298  if (spec_prefix && filename[0] != '/')
299    sprintf(tmp_name, "%s%s", spec_prefix, filename);
300  else
301    strcpy(tmp_name, filename);
302
303//  int old_mask=umask(S_IRWXU | S_IRWXG | S_IRWXO);
304  if (flags & O_WRONLY)
305  {
306    if ((flags & O_APPEND) == 0)
307    {
308      skip_size = 1;
309      //int errval = unlink(tmp_name);
310    }
311
312    flags &= ~O_WRONLY;
313    flags |= O_CREAT | O_RDWR;
314
315#if defined S_IRUSR && defined S_IRGRP && defined S_IROTH
316    fd = open(tmp_name, flags, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
317#else
318    fd = open(tmp_name, flags);
319#endif
320  }
321  else
322  {
323    fd = open(tmp_name, flags);
324  }
325
326//  umask(old_mask);
327  if (fd >= 0 && !skip_size)
328  {
329    file_length=lseek(fd,0,SEEK_END);
330    if ((flags&O_APPEND)==0)
331      lseek(fd,0,SEEK_SET);
332    else
333        current_offset = file_length;
334    start_offset=0;
335  } else
336  {
337    file_length=0;
338    start_offset=0;
339  }
340}
341
342
343class null_file : public bFILE     // this file type will use virtual opens inside of a spe
344{
345  public :
346  virtual int open_failure() { return 1; }
347  virtual int unbuffered_read(void *buf, size_t count)   { return 0; }
348  virtual int unbuffered_write(void const *buf, size_t count)  { return 0; }
349  virtual int unbuffered_seek(long offset, int whence)   { return 0; }
350
351  virtual int unbuffered_tell() { return 0; }
352  virtual int file_size() { return 0; }
353  virtual ~null_file() { ; }
354} ;
355
356
357static bFILE *(*open_file_fun)(char const *,char const *)=NULL;
358int (*verify_file_fun)(char const *,char const *)=NULL;
359
360void set_file_opener(bFILE *(*open_fun)(char const *, char const *))
361{
362  open_file_fun=open_fun;
363}
364
365bFILE *open_file(char const *filename, char const *mode)
366{
367  if (!verify_file_fun || verify_file_fun(filename,mode))
368  {
369    if (open_file_fun)
370      return open_file_fun(filename,mode);
371    else return new jFILE(filename,mode);
372  } else return new null_file;
373}
374
375void jFILE::open_internal(char const *filename, char const *mode, int flags)
376{
377  int wr=0;
378  for (char const *s=mode; *s; s++)
379    if (toupper(*s)=='A' || toupper(*s)=='W')
380      wr=1;
381
382  if (wr)
383    fd=-1;                 // only allow extern file openings for writing
384  else
385  {
386       fd = spec_main_fd;
387    if (fd>=0)                    // if we were able to open the main file, see if it's in there
388    {
389      start_offset=0;
390      SpecEntry *se=spec_main_sd.find(filename);
391      if (se)
392      {
393    start_offset=se->offset;
394    current_offset = 0;
395    file_length=se->size;
396    rbuf_start=rbuf_end=0;
397      } else
398      {
399    close(fd);
400    fd=-1;
401      }
402    }
403  }
404}
405
406jFILE::jFILE(char const *filename, char const *access_string)      // same as fopen parameters
407{
408 flags=access=0;
409 char const *s=access_string;
410  for (; *s; s++)
411    if (toupper(*s)=='R') access=O_RDONLY;
412
413  for (s=access_string; *s; s++)
414    if (toupper(*s)=='W')
415    {
416      if (access)
417        access=O_RDWR;
418      else access=O_WRONLY;
419    }
420
421  for (s=access_string; *s; s++)
422    if (toupper(*s)=='A')
423      access|=O_APPEND|O_WRONLY;
424
425  file_length=start_offset=-1;
426  current_offset = 0;
427
428  fd=-1;
429  if (search_order==SPEC_SEARCH_OUTSIDE_INSIDE)
430    open_external(filename,access_string,access);
431
432  if (fd<0)
433    open_internal(filename,access_string,access);
434
435  if (fd<0 && search_order==SPEC_SEARCH_INSIDE_OUTSIDE)
436    open_external(filename,access_string,access);
437
438  total_files_open++;
439}
440
441jFILE::~jFILE()
442{
443  flush_writes();
444  if (fd>=0 && !(flags&JFILE_CLONED))
445  {
446    total_files_open--;
447    if (fd != spec_main_fd)
448        close(fd);
449  }
450}
451
452int jFILE::unbuffered_tell()
453{
454//    int ret = ::lseek(fd,0,SEEK_CUR) - start_offset;
455//    if (ret != current_offset)
456//        fprintf(stderr,"Bad tell %d\n",current_offset);
457    return current_offset;
458}
459
460int jFILE::unbuffered_read(void *buf, size_t count)
461{
462    unsigned long len;
463
464    if (fd == spec_main_fd)
465    {
466        if (current_offset+start_offset != spec_main_offset)
467            spec_main_offset = lseek(fd, start_offset+current_offset, SEEK_SET);
468
469        len = ::read(fd,(char*)buf,count);
470        spec_main_offset += len;
471    }
472    else
473    {
474      len = ::read(fd,(char*)buf,count);
475    }
476    current_offset += len;
477    return len;
478}
479
480int jFILE::unbuffered_write(void const *buf, size_t count)
481{
482  long ret = ::write(fd,(char*)buf,count);
483    current_offset += ret;
484    return ret;
485}
486
487int jFILE::unbuffered_seek(long offset, int whence) // whence=SEEK_SET, SEEK_CUR, SEEK_END, ret=0=success
488{
489  long ret;
490
491  switch (whence)
492  {
493    case SEEK_SET :
494    { ret = lseek(fd,start_offset+offset,SEEK_SET); } break;
495    case SEEK_END :
496    { ret = lseek(fd,start_offset+file_length-offset,SEEK_SET); } break;
497    case SEEK_CUR :
498    { ret = lseek(fd,offset,SEEK_CUR); } break;
499    default:
500        ret = -1;
501        break;
502  }
503  if (ret>=0)
504  {
505    current_offset = ret - start_offset;
506    if (spec_main_fd == fd)
507      spec_main_offset = ret;
508    return ret;
509  }
510  else
511    return -1;  // if a bad whence, then failure
512}
513
514
515uint8_t bFILE::read_uint8()
516{ uint8_t x;
517  read(&x,1);
518  return x;
519}
520
521uint16_t bFILE::read_uint16()
522{
523  uint16_t x;
524  read(&x,2);
525  return lstl(x);
526}
527
528
529uint32_t bFILE::read_uint32()
530{
531  uint32_t x;
532  read(&x,4);
533  return lltl(x);
534}
535
536void bFILE::write_uint8(uint8_t x)
537{
538  write(&x,1);
539}
540
541void bFILE::write_uint16(uint16_t x)
542{
543  x=lstl(x);
544  write(&x,2);
545}
546
547
548void bFILE::write_uint32(uint32_t x)
549{
550  x=lltl(x);
551  write(&x,4);
552}
553
554void bFILE::write_double(double x)
555{
556  double a;
557  write_uint32((long)(modf(x,&a)*(double)(1<<31)));
558  write_uint32((long)a);
559}
560
561double bFILE::read_double()
562{
563  long a,b;
564  a=read_uint32();
565  b=read_uint32();
566  return (double)b+a/(double)(1<<31);
567}
568
569SpecDir::~SpecDir()
570{
571}
572
573void SpecDir::FullyLoad(bFILE *fp)
574{
575    for (int i = 0; i < m_entries.Count(); ++i)
576    {
577        SpecEntry *se = m_entries[i];
578        free(se->data);
579        se->data = malloc(se->size);
580        fp->seek(se->offset, SEEK_SET);
581        fp->read(se->data, se->size);
582    }
583}
584
585SpecEntry::SpecEntry(uint8_t spec_type, char const *object_name,
586                     char const *link_filename,
587                     unsigned long data_size, unsigned long data_offset)
588{
589    type = spec_type;
590    name = strdup(object_name);
591    data = NULL;
592    size = data_size;
593    offset = data_offset;
594}
595
596SpecEntry::~SpecEntry()
597{
598    free(name);
599    free(data);
600}
601
602void SpecEntry::Print()
603{
604    printf("%15s%25s%8ld%8ld\n", spec_types[type], name, size, offset);
605}
606
607void SpecDir::calc_offsets()
608{
609    size_t o = SPEC_SIG_SIZE + 2;
610
611    // calculate the size of directory info
612    for (int i = 0; i < m_entries.Count(); ++i)
613        o += 1 + 1 + strlen(m_entries[i]->name) + 1 + 1 + 8;
614
615    // calculate offset for each entry
616    for (int i = 0; i < m_entries.Count(); ++i)
617    {
618        m_entries[i]->offset = o;
619        o += m_entries[i]->size;
620    }
621}
622
623SpecEntry *SpecDir::find(char const *name, int type)
624{
625    for (int i = 0; i < m_entries.Count(); ++i)
626        if (!strcmp(m_entries[i]->name, name) && m_entries[i]->type == type)
627            return m_entries[i];
628    return nullptr;
629}
630
631SpecEntry *SpecDir::find(char const *name)
632{
633    int i = find_number(name);
634    return i >= 0 ? m_entries[i] : nullptr;
635}
636
637SpecEntry *SpecDir::find(int type)
638{
639    int i = find_number(type);
640    return i >= 0 ? m_entries[i] : nullptr;
641}
642
643int SpecDir::find_number(char const *name)
644{
645    for (int i = 0; i < m_entries.Count(); ++i)
646        if (!strcmp(m_entries[i]->name, name))
647            return i;
648    return -1;
649}
650
651int SpecDir::find_number(int type)
652{
653    for (int i = 0; i < m_entries.Count(); ++i)
654        if (m_entries[i]->type == type)
655            return i;
656    return -1;
657}
658
659long SpecDir::type_total(int type)
660{
661    int ret = 0;
662    for (int i = 0; i < m_entries.Count(); ++i)
663        if (m_entries[i]->type == type)
664            ++ret;
665    return ret;
666}
667
668void SpecDir::Print()
669{
670    printf("[   Entry type   ][   Entry name   ][  Size  ][ Offset ]\n");
671    for (int i = 0; i < m_entries.Count(); ++i)
672        m_entries[i]->Print();
673}
674
675void SpecDir::startup(bFILE *fp)
676{
677    char buf[256];
678    memset(buf, 0, 256);
679    fp->read(buf, 8);
680    buf[9] = 0;
681
682    int data_size = 0;
683    if (!strcmp(buf, SPEC_SIGNATURE))
684    {
685        int total = fp->read_uint16();
686        m_entries.Resize(total);
687        long start = fp->tell();
688
689        for (int i = 0; i < total; i++)
690        {
691            fp->read(buf, 2);
692            long entry_size = sizeof(SpecEntry) + (uint8_t)buf[1];
693            entry_size = (entry_size + 3) & ~3;
694            fp->read(buf, (uint8_t)buf[1]);
695            fp->read(buf, 9);
696
697            data_size += entry_size;
698        }
699
700        m_data.Resize(data_size);
701        uint8_t *dp = m_data.Data();
702
703        fp->seek(start, SEEK_SET);
704        for (int i = 0; i < total; i++)
705        {
706            SpecEntry *se = (SpecEntry *)dp;
707
708            uint8_t len, flags, type;
709            fp->read(&type, 1);
710            fp->read(&len, 1);
711            se->type = type;
712            se->data = nullptr;
713            se->name = (char *)dp + sizeof(SpecEntry);
714            fp->read(se->name, len);
715            fp->read(&flags, 1);
716
717            se->size = fp->read_uint32();
718            se->offset = fp->read_uint32();
719            dp += ((sizeof(SpecEntry) + len) + 3) & ~3;
720
721            m_entries[i] = se;
722        }
723    }
724    else
725    {
726        m_data.Empty();
727        m_entries.Empty();
728    }
729}
730
731SpecDir::SpecDir(bFILE *fp)
732{
733    startup(fp);
734}
735
736SpecDir::SpecDir(FILE *fp)
737{
738    jFILE jfp(fp);
739    startup(&jfp);
740}
741
742SpecDir::SpecDir()
743{
744}
745
746/*
747SpecDir::SpecDir(char *filename)
748{
749  jFILE *fp;
750  if (filename)
751  {
752    fp=new jFILE(filename,"rb");
753    if (!fp->open_failure())
754      startup(fp);
755    else
756    {
757      total=0;
758      entries=NULL;
759    }
760    delete fp;
761  } else printf("NULL filename to SpecDir::SpecDir\n");
762}*/
763
764int write_string(bFILE *fp, char const *st)
765{
766  unsigned char length=strlen(st)+1;
767  if (fp->write(&length,1)!=1) return 0;
768  if (fp->write(st,length)!=length) return 0;
769  return 1;
770}
771
772long SpecDir::data_start_offset()
773{
774    if (m_entries.Count())
775        return m_entries[0]->offset;
776
777    // If no entries, then no data, but return where it would start anyway
778    return SPEC_SIG_SIZE + 2;
779}
780
781long SpecDir::data_end_offset()
782{
783    if (m_entries.Count())
784        return m_entries.Last()->offset + m_entries.Last()->size;
785
786  return SPEC_SIG_SIZE + 2;
787}
788
789int SpecDir::write(bFILE *fp)
790{
791    char sig[SPEC_SIG_SIZE];
792    strcpy(sig, SPEC_SIGNATURE);
793
794    if (fp->write(sig, sizeof(sig)) != sizeof(sig))
795        return 0;
796
797    fp->write_uint16(m_entries.Count());
798
799    for (int i = 0; i < m_entries.Count(); ++i)
800    {
801        if (fp->write(&m_entries[i]->type, 1) != 1)
802            return 0;
803        if (!write_string(fp, m_entries[i]->name))
804            return 0;
805
806        uint8_t flags = 0;
807        if (fp->write(&flags, 1) != 1)
808            return 0;
809
810        uint32_t data_size = lltl(m_entries[i]->size);
811        if (fp->write((char *)&data_size, 4) != 4)
812            return 0;
813        uint32_t offset = lltl(m_entries[i]->offset);
814        if (fp->write((char *)&offset, 4) != 4)
815            return 0;
816
817    }
818    return 1;
819}
820
821jFILE *SpecDir::write(char const *filename)
822{
823    jFILE *fp = new jFILE(filename,"wb");
824    if (fp->open_failure())
825    {
826        delete fp;
827        return nullptr;
828    }
829
830    if (!write(fp))
831    {
832        delete fp;
833        return nullptr;
834    }
835
836    return fp;
837}
838
839uint16_t read_uint16(FILE *fp)
840{
841  uint16_t x;
842  fread(&x,1,2,fp);
843  return lstl(x);
844}
845
846uint32_t read_uint32(FILE *fp)
847{
848  uint32_t x;
849  fread(&x,1,4,fp);
850  return lltl(x);
851}
852void write_uint16(FILE *fp, uint16_t x)
853{
854  x=lstl(x);
855  fwrite(&x,1,2,fp);
856}
857
858void write_uint32(FILE *fp, uint32_t x)
859{
860  x=lltl(x);
861  fwrite(&x,1,4,fp);
862}
863
864uint8_t read_uint8(FILE *fp) { return fgetc(fp)&0xff; }
865void write_uint8(FILE *fp, uint8_t x) { fputc((unsigned char)x,fp); }
866
867void SpecDir::remove(SpecEntry *e)
868{
869    for (int i = 0; i < m_entries.Count(); ++i)
870    {
871        if (m_entries[i] == e)
872        {
873            delete e;
874            m_entries.Remove(i);
875            return;
876        }
877    }
878    ASSERT(false, "bad spec entry pointer");
879}
880
881void SpecDir::add_by_hand(SpecEntry *e)
882{
883    m_entries.Push(e);
884}
885
886void SpecDir::delete_entries()   // if the directory was created by hand instead of by file
887{
888    for (int i = 0; i < m_entries.Count(); ++i)
889        delete m_entries[i];
890    m_entries.Empty();
891}
892
893void note_open_fd(int fd, char const *str)
894{
895    total_files_open++;
896}
897
898void note_close_fd(int fd)
899{
900    total_files_open--;
901}
902
903void list_open_fds()
904{
905    printf("Total open file descriptors: %d\n", total_files_open);
906}
907
Note: See TracBrowser for help on using the repository browser.