1 /*
2      cmap_data.c: read and write map sections.
3      Copyright (C) 2001  CCLRC, Charles Ballard
4 
5      This library is free software: you can redistribute it and/or
6      modify it under the terms of the GNU Lesser General Public License
7      version 3, modified in accordance with the provisions of the
8      license to address the requirements of UK law.
9 
10      You should have received a copy of the modified GNU Lesser General
11      Public License along with this library.  If not, copies may be
12      downloaded from http://www.ccp4.ac.uk/ccp4license.php
13 
14      This program is distributed in the hope that it will be useful,
15      but WITHOUT ANY WARRANTY; without even the implied warranty of
16      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17      GNU Lesser General Public License for more details.
18 */
19 #include <math.h>
20 #include <string.h>
21 #include <stdlib.h>
22 #include "cmaplib.h"
23 #include "cmap_data.h"
24 #include "cmap_stats.h"
25 #include "cmap_errno.h"
26 
27 /*! Internal: return the an estimate of the number of sections in the map
28   file based upon the length.
29   Update mfile->data.number as a side effect.
30   \param mfile (CMMFile *)
31   \return number of sections according to length-data/section_size */
number_sections(CMMFile * mfile)32 int number_sections(CMMFile *mfile)
33 {
34   div_t sections;
35 
36   sections = div(ccp4_file_length(mfile->stream)-mfile->data.offset,
37                  mfile->data.block_size);
38 
39   return mfile->data.number = sections.quot;
40 }
41 
42 /*! seek among the map sections.  The units are of size block_size.
43  \param mfile (CMMFile *)
44  \param sec (int) section number
45  \param whence (unsigned int) SEEK_SET, SEEK_CUR or SEEK_END
46  \return offset in file, or EOF */
ccp4_cmap_seek_section(CMMFile * mfile,int sec,unsigned int whence)47 int ccp4_cmap_seek_section(CMMFile *mfile, int sec, unsigned int whence)
48 {
49   size_t curr_posn;
50   div_t secs;
51   int result = EOF;
52 
53   if ( mfile == NULL ) {
54     ccp4_signal( CCP4_ERRLEVEL(3) | CMAP_ERRNO(CMERR_NoChannel),
55 		 "ccp4_cmap_seekdata",NULL);
56     return EOF; }
57 
58   switch (whence) {
59   case SEEK_SET:
60     if ( ccp4_file_is_read(mfile->stream) &&
61          ( sec < 0 || sec > mfile->data.number) )
62       ccp4_signal( CCP4_ERRLEVEL(2) | CMAP_ERRNO(CMERR_ParamError),
63 		 "ccp4_cmap_seek_section",NULL);
64     else
65       result = ccp4_file_raw_seek(mfile->stream, mfile->data.offset +
66                               sec * mfile->data.block_size, SEEK_SET);
67     break;
68   case SEEK_END:
69     if ( ccp4_file_is_read(mfile->stream) &&
70          ( sec > 0 || abs(sec) > mfile->data.number) )
71       ccp4_signal( CCP4_ERRLEVEL(2) | CMAP_ERRNO(CMERR_ParamError),
72 		 "ccp4_cmap_seek_section",NULL);
73     else
74       result = ccp4_file_raw_seek(mfile->stream, sec * mfile->data.block_size,
75                               SEEK_END);
76     break;
77   case SEEK_CUR:
78     curr_posn = ccp4_file_tell(mfile->stream);
79     secs = div(curr_posn - mfile->data.offset,mfile->data.block_size);
80     if ( ccp4_file_is_read(mfile->stream) &&
81        ( (secs.quot + sec) < 0 || (secs.quot + sec) >= mfile->data.number) )
82       ccp4_signal( CCP4_ERRLEVEL(2) | CMAP_ERRNO(CMERR_ParamError),
83 		 "ccp4_cmap_seek_section",NULL);
84     else
85       result = ccp4_file_raw_seek(mfile->stream,
86                               (sec > 0) ? (mfile->data.block_size - secs.rem +
87                                          (sec - 1)*mfile->data.block_size) :
88                                         (sec*mfile->data.block_size - secs.rem),
89                               SEEK_CUR);
90   }
91   return (result == EOF) ? EOF :
92     ((result - mfile->data.offset)/mfile->data.block_size);
93 }
94 
95 /*! write map section to file.
96   Note:  this wraps a raw write, with no location checking.  It is
97   therefore the responsibility of the calling program to ensure that
98   everything is correct.  Effectively assume appending to file.
99   \param mfile (CMMFile *)
100   \param section (const void *)
101   \return 1 on success, 0 on failure */
ccp4_cmap_write_section(CMMFile * mfile,const void * section)102 int ccp4_cmap_write_section(CMMFile *mfile, const void *section)
103 {
104   int result=0;
105   size_t write_dim;
106 
107   if (mfile == NULL || section == NULL) {
108     ccp4_signal( CCP4_ERRLEVEL(2) | CMAP_ERRNO(CMERR_NoChannel),
109 		 "ccp4_cmap_write_section",NULL);
110     return 0; }
111 
112   if (!ccp4_file_is_write(mfile->stream)) {
113     ccp4_signal( CCP4_ERRLEVEL(3) | CMAP_ERRNO(CMERR_WriteFail),
114 		 "ccp4_cmap_write_section",NULL);
115     return 0; }
116 
117   write_dim = mfile->map_dim[0] * mfile->map_dim[1];
118   result = ccp4_file_write(mfile->stream, section, write_dim);
119 
120 /* note that we have started writing */
121   mfile->data.number++;
122 
123   if (result != write_dim)
124         ccp4_signal( CCP4_ERRLEVEL(3) | CMAP_ERRNO(CMERR_WriteFail),
125 		 "ccp4_cmap_write_section",NULL);
126   else
127     if (mfile->data_mode == FLOAT32)
128       stats_update(&mfile->stats, (float *)section,
129                    (float *)section+write_dim);
130 
131   return (result == write_dim) ? 1 : 0;
132 }
133 
134 /*! read current map section from file to section.
135   Some checking is performed to ensure we are at the start of a
136   legitimate map section.
137   \param mfile (CMMFile *)
138   \param section (void *) array large enough to hold the map section
139   \return 1 on success, 0 on failure */
ccp4_cmap_read_section(CMMFile * mfile,void * section)140 int ccp4_cmap_read_section(CMMFile *mfile, void *section)
141 {
142   int result = 0;
143   div_t secs;
144   off_t curr_posn;
145   const off_t data_offset = 0;
146   size_t read_dim;
147 
148   if (mfile == NULL ) {
149     ccp4_signal( CCP4_ERRLEVEL(2) | CMAP_ERRNO(CMERR_NoChannel),
150 		 "ccp4_cmap_read_section",NULL);
151     return 0; }
152 
153   if (!ccp4_file_is_read(mfile->stream)) {
154     ccp4_signal( CCP4_ERRLEVEL(2) | CMAP_ERRNO(CMERR_ReadFail),
155 		 "ccp4_cmap_read_section",NULL);
156     return 0; }
157 
158   curr_posn = ccp4_file_tell(mfile->stream);
159 
160   secs = div(curr_posn - mfile->data.offset,
161              mfile->data.block_size);
162 
163 /* ensure legit section (although rely upon EOF ) */
164   if (secs.quot < 0 || secs.rem < 0) {
165     ccp4_file_raw_seek(mfile->stream, mfile->data.offset, SEEK_SET);
166     secs.quot = 0;
167   } else if( secs.rem > data_offset && secs.rem < mfile->data.section_size )
168     ccp4_file_raw_seek(mfile->stream, - secs.rem, SEEK_CUR);
169   else if ( secs.rem >= mfile->data.section_size ) {
170     ccp4_file_raw_seek(mfile->stream, (mfile->data.block_size-secs.rem), SEEK_CUR);
171     secs.quot++; }
172 
173   read_dim = mfile->map_dim[0] * mfile->map_dim[1];
174 /* do not read if at end */
175   if (secs.quot < 0 || secs.quot < mfile->data.number)
176     result = ccp4_file_read(mfile->stream, section, read_dim);
177 
178   if (result != read_dim)
179     ccp4_signal( CCP4_ERRLEVEL(3) | CMAP_ERRNO(CMERR_ReadFail),
180 		 "ccp4_cmap_read_section",NULL);
181 
182   return (result == read_dim) ? 1 : 0;
183 }
184 
185 /*! read current section header (character array)
186   After reading we are at the end of the local header
187   \param mfile (CMMFile *)
188   \param header (char *) character array large enough to hold
189   the local header (raw read so not string)
190   \return 1 on success, 0 on failure */
ccp4_cmap_read_section_header(const CMMFile * mfile,char * header)191 int ccp4_cmap_read_section_header(const CMMFile *mfile, char *header)
192 {
193   int result;
194   div_t secs;
195 
196   if (mfile == NULL || header == NULL) {
197     ccp4_signal( CCP4_ERRLEVEL(3) | CMAP_ERRNO(CMERR_NoChannel),
198 		 "ccp4_cmap_read_section_header",NULL);
199     return EOF; }
200 
201   if (!ccp4_file_is_read(mfile->stream)) {
202     ccp4_signal( CCP4_ERRLEVEL(3) | CMAP_ERRNO(CMERR_ReadFail),
203 		 "ccp4_cmap_read_section header",NULL);
204     return EOF; }
205 
206   if ( mfile->data.header_size == 0) return (0);
207 
208   result = ccp4_file_tell(mfile->stream);
209   secs = div(result - mfile->data.offset, mfile->data.block_size);
210 
211   if ( secs.quot < 0 || secs.quot >= mfile->data.number ) return (0);
212 
213 /* navigate to nearest header */
214   if ( secs.rem != mfile->data.section_size)
215     ccp4_file_raw_seek(mfile->stream,(mfile->data.section_size
216 				      - secs.rem), SEEK_CUR);
217 
218   if ( (result = ccp4_file_readchar( mfile->stream, (uint8 *) header,
219         mfile->data.header_size)) != mfile->data.header_size)
220     ccp4_signal(ccp4_errno,
221 		"ccp4_cmap_read_section_header",
222 		NULL);
223 
224   return (result == mfile->data.header_size) ? 1 : 0;
225 }
226 
227 /*! write the local section header to the file.  This must be of
228   size mfile->data.header.size.
229   Note: no checking is done so it is up to the calling program
230   to ensure that the file is in the correct location.  As seeking
231   is turned off, this assumes we are appending to the file.
232  \param mfile (CMMFile *)
233  \param header (const char *) the local header character array
234  (not necessarily a string)
235  \return number of bytes written or EOF */
ccp4_cmap_write_section_header(CMMFile * mfile,const char * header)236 int ccp4_cmap_write_section_header(CMMFile *mfile, const char *header)
237 {
238   char *output;
239   int result;
240 
241   if (mfile == NULL ) {
242     ccp4_signal( CCP4_ERRLEVEL(3) | CMAP_ERRNO(CMERR_NoChannel),
243 		 "ccp4_cmap_write_section_header",NULL);
244     return EOF; }
245 
246   if (!ccp4_file_is_write(mfile->stream)) {
247     ccp4_signal( CCP4_ERRLEVEL(3) | CMAP_ERRNO(CMERR_NoChannel),
248 		 "ccp4_cmap_write_section_header",NULL);
249     return EOF; }
250 
251   if ( mfile->data.header_size == 0)  return (0);
252 
253   output = (char *) malloc(mfile->data.header_size);
254   memset(output,' ', mfile->data.header_size);
255   if (header) memcpy(output, header,mfile->data.header_size);
256 
257   if ( (result = ccp4_file_writechar( mfile->stream, (uint8 *) output,
258                                      mfile->data.header_size))
259 	 != mfile->data.header_size)
260     ccp4_signal(ccp4_errno,
261 		"ccp4_cmap_write_section_header",
262 		NULL);
263 
264   return (result == mfile->data.header_size) ? 1 : 0;
265 }
266 
267 /*! seek a row within a map section
268  \param mfile (CMMFile *)
269  \param row (int)
270  \param whence (unsigned int) SEEK_SET, SEEK_END, SEEK_CUR
271  \return offset in file or EOF */
ccp4_cmap_seek_row(CMMFile * mfile,int row,unsigned int whence)272 int ccp4_cmap_seek_row(CMMFile *mfile, int row, unsigned int whence)
273 {
274   size_t curr_posn;
275   div_t secs, rows;
276   int result = EOF;
277   size_t item_size;
278 
279   if ( mfile == NULL ) {
280     ccp4_signal( CCP4_ERRLEVEL(2) | CMAP_ERRNO(CMERR_NoChannel),
281 		 "ccp4_cmap_seek_row",NULL);
282     return EOF; }
283 
284   item_size = ccp4_file_itemsize(mfile->stream);
285   curr_posn = ccp4_file_tell(mfile->stream);
286   secs = div(curr_posn - mfile->data.offset,mfile->data.block_size);
287 
288   switch (whence) {
289   case SEEK_SET:
290     if ( row < 0 || row >= mfile->map_dim[1])
291       ccp4_signal( CCP4_ERRLEVEL(2) | CMAP_ERRNO(CMERR_ParamError),
292 		 "ccp4_cmap_seek_row",NULL);
293     else
294       result = ccp4_file_raw_seek(mfile->stream, mfile->data.offset +
295                               (secs.quot * mfile->data.block_size +
296                               row * mfile->map_dim[0]*item_size),
297                               SEEK_SET);
298     break;
299   case SEEK_END:
300     if ( row >= 0 || abs(row) > mfile->map_dim[1])
301       ccp4_signal( CCP4_ERRLEVEL(2) | CMAP_ERRNO(CMERR_ParamError),
302 		 "ccp4_cmap_seek_row",NULL);
303     else
304       result = ccp4_file_raw_seek(mfile->stream, mfile->data.offset +
305                               (secs.quot * mfile->data.block_size +
306                               mfile->data.section_size +
307                               row * mfile->map_dim[0]*item_size),
308                               SEEK_SET);
309      break;
310   case SEEK_CUR:
311     rows = div(secs.rem,mfile->map_dim[0]*item_size);
312     if ( (rows.quot + row) < 0 || (rows.quot + row) >= mfile->data.number)
313        ccp4_signal( CCP4_ERRLEVEL(2) | CMAP_ERRNO(CMERR_ParamError),
314 		   "ccp4_cmap_seek_row",NULL);
315     else
316       result = ccp4_file_raw_seek(mfile->stream,
317                               ( row > 0) ? (mfile->map_dim[0]*item_size - rows.rem
318                                + (row-1)*mfile->map_dim[0]*item_size) :
319                                ( row*mfile->map_dim[0]*item_size - rows.rem),
320                               SEEK_CUR);
321   }
322   return (result);
323 }
324 
325 /*! write map row to file.
326   Note:  this wraps a raw write, with no location checking.  It is
327   therefore the responsibility of the calling program to ensure that
328   everything is correct.  Effectively assume appending to file.
329   \param mfile (CMMFile *)
330   \param row (const void *) data to be written
331   \return 1 on success, 0 on failure */
ccp4_cmap_write_row(CMMFile * mfile,const void * row)332 int ccp4_cmap_write_row(CMMFile *mfile, const void *row)
333 {
334   int result=0;
335 
336   if (mfile == NULL || row == NULL) {
337     ccp4_signal( CCP4_ERRLEVEL(2) | CMAP_ERRNO(CMERR_NoChannel),
338 		 "ccp4_cmap_write_row",NULL);
339     return EOF; }
340 
341   if (!ccp4_file_is_write(mfile->stream)) {
342     ccp4_signal( CCP4_ERRLEVEL(3) | CMAP_ERRNO(CMERR_WriteFail),
343 		 "ccp4_cmap_write_row",NULL);
344     return EOF; }
345 
346   result = ccp4_file_write(mfile->stream, row, mfile->map_dim[0]);
347 
348 /* note that we have started writing */
349   mfile->data.number++;
350 
351   if (result != mfile->map_dim[0])
352         ccp4_signal( CCP4_ERRLEVEL(3) | CMAP_ERRNO(CMERR_WriteFail),
353 		 "ccp4_cmap_write_row",NULL);
354   else
355     if (mfile->data_mode == FLOAT32)
356       stats_update(&mfile->stats, (float *)row, (float *)row+mfile->map_dim[0]);
357 
358   return (result == mfile->map_dim[0]) ? 1 : 0;
359 }
360 
361 /*! read current map section from file to section.
362   Some checking is performed to ensure we are at the start of a
363   legitimate map row.
364   \param mfile (CMMFile *)
365   \param row (void *) array large enough to hold the map row
366   \return 1 on success, 0 on failure */
ccp4_cmap_read_row(CMMFile * mfile,void * row)367 int ccp4_cmap_read_row(CMMFile *mfile, void *row)
368 {
369   int result = 0, item_size;
370   div_t secs, rows;
371   off_t curr_posn;
372 
373   if (mfile == NULL) {
374     ccp4_signal( CCP4_ERRLEVEL(2) | CMAP_ERRNO(CMERR_NoChannel),
375 		 "ccp4_cmap_read_row",NULL);
376     return EOF; }
377 
378   if (!ccp4_file_is_read(mfile->stream) || row == NULL) {
379     ccp4_signal( CCP4_ERRLEVEL(2) | CMAP_ERRNO(CMERR_ReadFail),
380 		 "ccp4_cmap_read_row",NULL);
381     return EOF; }
382 
383   item_size = ccp4_file_itemsize(mfile->stream);
384   curr_posn = ccp4_file_tell(mfile->stream);
385 
386   secs = div(curr_posn - mfile->data.offset,
387              mfile->data.block_size);
388   rows = div(secs.rem, mfile->map_dim[0]*item_size);
389 
390   if (secs.quot < 0 || secs.rem < 0)
391     ccp4_file_raw_seek(mfile->stream, mfile->data.offset, SEEK_SET);
392   else if(rows.quot >= mfile->map_dim[1] )
393     ccp4_file_raw_seek(mfile->stream, (mfile->data.block_size
394                        - secs.rem), SEEK_CUR);
395   else if( rows.rem != 0)
396     ccp4_file_raw_seek(mfile->stream, ( - secs.rem), SEEK_CUR);
397 
398   result = ccp4_file_read(mfile->stream, row, mfile->map_dim[0]);
399 
400   if (result != mfile->map_dim[0])
401     ccp4_signal( CCP4_ERRLEVEL(3) | CMAP_ERRNO(CMERR_ReadFail),
402 		 "ccp4_cmap_read_row",NULL);
403 
404   return (result == mfile->map_dim[0]) ? 1 : 0;
405 }
406 
407 /*! raw seek in items
408   \param mfile (CMMFile *)
409   \param offset (int) number of items
410   \param whence (unsigned int) SEEK_SET, SEEK_CUR, SEEK_END;
411   \return 0 on success, EOF on failure */
ccp4_cmap_seek_data(CMMFile * mfile,int offset,unsigned int whence)412 int ccp4_cmap_seek_data(CMMFile *mfile, int offset, unsigned int whence)
413 {
414   int result = EOF;
415 
416   if ( mfile == NULL ) {
417     ccp4_signal( CCP4_ERRLEVEL(2) | CMAP_ERRNO(CMERR_NoChannel),
418 		 "ccp4_cmap_seekdata",NULL);
419     return (result); }
420 
421   if ((result = ccp4_file_seek( mfile->stream, offset, whence)) == -1)
422     ccp4_signal(ccp4_errno, "ccp4_cmap_seek_data",NULL);
423 
424   return (result);
425 }
426 
427 /*! raw write of nelements items to file, according to the datamode,
428    at current location
429  \param mfile (const CMMFile *)
430  \param section (void *) values written, should contain at least
431  nelements items
432  \param n_items (int) number of items to be written
433  \return number of items written or EOF */
ccp4_cmap_write_data(CMMFile * mfile,const void * items,int n_items)434 int ccp4_cmap_write_data(CMMFile *mfile, const void *items, int n_items)
435 {
436   int result=0;
437 
438   if (mfile == NULL || items == NULL) {
439     ccp4_signal( CCP4_ERRLEVEL(2) | CMAP_ERRNO(CMERR_NoChannel),
440 		 "ccp4_cmap_write_data",NULL);
441     return EOF; }
442 
443   if (ccp4_file_is_write(mfile->stream)) {
444     result = ccp4_file_write(mfile->stream, (uint8 *) items, n_items);
445     if (result != n_items)
446       ccp4_signal( CCP4_ERRLEVEL(3) | CMAP_ERRNO(CMERR_WriteFail),
447 		 "ccp4_cmap_write_data",NULL);
448      else if (mfile->data_mode == FLOAT32)
449       stats_update(&mfile->stats, (float *)items, (float *)items+result);
450   }
451 
452   return (result);
453 }
454 
455 /*! raw read of nelements items from file according to the datamode
456    at current location
457  \param mfile (const CMMFile *)
458  \param items (void *) values read to here, so should have enough space
459  for nelements items
460  \param n_items (int) number of items to be read
461  \return number of items read or EOF */
ccp4_cmap_read_data(const CMMFile * mfile,void * items,int n_items)462 int ccp4_cmap_read_data(const CMMFile *mfile, void *items, int n_items)
463 {
464   int result=0;
465 
466   if (mfile == NULL || items == NULL) {
467     ccp4_signal( CCP4_ERRLEVEL(2) | CMAP_ERRNO(CMERR_NoChannel),
468 		 "ccp4_cmap_read_data",NULL);
469     return EOF; }
470 
471   if (ccp4_file_is_read(mfile->stream))
472     result = ccp4_file_read(mfile->stream, (uint8 *) items, n_items);
473 
474   return (result);
475 }
476 
477