summaryrefslogtreecommitdiffstats
path: root/doc/libdmsdos.doc
blob: 8d4ce700b0da57174842bf0ce395a0450b8a9729 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
This is documentation for the dmsdos library.                        10Sep1998


The dmsdos library, libdmsdos.a, provides the dmsdos interface that is known
from the kernel to a user-level process. This is acchieved by compiling the
dmsdos code simply in a different way (well, with some rather dirty tricks).

The library is intended to export the following functions:

  open_cvf
  close_cvf
  dbl_bitfat_value
  dbl_fat_nextcluster
  dbl_mdfat_cluster2sector
  dbl_mdfat_value
  dmsdos_read_cluster
  dmsdos_write_cluster
  raw_bread
  raw_brelse
  raw_getblk
  raw_mark_buffer_dirty
  raw_set_uptodate
  simple_check
  stac_bitfat_state

In fact, it exports much more, but only these listed here are guaranteed
to be kept in future versions. Well, one thing should be important to know:
The library does NOT export virtual sector management. It will never do.

The functions are just used like in the kernel. For prototypes, see file
dmsdos.h. How to use them see the sources. There's some example code
how to use the library (dcread.c).

The first two, open_cvf and close_cvf, are special. They are called instead
of mounting and unmounting the CVF.

How to "mount" a CVF (example code):

    #include "lib_interface.h"
    #include "dmsdos.h"

    struct super_block*sb;
    sb=open_cvf("CVF_Filename",rwflag /*0=RDONLY or 1=RDWR*/);
    if(sb==NULL)
    { fprintf(stderr,"open CVF failed\n");
      exit(1);
    }

Keep the sb pointer. It must be passed to a lot of functions. It can be used
in the same way as in the kernel. The sb pointer is used to distinguish
between several open CVFs (in fact, in sb->s_dev the file descriptor is
stored).

After use, the CVF must be "unmounted" again:

    close_cvf(sb);

If you want to see more example code, look at the dcread utility source
(file dcread.c in the src directory).

Notes:
    * The user-level functions are single-threaded. You cannot run several
      processes on the same CVF unless all are only reading. The library 
      uses flock in order to detect such situations and just denies access
      in that case. You can however use different CVFs in parallel, even
      read-write, even in one program.
    * You should use this library only to access a CVF that is currently 
      _not_ mounted. There's a high risk of destroying the CVF or even 
      crashing the system otherwise.
    * You should not touch CVFs that are currently used by dosemu with
      wholedisk or partition access. Again there's a high risk of destroying
      the CVF or crashing dosemu otherwise.
    * If you do not intend to write to the CVF, open it in read-only mode.
      Then the CVF is not locked exclusively and other processes are allowed
      to read the CVF at the same time.
    * The library prints the "kernel" messages to stderr. It obeys the
      loglevel variable. The only way to make libdmsdos quiet is to
      direct stderr into a file or to /dev/null.
    * The first call of open_cvf initializes the library and prints some
      version information to stderr.
    * open_cvf does not do a filesystem check. If you do want the same
      simple filesystem check that dmsdos usually does when mounting, call
      simple_check afterwards.
    * open_cvf may open the filesystem read-only though you request
      read-write access. This happens if write access is denied or if the
      dmsdos subsystem detects unexpected problems with the CVF. You may 
      not like this behaviour, but it's best for long life of your CVFs :)
      Programmers please check sb->s_flags for the MS_RDONLY flag after
      calling open_cvf to be sure.
    * To compile your application using the dmsdos library you should under
      no cirumstances define the macro __KERNEL__ for the whole program.
      It causes problems with libc6.

As an idea what the dmsdos library might be good for:

    * A mtools clone for CVFs, for example called dtools. (A simple program,
      dcread, exists but its quatilty cannot be compared to mtools. Sorry.)
    * A fsck.dmsdos. (An incomplete alpha test version exists.)
    * A defragmentation program. (Not yet written.)
    * For debugging dmsdos at user level (it's the same source code).
    * An external fs for midnight commander. (A read-only interface program,
      mcdmsdos, exists.)
    * ... add your favourite idea here :)
    * ... I've even received mail about whether it would be possible to
      port libdmsdos to Win32 and use it for a Win95/98/NT driver :))

Support for a shared dmsdos library
-----------------------------------

libdmsdos can be compiled as a shared library. You must edit the
Makefile in the src directory for that purpose. Note that the default 
installation does not compile the shared library. This is intended. If 
you are not an experienced shared library hacker please be careful. It's 
easy to screw up some binaries with shared libraries :)

