source: rtems/testsuites/psxtests/psxfile01/test.c @ bdcf4102

4.115
Last change on this file since bdcf4102 was d1c5c01f, checked in by Sebastian Huber <sebastian.huber@…>, on 09/13/13 at 15:06:06

psxtests/psxfile01: Fix according to POSIX

ftruncate() and open() with O_TRUNC shall upon successful completion
mark for update the st_ctime and st_mtime fields of the file.

truncate() shall upon successful completion, if the file size is
changed, mark for update the st_ctime and st_mtime fields of the file.

The POSIX standard "The Open Group Base Specifications Issue 7", IEEE
Std 1003.1, 2013 Edition says nothing about the behaviour of truncate()
if the file size remains unchanged.

Future directions of the standard may mandate the behaviour specified in
ftruncate():

http://austingroupbugs.net/view.php?id=489

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