source: rtems/testsuites/psxtests/psxfile01/test.c @ 99e6fb52

4.115
Last change on this file since 99e6fb52 was 99e6fb52, checked in by Joel Sherrill <joel.sherrill@…>, on 08/07/10 at 00:22:46

2010-08-06 Bharath Suri <bharath.s.jois@…>

PR 1654/testing

  • psx13/test.c, psx13/psx13.scn: Test case to improve testing of dup2 routine.
  • psxfile01/test.c, psxfile01/psxfile01.scn: New test to improve testing of fcntl routine.
  • psximfs01/init.c, psximfs01/psximfs01.scn: Improve testing of imfs routines.
  • psximfs02/init.c, psximfs02/psximfs02.scn: Improve coverage of imfs routines.
  • psxpasswd02/init.c, psxpasswd02/psxpasswd02.scn: New tests to improve coverage of getpwent.c.
  • Property mode set to 100644
File size: 17.8 KB
Line 
1/*
2 *  Simple test program to exercise some of the basic functionality of
3 *  POSIX Files and Directories Support.
4 *
5 *  This test assumes that the file system is initialized with the
6 *  following directory structure:
7 *
8 *  XXXX fill this in.
9 *    /
10 *    /dev
11 *    /dev/XXX   [where XXX includes at least console]
12 *
13 *  COPYRIGHT (c) 1989-2010.
14 *  On-Line Applications Research Corporation (OAR).
15 *
16 *  The license and distribution terms for this file may be
17 *  found in the file LICENSE in this distribution or at
18 *  http://www.rtems.com/license/LICENSE.
19 *
20 *  $Id$
21 */
22
23#include <stdio.h>
24
25#include <pmacros.h>
26#include <sys/types.h>
27#include <sys/stat.h>
28#include <fcntl.h>
29#include <unistd.h>
30#include <errno.h>
31#include <string.h>
32#include <ctype.h>
33#include <reent.h>
34#include <rtems/imfs.h>
35
36#include <rtems.h>
37#include <rtems/libio.h>
38
39void test_case_reopen_append(void);
40
41char test_write_buffer[ 1024 ];
42rtems_filesystem_operations_table  IMFS_ops_no_evalformake;
43rtems_filesystem_operations_table  IMFS_ops_no_rename;
44
45/*
46 *  File test support routines.
47 */
48
49void test_cat(
50  char *file,
51  int   offset_arg,
52  int   length
53);
54
55void test_write(
56  char   *file,
57  off_t  offset,
58  char  *buffer
59);
60
61void test_extend(
62  char *file,
63  off_t new_len
64);
65
66void IMFS_dump( void );
67int IMFS_memfile_maximum_size( void );
68
69/*
70 *  dump_statbuf
71 */
72
73void dump_statbuf( struct stat *buf )
74{
75  int         major1;
76  int         minor1;
77  int         major2;
78  int         minor2;
79
80  rtems_filesystem_split_dev_t( buf->st_dev, major1, minor1 );
81  rtems_filesystem_split_dev_t( buf->st_rdev, major2, minor2 );
82
83  printf( "....st_dev     (0x%x:0x%x)\n", major1, minor1 );
84  printf( "....st_ino     %" PRIxino_t "  may vary by small amount\n", buf->st_ino );
85  printf( "....mode  = %08o\n", (unsigned int) buf->st_mode );
86  printf( "....nlink = %d\n", buf->st_nlink );
87
88  printf( "....uid = %d\n", buf->st_uid );
89  printf( "....gid = %d\n", buf->st_gid );
90
91  printf( "....atime = %s", ctime(&buf->st_atime) );
92  printf( "....mtime = %s", ctime(&buf->st_mtime) );
93  printf( "....ctime = %s", ctime(&buf->st_ctime) );
94
95  printf( "....st_blksize %" PRIxblksize_t "\n", buf->st_blksize );
96  printf( "....st_blocks  %" PRIxblkcnt_t "\n", buf->st_blocks );
97}
98
99void stat_a_file(
100  const char *file
101)
102{
103  int         status;
104  struct stat statbuf;
105
106  rtems_test_assert( file );
107
108  printf( "stat( %s ) returned ", file );
109  fflush( stdout );
110
111  status = stat( file, &statbuf );
112
113  if ( status == -1 ) {
114    printf( ": %s\n", strerror( errno ) );
115  } else {
116    puts("");
117    dump_statbuf( &statbuf );
118  }
119
120}
121
122/*
123 *  Main entry point of the test
124 */
125
126#if defined(__rtems__)
127int test_main(void)
128#else
129int main(
130  int argc,
131  char **argv
132)
133#endif
134{
135  int               status;
136  size_t            max_size;
137  int               fd;
138  int               i;
139  struct stat       buf;
140  char              buffer[128];
141  FILE             *file;
142  time_t            atime1;
143  time_t            mtime1;
144  time_t            ctime1;
145  time_t            atime2;
146  time_t            mtime2;
147  time_t            ctime2;
148  rtems_status_code rtems_status;
149  rtems_time_of_day time;
150
151  printf( "\n\n*** FILE TEST 1 ***\n" );
152
153  /*
154   *  Grab the maximum size of an in-memory file.
155   */
156
157  max_size = IMFS_memfile_maximum_size();
158
159  build_time( &time, 12, 31, 1988, 9, 0, 0, 0 );
160  rtems_status = rtems_clock_set( &time );
161
162  /*
163   *  Dump an empty file system
164   */
165
166  IMFS_dump();
167
168  /*
169   *  Simple stat() of /dev/console.
170   */
171
172  puts( "stat of /dev/console" );
173  status = stat( "/dev/console", &buf );
174  rtems_test_assert( !status );
175
176  dump_statbuf( &buf );
177
178  /*
179   *  Exercise mkdir() and some path evaluation.
180   */
181
182  puts( "" );
183  puts( "mkdir /dev/tty" );
184  status = mkdir( "/dev/tty", S_IRWXU );
185  rtems_test_assert( !status );
186
187  puts( "" );
188  puts( "mkdir /usr" );
189  status = mkdir( "/usr", S_IRWXU );
190  rtems_test_assert( !status );
191  puts( "mkdir /etc" );
192  status = mkdir( "/etc", S_IRWXU );
193  rtems_test_assert( !status );
194
195  puts( "mkdir /tmp" );
196  status = mkdir( "/tmp", S_IRWXU );
197  rtems_test_assert( !status );
198
199  /* this tests the ".." path in path name evaluation */
200  puts( "mkdir /tmp/.." );
201  status = mkdir( "/tmp/..", S_IRWXU );
202  rtems_test_assert( status == -1 );
203  rtems_test_assert( errno == EEXIST );
204
205  /* now check out trailing separators */
206  puts( "mkdir /tmp/" );
207  status = mkdir( "/tmp/", S_IRWXU );
208  rtems_test_assert( status == -1 );
209  rtems_test_assert( errno == EEXIST );
210
211  /* try to make a directory under a non-existent subdirectory */
212  puts( "mkdir /j/j1" );
213  status = mkdir( "/j/j1", S_IRWXU );
214  rtems_test_assert( status == -1 );
215  rtems_test_assert( errno == ENOENT );
216
217  /* this tests the ability to make a directory in the current one */
218  puts( "mkdir tmp" );
219  status = mkdir( "tmp", S_IRWXU );
220  rtems_test_assert( status == -1 );
221  rtems_test_assert( errno == EEXIST );
222
223  /* test rtems_filesystem_evaluate_path by sending NULL path */
224  status = chdir( NULL );
225  rtems_test_assert( status == -1 );
226  rtems_test_assert( errno == EFAULT );
227
228  /*
229   *  Now switch gears and exercise rmdir().
230   */
231
232  puts( "" );
233  puts( "rmdir /usr" );
234  status = rmdir( "/usr" );
235  rtems_test_assert( !status );
236
237  puts( "rmdir /dev" );
238  status = rmdir( "/dev" );
239  rtems_test_assert( status == -1 );
240  rtems_test_assert( errno ==  ENOTEMPTY);
241
242  puts( "rmdir /fred" );
243  status = rmdir ("/fred");
244  rtems_test_assert (status == -1);
245  rtems_test_assert( errno == ENOENT );
246
247  puts( "rmdir /tmp/bha" );
248  status = rmdir( "/tmp/bha" );
249  rtems_test_assert( status == -1 );
250  rtems_test_assert( errno == ENOENT );
251
252  puts( "unlink /dev/tty" );
253  status = unlink( "/dev/tty" );
254  rtems_test_assert( status == -1 );
255  rtems_test_assert( errno == EISDIR );
256
257  puts( "mknod /dev/test_console" );
258  status = mknod( "/dev/test_console", S_IFCHR, 0LL );
259  rtems_test_assert( !status );
260
261  puts( "mknod /dev/tty/S3" );
262  status = mknod( "/dev/tty/S3", S_IFCHR, 0xFF00000080LL );
263  rtems_test_assert( !status );
264
265  puts ("mknod /etc/passwd");
266  status = mknod( "/etc/passwd", (S_IFREG | S_IRWXU), 0LL );
267  rtems_test_assert( !status );
268
269  puts( "mkdir /tmp/my_dir");
270  status = mkdir( "/tmp/my_dir", S_IRWXU );
271  rtems_test_assert( status == 0 );
272
273  puts("mkfifo /c/my_dir" );
274  status = mkfifo( "/c/my_dir", S_IRWXU );
275  rtems_test_assert( status == -1 );
276
277  /*
278   *  Try to make a directory under a file -- ERROR
279   */
280
281  puts( "mkdir /etc/passwd/j" );
282  status = mkdir( "/etc/passwd/j", S_IRWXU );
283  rtems_test_assert( status == -1 );
284  rtems_test_assert( errno == ENOTDIR );
285
286  /*
287   *  Simple open failure case on non-existent file
288   */
289
290  puts( "open /tmp/joel - should fail with ENOENT" );
291  fd = open( "/tmp/joel", O_RDONLY );
292  rtems_test_assert( fd == -1 );
293  rtems_test_assert( errno == ENOENT );
294
295  /*
296   *  Simple open case where the file is created.
297   */
298
299  puts( "open /tmp/j" );
300  fd = open( "/tmp/j", O_RDWR|O_CREAT, S_IRWXU|S_IRWXG|S_IRWXO );
301  rtems_test_assert( fd != -1 );
302  printf( "open returned file descriptor %d\n", fd );
303
304  puts( "close /tmp/j" );
305  status = close( fd );
306  rtems_test_assert( !status );
307
308  puts( "close /tmp/j again" );
309  status = close( fd );
310  rtems_test_assert( status == -1 );
311
312  puts( "unlink /tmp/j" );
313  status = unlink( "/tmp/j" );
314  rtems_test_assert( !status );
315
316  puts( "unlink /tmp" );
317  status = unlink( "/tmp" );
318  rtems_test_assert( status );
319
320  /*
321   *  Simple open failure. Trying to create an existing file.
322   */
323
324  puts("create and close /tmp/tom");
325  fd = open( "/tmp/tom", O_CREAT, S_IRWXU|S_IRWXG|S_IRWXO );
326  rtems_test_assert( fd != -1 );
327  status = close( fd );
328  rtems_test_assert( status == 0 );
329
330  puts("Attempt to recreate /tmp/tom");
331  fd = open( "/tmp/tom", O_CREAT | O_EXCL, S_IRWXU|S_IRWXG|S_IRWXO );
332  rtems_test_assert( fd == -1 );
333  rtems_test_assert( errno == EEXIST );
334
335  puts("create /tmp/john");
336  fd = open( "/tmp/john", O_RDWR|O_CREAT, S_IRWXU|S_IRWXG|S_IRWXO );
337  rtems_test_assert( fd != -1 );
338
339  puts("tcdrain /tmp/john" );
340  status = tcdrain( fd );
341  rtems_test_assert( status == 0 );
342
343  /*
344   * Open a file in read-only mode and try to truncate
345   */
346
347  puts( "Attempt to create a file, open in read-only mode and truncate it" );
348  fd = open( "/tmp/bha", O_CREAT | O_RDONLY | O_TRUNC, S_IRUSR );
349  rtems_test_assert( fd == -1 );
350  rtems_test_assert( errno == EINVAL );
351
352  puts( "Exercise the reentrant version _link_r -- Expect ENOENT" );
353  status = _link_r( NULL, "/tmp/notexist", "/tmp/cannotexist" );
354  rtems_test_assert( status == -1 );
355  rtems_test_assert( errno == ENOENT );
356
357  puts( "Unlink /tmp/bha using the reentrant version -- OK" );
358  status = _unlink_r( NULL, "/tmp/bha" );
359  rtems_test_assert( status == 0 );
360
361  /*
362   * Simple test case for mknod
363   */
364
365  puts( "mknod with bad type - expect EINVAL" );
366  status = mknod( "/tmp/bha", 0, 0LL );
367  rtems_test_assert( status == -1 );
368  rtems_test_assert( errno == EINVAL );
369
370  /*
371   * Read from filedes opened for write
372   */
373
374  puts( "open /tmp/bha in write only mode -- OK" );
375  fd = open( "/tmp/bha", O_CREAT | O_WRONLY, S_IRWXU|S_IRWXG|S_IRWXO );
376  rtems_test_assert( fd != -1 );
377
378  puts( "attempt fcntl on opened file -- OK" );
379  status = fcntl( fd, F_SETFD, 0 );
380  rtems_test_assert( status == 0 );
381
382  puts( "attempt to read from /tmp/bha - expect EINVAL" );
383  status = read( fd, buffer, 10 );
384  rtems_test_assert( status == -1 );
385  rtems_test_assert( errno == EINVAL );
386
387  puts( "closing and unlinking /tmp/bha" );
388  status = close( fd );
389  status |= unlink( "/tmp/bha" );
390  rtems_test_assert( status == 0 );
391
392  puts( "open /tmp/bha in read only mode -- OK" );
393  fd = open( "/tmp/bha", O_CREAT | O_RDONLY, S_IRWXU|S_IRWXG|S_IRWXO );
394  rtems_test_assert( fd != -1 );
395 
396  puts( "attempt to read from /tmp/bha - expect EINVAL" );
397  status = write( fd, buffer, 10 );
398  rtems_test_assert( status == -1 );
399  rtems_test_assert( errno == EINVAL );
400
401  puts( "closing and unlinking /tmp/bha" );
402  status = close( fd );
403  status |= unlink( "/tmp/bha" );
404  rtems_test_assert( status == 0 );
405
406  /*
407   * Read/write from an unopened filedes
408   */
409  puts( "attempt to read from an unopened filedes - expect EBADF" );
410  status = read( 5, buffer, 10 );
411  rtems_test_assert( status == -1 );
412  rtems_test_assert( errno == EBADF );
413
414  puts( "attempt to write to an unopened filedes - expect EBADF" );
415  status = write( 5, buffer, 10 );
416  rtems_test_assert( status == -1 );
417  rtems_test_assert( errno == EBADF );
418
419  /*
420   *  Test simple write to a file at offset 0
421   */
422
423  puts( "mknod /tmp/joel" );
424  status = mknod( "/tmp/joel", (S_IFREG | S_IRWXU), 0LL );
425  test_write( "/tmp/joel", 0, "the first write!!!\n" );
426  test_cat( "/tmp/joel", 0, 0 );
427
428  /* Exercise _rename_r */
429
430  /* Simple rename test */
431  puts( "rename /tmp/joel to /tmp/drjoel");
432  status = _rename_r(NULL,"/tmp/joel","/tmp/drjoel");
433  rtems_test_assert(status == 0);
434
435  /* Simple rename test */
436  puts("rename /tmp/drjoel to /tmp/joel");
437  status = _rename_r(NULL,"/tmp/drjoel","/tmp/joel");
438  rtems_test_assert(status == 0);
439
440  /* Invalid old path */
441  puts("rename /tmp/drjoel to /tmp/joel - Should result in an error \
442since old path is not valid");
443  status = _rename_r(NULL,"/tmp/drjoel","/tmp/joel");
444  rtems_test_assert(status == -1);
445
446  /* Invalid new path */
447  puts("rename /tmp/joel to /tmp/drjoel/joel - Should result in an error \
448since new path is not valid");
449  status = _rename_r(NULL,"/tmp/joel","/tmp/drjoel/joel");
450  rtems_test_assert(status == -1);
451
452  puts("changing dir to /tmp");
453  status = chdir("/tmp/");
454  rtems_test_assert(status == 0);
455
456  puts("rename joel to drjoel");
457  status = _rename_r(NULL,"joel","drjoel");
458  rtems_test_assert(status == 0);
459
460  puts("rename drjoel to joel");
461  status = _rename_r(NULL,"drjoel","joel");
462  rtems_test_assert(status == 0);
463
464  /* Rename across file systems */
465  puts("creating directory /imfs");
466  status = mkdir("/imfs",0777);
467  rtems_test_assert(status == 0);
468  puts("creating directory /imfs/hidden_on_mount");
469  status = mkdir("/imfs/hidden_on_mount",0777);
470  rtems_test_assert(status == 0);
471
472  puts("mounting filesystem with IMFS_ops at /imfs");
473  status = mount("null", "/imfs", "imfs", RTEMS_FILESYSTEM_READ_WRITE, NULL);
474  rtems_test_assert(status == 0);
475  puts("creating directory /imfs/test (on newly mounted filesystem)");
476  status = mkdir("/imfs/test", 0777);
477  rtems_test_assert(status == 0);
478
479  puts("attempt to rename directory joel to /imfs/test/joel - should fail with EXDEV");
480  status = _rename_r(NULL, "joel", "/imfs/test/joel");
481  rtems_test_assert(status == -1);
482  rtems_test_assert(errno == EXDEV);
483
484  puts("changing dir to /");
485  status = chdir("/");
486  rtems_test_assert(status == 0);
487
488  puts("attempt to rename across filesystem, with old path having a parent node");
489  puts("attempt to rename tmp/joel to /imfs/test/joel");
490  status = _rename_r(NULL, "tmp/joel", "/imfs/test/joel");
491  rtems_test_assert(status == -1);
492  rtems_test_assert(errno == EXDEV);
493
494  puts("End of _rename_r tests");
495
496  /*
497   *  Test simple write to a file at a non-0 offset in the first block
498   */
499
500  status = unlink( "/tmp/joel" );
501  rtems_test_assert( !status );
502
503  status = mknod( "/tmp/joel", (S_IFREG | S_IRWXU), 0LL );
504  rtems_test_assert( !status );
505
506  test_write( "/tmp/joel", 10, "the first write!!!\n" );
507  test_cat( "/tmp/joel", 0, 0 );
508  stat_a_file( "/tmp/joel" );
509
510  /*
511   *  Test simple write to a file at a non-0 offset in the second block.  Then
512   *  try to read from various offsets and lengths.
513   */
514
515  puts("unlink /tmp/joel");
516  status = unlink( "/tmp/joel" );
517  rtems_test_assert( !status );
518
519  /* Test a failure path */
520
521  puts( "unlink /tmp/joel" );
522  status = unlink( "/tmp/joel" );
523  rtems_test_assert( status == -1 );
524
525  puts( "mknod /tmp/joel");
526  status = mknod( "/tmp/joel", (S_IFREG | S_IRWXU), 0LL );
527  rtems_test_assert( !status );
528
529  test_write( "/tmp/joel", 514, "the first write!!!\n" );
530  test_write( "/tmp/joel", 1, test_write_buffer );
531  test_write( "/tmp/joel", 63, test_write_buffer );
532  test_cat( "/tmp/joel", 0, 1 );
533  test_cat( "/tmp/joel", 1, 1 );
534  test_cat( "/tmp/joel", 490, 1 );
535  test_cat( "/tmp/joel", 512, 1 );
536  test_cat( "/tmp/joel", 513, 1 );
537  test_cat( "/tmp/joel", 514, 1 );
538  test_cat( "/tmp/joel", 520, 1 );
539  test_cat( "/tmp/joel", 1, 1024 );
540
541  /*
542   *  Read from a much longer file so we can descend into doubly and
543   *  triply indirect blocks.
544   */
545
546  if ( max_size < (size_t) 300 * 1024 ) {
547    test_extend( "/tmp/joel", max_size - 1 );
548    test_cat( "/tmp/joel", max_size / 2, 1024 );
549  } else {
550    printf( "Skipping maximum file size test since max_size is %zu bytes\n", max_size );
551    puts("That is likely to be bigger than the available RAM on many targets." );
552  }
553
554  stat_a_file( "/tmp/joel" );
555
556  /*
557   *  Now try to use a FILE * descriptor
558   *
559   *  /tmp/j should not exist at this point.
560   */
561
562  puts( "stat of /tmp/j" );
563  errno = 0;
564  status = stat( "/tmp/j", &buf );
565  printf( "stat(/tmp/j) returned %d (errno=%d)\n", status, errno );
566  dump_statbuf( &buf );
567
568  puts( "fopen of /tmp/j" );
569  file = fopen( "/tmp/j", "w+" );
570  rtems_test_assert( file );
571
572  puts( "fprintf to /tmp/j" );
573  for (i=1 ; i<=5 ; i++) {
574    status = fprintf( file, "This is call %d to fprintf\n", i );
575    rtems_test_assert( status );
576    printf( "(%d) %d characters written to the file\n", i, status );
577  }
578
579  fflush( file );
580
581  status = stat( "/tmp/j", &buf );
582  rtems_test_assert( !status );
583  dump_statbuf( &buf );
584  atime2 = buf.st_atime;
585  mtime2 = buf.st_mtime;
586  ctime2 = buf.st_ctime;
587
588
589  status = rtems_task_wake_after( rtems_clock_get_ticks_per_second() );
590  rewind( file );
591  while ( fgets(buffer, 128, file) )
592    printf( "%s", buffer );
593
594  /*
595   * Verify only atime changed for a read.
596   */
597  status = stat( "/tmp/j", &buf );
598  rtems_test_assert( !status );
599  dump_statbuf( &buf );
600  atime1 = buf.st_atime;
601  mtime1 = buf.st_mtime;
602  ctime1 = buf.st_ctime;
603  rtems_test_assert( atime1 != atime2);
604  rtems_test_assert( mtime1 == mtime2);
605  rtems_test_assert( ctime1 == ctime2);
606
607  IMFS_dump();
608
609  unlink( "/tmp/joel" );
610
611  /*
612   *  Now truncate a file
613   */
614
615  status = rtems_task_wake_after( rtems_clock_get_ticks_per_second() );
616  puts( "truncate /tmp/j to length of 40" );
617  status = truncate( "/tmp/j", 40 );
618  rtems_test_assert( !status );
619
620  /*
621   * Verify truncate changed only atime.
622   */
623  status = stat( "/tmp/j", &buf );
624  rtems_test_assert( !status );
625  dump_statbuf( &buf );
626  atime2 = buf.st_atime;
627  mtime2 = buf.st_mtime;
628  ctime2 = buf.st_ctime;
629  rtems_test_assert( atime1 != atime2);
630  rtems_test_assert( mtime1 == mtime2);
631  rtems_test_assert( ctime1 == ctime2);
632
633  IMFS_dump();
634
635  /* try to truncate the console and see what happens */
636  status = truncate( "/dev/console", 40 );
637  rtems_test_assert( status == 0 );
638
639  puts( "truncate /tmp/j to length of 0" );
640  status = truncate( "/tmp/j", 0 );
641  rtems_test_assert( !status );
642
643  puts( "truncate /tmp to length of 0 should fail with EISDIR\n");
644  status = truncate( "/tmp", 0 );
645  rtems_test_assert( status == -1 );
646  printf( "%d: %s\n", errno, strerror( errno ) );
647  rtems_test_assert( errno == EISDIR );
648
649  IMFS_dump();
650
651  status = truncate( "/tmp/fred", 10 );
652  rtems_test_assert( status == -1);
653
654  rtems_status = rtems_io_register_name( "/dev/console", 0, 0 );
655
656  test_case_reopen_append();
657
658  printf( "*** END OF FILE TEST 1 ***\n" );
659  rtems_test_exit( 0 );
660}
661
662/*
663 *  Open/Create a File and write to it
664 *
665 *  Test case submitted by Andrew Bythell <abythell@nortelnetworks.com>.
666 *
667 */
668
669void test_file (char *filename, char *mode);
670
671void test_case_reopen_append(void)
672{
673  printf ("Writing First File\n");
674  test_file ("/one.txt", "a");
675  test_file ("/one.txt", "a");
676
677  /* but not the second time - this will insert junk.
678     the number of ^@'s seems to equal the number of
679     actual characters in the file */
680
681  printf ("Writing Second File\n");
682  test_file ("/two.txt", "a");
683  test_file ("/two.txt", "a");
684
685  test_cat( "/one.txt", 0, 1024 );
686  test_cat( "/two.txt", 0, 1024 );
687}
688
689void test_file (char *filename, char *mode)
690{
691  FILE *fp;
692  fp = fopen (filename, mode);
693  if (!fp)
694      perror ("fopen");
695  fprintf (fp, "this is a test line\n");
696  if (fclose (fp))
697      perror ("fclose");
698}
Note: See TracBrowser for help on using the repository browser.