You should not use the dmsdos shared library for now as there's currently no
standard that ensures that future versions will be backwards compatible.
Link statically with libdmsdos.a unless disk space or memory is very critical.

WARNING: If there's a shared dmsdos library lying around, gcc defaults to
linking *dynamically* all programs that need this library. For example, if 
libdmsdos.so is present and you recompile dmsdosfsck, the binary will be
dynamically linked against that library. This can be very confusing and may
not do what you want :(

Built-in direct translation access mode
---------------------------------------

This feature has been added to the library for convenience. It only works
for FAT12 or FAT16 MSDOS filesystems. It does not work, for example, for
CVFs on a cdrom or in a FAT32 or non-FAT filesystem.

The dmsdos library can access a CVF that resides in an uncompressed FAT12 or 
FAT16 MSDOS host partition even if the uncompressed host partition is not 
mounted (this was programmed in fact by cut'n'paste of some old dmsdosfs 
functions). If there are more than one CVF in that partition, the first 
valid CVF found in the root directory of the uncompressed host partition
is used. If you want to select one of more CVFs, append a colon and the
extension of the CVF to the filename.

For example, if your uncompressed host partition is /dev/hda1 and you want
to access the CVF DRVSPACE.001 on that partition, use "/dev/hda1:001" as
filename when calling open_cvf. If DRVSPACE.001 is the only CVF inside
/dev/hda1 you can even use "/dev/hda1" and it will find the CVF.

This special feature allows, for example, dmsdosfsck to check a CVF in
a partition that has not yet been mounted. This may be useful at boot time
to check CVFs :)

Standard C library functions that libdmsdos needs
-------------------------------------------------

If you want to use libdmsdos in an environment where the standard C library
functions are not available then you must write an emulation for all these 
functions and link your code against it instead of the standard C library. 
The emulation needn't be full-featured like the C library. Look at the source 
if you are in doubt.

  close (4)
  errno
  exit (3)
  flock (only if compiled with -DUSE_FLOCK) (4)
  sopen (only if compiled with -DUSE_SOPEN) (4) (8)
  fprintf (2)
  free (6)
  lseek (1) (7)
  malloc (6)
  memcpy
  memmove
  memset
  open (only if _not_ compiled with -DUSE_SOPEN) (8)
  read (1)
  strcat
  strerror
  strncat
  strncmp
  strncpy
  strrchr
  time (5)
  vsprintf (2) (9)
  write (1)

  (1) only used in aligned 512 byte blocks
  (2) only used for logging to stderr, no other files are used
  (3) used for abort in case of fatal error
  (4) locking may be left out
  (5) a time-proportional counter would be enough, needn't be exact
  (6) called quite often, be aware of possible memory fragmentation
  (7) only used on 512 byte boundaries with SEEK_SET, also used to find out 
      file size with SEEK_END
  (8) must parse filename (invent your own syntax if necessary)
  (9) a free, portable emulation is in linux/lib/vsprint.c

If possible, please avoid hacking around in dmsdos kernel code if the library
does not work in your environment (unless it's a bug). The only files that 
should be modified due to OS or compiler differences are lib_interface.c and 
lib_interface.h. Please surround your changes with appropriate #ifdef's so 
the modified code still compiles under all those environments where it worked
before. Please do not violate portability of the code. Hardware comes and 
goes, OSses are born and die, but portable code survives :)


dmsdos library reference guide
------------------------------

All functions need the header files lib_interface.h and dmsdos.h. Error
checking is not always possible. This is because some of the low-level kernel
functions dmsdos calls when running as kernel module never fail. In user
space, there may be failures, however. This means, libdmsdos is currently
not 100% error-safe. As a workaround (that works under the standard C library)
you can set errno to NO_ERROR before calling the function and afterwards 
check whether it has changed to an error value.

  open_cvf:

    struct super_block* open_cvf(char*filename,int rwflag)

    Opens CVF described by filename. rwflag is 0 for read-only, otherwise
    the file is opened read-write. The file is locked read-only or 
    read-write, depending on rwflag (by calling flock or sopen).

    filename is interpreted in a special way if it contains a colon.
    See chapter 'Built-in direct translation access mode' above. The colon 
    and the rest of the string after it are stripped off before the filename
    is forwarded to the libc function open or sopen.

    Return Value: Pointer to valid super_block structure (which can be
    interpreted as a kind of file handle), NULL if failed.

    Note: Keep the super_block pointer. Most dmsdos functions need it in
    order to identify the CVF.

    Note: The CVF may be opened read-only though you opened it in read-write
    mode. This happens if the dmsdos code detects unexpected errors in the
    CVF. Please check sb->s_flags for the MS_RDONLY bit after calling
    open_cvf.

  close_cvf:

    void close_cvf(struct super_block*sb)

    Closes the CVF. If necessary, the file is unlocked before.

  dbl_bitfat_value:

    int dbl_bitfat_value(struct super_block*sb,int sectornr,int*new)

    Reads or writes bitfat value that belongs to sector sectornr.
    *** This is a low-level function you are probably never interested in.
    Bitfat values are CVF format specific. You do not need to know them
    unless debugging write access :))

    If new is NULL the value is read and returned.
    Otherwise the actual value is replaced with that one found in *new.

    Return value: read access: read value. write access: undefined. A
    negative value indicates an error.
 
  dbl_fat_nextcluster:

    int dbl_fat_nextcluster(struct super_block*sb,int clusternr,int*new)

    Reads or writes the FAT entry for cluster clusternr. (You should know
    what a FAT is, otherwise please read a good book on Dos disk access.)

    If new is NULL the value is read and returned.
    Otherwise the actual value is replaced with that one found in *new.

    Return value: read access: read value. write access: undefined. 
    -1 means the cluster is marked as last cluster in a file or an error
    occured. Errors may also be represented by other negative results.

    Note: -1 can also be used for write access in order to indicate end of
    file mark. It is automatically converted to the right value for the FAT
    size.

    Note: if clusternr is invalid a -1 is returned. This usually makes
    runaway FAT reading loops break as if there was an EOF in the cluster 
    chain. There's no means to find out whether a -1 is an error or an EOF
    except making sure a valid cluster number is given.

  dbl_mdfat_cluster2sector:

    int dbl_mdfat_cluster2sector(struct super_block*sb,int clusternr)

    This is a low-level function that reads the mdfat and extracts the
    starting sector information from it. It may be useful for write
    access with dmsdos_write_cluster. It returns the starting sector number
    of cluster clusternr.
    *** Do not use the standard formula that is used in a normal FAT
    filesystem (multiplying the cluster number with the number of sectors
    per cluster and adding some offsets) - that formula is not valid in a
    compressed filesystem. Use dbl_mdfat_cluster2sector instead.

    Return value: starting sector of the cluster clusternr. If negative,
    an error occured.
    
    Note: see dmsdos_write_cluster what this function is useful for.

  dbl_mdfat_value:

    int dbl_mdfat_value(struct super_block* sb,int clusternr,
                        Mdfat_entry*new,Mdfat_entry*mde)

    Reads or writes the mdfat/allocmap entry that belongs to cluster
    clusternr.
    *** This is a low-level function you are probably never interested in.
    Mdfat values are CVF format specific. You do not need to know them
    unless debugging dmsdos :))

    If new is NULL the value is read and stored in *mde.
    Otherwise the actual value is replaced with that one found in *new.

    Return value: A negative value indicates an error.
    Note: consider *mde as undefined after write access.

  dmsdos_read_cluster:

    int dmsdos_read_cluster(struct super_block*sb,
                            unsigned char*clusterd, int clusternr)

    Reads cluster clusternr in clusterd. The dmsdos library handles all
    low-level access including decompression of the data.

    *** Be sure to have reserved enough memory for the data that this
    function writes into clusterd. Usually, use a full cluster size.
    How to determine the full cluster size see the dcread example code.

    Return value: number of bytes actually read (clusters may be shorter
    than the full size in a CVF, which is not possible in an uncompressed
    FAT filesystem). Usually, you can ignore this unless you use write
    access. (A cluster may even have length zero.) Just in case, if the
    cluster is shorter than full size the unused slack is zero'd out. On 
    error, a negative value is returned.

    Note: This function cannot read the root directory (cluster 0). You must
    use low-level disk access for that (i.e. raw_bread). See also the dcread
    example code.

  dmsdos_write_cluster:

    int dmsdos_write_cluster(struct super_block*sb, unsigned char* clusterd,
                             int length, int clusternr, int near_sector, 
                             int ucflag)

    Writes cluster clusternr from clusterd back to disk. The dmsdos
    library handles all low-level disk access including compression.

    length is the position of the last valid byte plus 1 in clusterd.
    This is usually the value that dmsdos_read_cluster returned when you
    read this cluster before unless you modified some bytes beyond that 
    value. The idiot-proof value is always the full cluster size.
    *** Warning: if length is too low, the data may be truncated during
    write access. If it is too high disk space is wasted.

    ucflag is a flag that indicates whether libdmsdos should try to
    compress the data. 0 means try to compress, 1 means write the data
    without compression. Please do not use other values than 0 and 1
    (they do exist but have some very special meanings).

    near_sector can be used to control where libdmsdos tries to place
    the data on disk. For good performance on large files, it should
    be set to the start sector value of the cluster that preceeds the
    actual cluster in the file. The start sector value can be obtained with
    dbl_mdfat_cluster2sector on that preceeding cluster. (This way actually
    helps avoiding low-level fragmentation _and_ file data fragmentation in 
    the CVF.)
    *** Do not use the standard formula that is used in a normal FAT
    filesystem (multiplying the cluster number with the number of sectors
    per cluster and adding some offsets) - that formula is not valid in a
    compressed filesystem.

    The near_sector value is just meant as a hint. If there's not enough 
    free space on the disk around near_sector, dmsdos writes the data 
    somewhere else. If near_sector is set to zero, dmsdos uses an internal 
    way to find out a good value that avoids low-level fragmentation but
    that is not necessarily good for performance on the file that is being
    written. Nevertheless, 0 always works.

    WARNING: If you are writing a directory cluster you should not compress
    the data (it would hurt performance a lot). Some of the original CVF Dos
    software versions never compress directories, so you shouldn't do
    either (it may cause compatibility problems otherwise). You also should 
    always use full cluster size for directories on some CVF versions. In 
    order to feel safe you can use the DIR_MAY_BE_COMPRESSED and 
    DIR_MAY_BE_SHORT macros. Have a look at their definition in file
    dmsdos.h.

    Return value: A negative value indicates an error.

    Note: The data in clusterd are not destroyed by this function.

  raw_bread:

    struct buffer_head* raw_bread(struct super_block*sb,int block)

    Read a 512 byte block from the CVF. The data are read without further
    modification. Block numbering scheme begins with zero.

    Return value: pointer to a buffer_head structure that contains the 512 
    bytes of data that represent the data block. The data are in
    buffer_head->b_data in an unsigned character array (see the dcread
    example). On error, NULL is returned.

    Note: The data are returned in dynamically allocated memory. If you
    do not need the data any more, you must free them by calling raw_brelse
    in the buffer_head structure pointer.

    Note: You may modify the data. You must call raw_mark_buffer_dirty
    after modification (this writes the data back to disk) and before
    raw_brelse.

    Note: If you want to overwrite the whole block, use raw_getblk instead
    of raw_bread (this leaves out the unnecessary disk read access).

  raw_brelse:

    void raw_brelse(struct super_block*sb,struct buffer_head*bh)

    Release dynamic memory for the data read by raw_bread.

  raw_getblk:

    struct buffer_head* raw_getblk(struct super_block*sb,int block)

    Like raw_bread, but leaves out the actual disk access. This is intended
    to leave out unnecessary disk read access if you want to overwrite the 
    whole block. Consider the data as undefined. See raw_bread.

    Note: raw_getblk should be followed by a call of raw_set_uptodate
    for compatibility. See there.

  raw_mark_buffer_dirty:

    void raw_mark_buffer_dirty(struct super_block*sb,struct buffer_head*bh,
                               int dirty_val)

    Write data back to disk after they have been modified.

    dirty_val must be always 1 (due to a misunderstanding/bug in dmsdos).

    Note: This function does not indicate an error. This is bad, but it is 
    because the Linux kernel interface also does not return error codes with
    this function. As a hack (that works with libc) you can set errno to 
    NO_ERROR and check it afterwards, but note that this will not work in 
    the dmsdos kernel driver. Usually, there cannot be an error during that
    kind of write access unless the disk has bad sectors.

  raw_set_uptodate:

    void raw_set_uptodate(struct super_block*sb,struct buffer_head*bh,int v)

    No longer used (does nothing in libdmsdos).

    For compatibility with the kernel driver, this should be called with 
    v=1 after raw_getblk on the buffer_head pointer that raw_getblk returned
    (unless it's NULL due to failure).

  simple_check:

    int simple_check(struct super_block*sb,int repair)

    Check some internal filesystem tables for consistency.

    If repair is 1 libdmsdos tries to correct errors (the CVF must have been
    opened read-write in that case). If it is 0 the tables are just checked.

    Return value: a negative value indicates an error, i.e. either the
    tables are inconsistent or the errors, if any, could not be repaired.

    Note: It is strongly recommended to run simple_check immediately after
    opening the CVF if you want to write to it. Do not write to a CVF if
    simple_check fails on it (you may detroy some files in the CVF). As
    CVFs are known to be very prone to so-called bitfat errors, you probably
    want to give the program that you are writing a kind of "expert option" 
    that lets simple_check run with repair=1. The same warnings apply as to
    the dmsdos mount option bitfaterrs:repair :))

  stac_bitfat_state:

    int stac_bitfat_state(struct super_block*sb,int new_state)

    Stacker has a kind of clean/dirty flag that indicates whether the CVF
    was correctly unmounted. This function can be used to manipulate it.
    *** This is a low-level function you should not use unless you are a
    dmsdos and Stacker expert. See the dmsdos source code.

    Note: Do not call this function on non-Stacker CVFs.


Programming notes
-----------------

In a normal libdmsdos application, you open the CVF with open_cvf. If you
intend to write to the CVF later, you should call simple_check after that
and refuse write access if an error is found.

Then you use dmsdos_read_cluster and dmsdos_write_cluster to access the
CVF. Only for the root directory you need raw_bread, raw_mark_buffer_dirty
and raw_brelse. For following files you need to read the FAT with
dbl_fat_nextcluster.

You write to the FAT with dbl_fat_nextcluster in order to truncate files or 
append data to them, or even to delete files and directories and to create 
new ones - all like in a usual FAT filesystem.

If you are done, don't forget to close the CVF with close_cvf.

Here is a simple example that illustrates the usage of all important
libdmsdos functions. You should not compile and execute this example as
it's not always correct C syntax. Things like error handling have been left
out to simplify the code (which is not good). Furthermore, the program just 
pokes around in the CVF which would be bad for your data :)


    #include "lib_interface.h"
    #include "dmsdos.h"

    struct super_block*sb;
    struct Dblsb*dblsb;

    /* open the CVF read-write */
    sb=open_cvf("dblspace.001",1);
    if(sb==NULL)...error...

    /* check the CVF */
    if(simple_check(sb,0)<0)...error...

    /* read and display cluster 2 as hex numbers */
    int i;
    int size;
    int full_size;
    unsigned char* clusterd;

    /* determine full size */
    dblsb=MSDOS_SB(sb)->private_data;
    full_size=dblsp->s_sectperclust*512;

    clusterd=malloc(full_size);
    if(clusterd==NULL)...error...

    size=dmsdos_read_cluster(sb,clusterd,2);
    if(size<0)...error...
    for(i=0;i<size;++i) printf("%x ",clusterd[i]);
    printf("\n");

    /* modify one byte at position 4234 and write cluster 2 back */
    int pos;

    pos=4234;
    clusterd[4234]=123;
    if(size<=pos)size=pos+1; /* adapt size */

    if(dmsdos_write_cluster(sb,clusterd,size,2,0,0)<0)...error...

    free(clusterd);  /* free memory that is no longer needed */

    /* determine which cluster follows cluster 2 in fat */
    int fatval;

    fatval=dbl_fat_nextcluster(sb,2,NULL);
    if(fatval==0) printf("cluster 2 is unused\n");
    else if(fatval==-1) printf("cluster 2 is marked as EOF\n");
    else printf("cluster 2 is followed by cluster %d in FAT\n");

    /* mark it as EOF */
    int new;

    new=-1;
    dbl_fat_nextcluster(sb,2,&new);

    /* read root directory */
    int i;
    unsigned char*data;
    int number;
    struct buffer_head*bh;

    dblsb=MSDOS_SB(sb)->private_data;
    number=dblsb->s_rootdirentries*32/512;
    startblock=dblsb->s_rootdir;

    data=malloc(number*512);
    if(data==NULL)...error...

    for(i=0;i<number;++i)
    { bh=raw_bread(sb,startblock+i);
      if(bh==NULL)...error...
      memcpy(data+i*512,bh->b_data,512);
      raw_brelse(sb,bh);
    }
    /* complete root directory is now in data */

    /* modify root directory */
    ...modify data where you like...

    /* write it back completely */
    for(i=0;i<number;++i)
    { bh=raw_getblk(sb,startblock+i); /* or raw_bread(...) */
      if(bh==NULL)...error...
      raw_set_uptodate(sb,bh,1);      /* leave out if raw_bread used above */
      memcpy(bh->data,data+i*512,512);
      raw_mark_buffer_dirty(sb,bh,1);
      raw_brelse(sb,bh);
    }

    free(data); /* free memory that is no longer needed */

    /* we are done */
    close_cvf(sb);


For a piece of code that actually compiles and runs and does something
eventually useful and ... does not kill your data :-)) see the dcread 
example program (file dcread.c).