source: git/src/img.c @ a4454620

RELEASE/1.2debug-cidebug-ci-sanitisersstereowalls-data
Last change on this file since a4454620 was a4454620, checked in by Olly Betts <olly@…>, 10 years ago

src/img.c,src/useful.h: Fix some glibc fortify source warnings.

  • Property mode set to 100644
File size: 68.8 KB
Line 
1/* img.c
2 * Routines for reading and writing Survex ".3d" image files
3 * Copyright (C) 1993-2004,2005,2006,2010,2011,2013,2014 Olly Betts
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
18 */
19
20#ifdef HAVE_CONFIG_H
21# include <config.h>
22#endif
23
24#include <ctype.h>
25#include <errno.h>
26#include <limits.h>
27#include <locale.h>
28#include <stdio.h>
29#include <stdlib.h>
30#include <string.h>
31#include <time.h>
32
33#include "img.h"
34
35#define TIMENA "?"
36#ifdef IMG_HOSTED
37# define INT32_T int32_t
38# include "debug.h"
39# include "filelist.h"
40# include "filename.h"
41# include "message.h"
42# include "useful.h"
43# define TIMEFMT msg(/*%a,%Y.%m.%d %H:%M:%S %Z*/107)
44#else
45# ifdef HAVE_STDINT_H
46#  include <stdint.h>
47#  define INT32_T int32_t
48# else
49#  include <limits.h>
50#  if INT_MAX >= 2147483647
51#   define INT32_T int
52#  else
53#   define INT32_T long
54#  endif
55# endif
56# define TIMEFMT "%a,%Y.%m.%d %H:%M:%S %Z"
57# define EXT_SVX_3D "3d"
58# define EXT_SVX_POS "pos"
59# define FNM_SEP_EXT '.'
60# define METRES_PER_FOOT 0.3048 /* exact value */
61# define xosmalloc(L) malloc((L))
62# define xosrealloc(L,S) realloc((L),(S))
63# define osfree(P) free((P))
64# define osnew(T) (T*)malloc(sizeof(T))
65
66/* in IMG_HOSTED mode, this tests if a filename refers to a directory */
67# define fDirectory(X) 0
68/* open file FNM with mode MODE, maybe using path PTH and/or extension EXT */
69/* path isn't used in img.c, but EXT is */
70# define fopenWithPthAndExt(PTH,FNM,EXT,MODE,X) \
71    ((*(X) = NULL), fopen(FNM,MODE))
72# ifndef PUTC
73#  define PUTC(C, FH) putc(C, FH)
74# endif
75# ifndef GETC
76#  define GETC(FH) getc(FH)
77# endif
78# define fputsnl(S, FH) (fputs((S), (FH)) == EOF ? EOF : putc('\n', (FH)))
79# define SVX_ASSERT(X)
80
81#ifdef __cplusplus
82# include <algorithm>
83using std::max;
84using std::min;
85#else
86/* Return max/min of two numbers. */
87/* May be defined already (e.g. by Borland C in stdlib.h) */
88/* NB Bad news if X or Y has side-effects... */
89# ifndef max
90#  define max(X, Y) ((X) > (Y) ? (X) : (Y))
91# endif
92# ifndef min
93#  define min(X, Y) ((X) < (Y) ? (X) : (Y))
94# endif
95#endif
96
97static INT32_T
98get32(FILE *fh)
99{
100   INT32_T w = GETC(fh);
101   w |= (INT32_T)GETC(fh) << 8l;
102   w |= (INT32_T)GETC(fh) << 16l;
103   w |= (INT32_T)GETC(fh) << 24l;
104   return w;
105}
106
107static void
108put32(long w, FILE *fh)
109{
110   PUTC((char)(w), fh);
111   PUTC((char)(w >> 8l), fh);
112   PUTC((char)(w >> 16l), fh);
113   PUTC((char)(w >> 24l), fh);
114}
115
116static short
117get16(FILE *fh)
118{
119   short w = GETC(fh);
120   w |= (short)GETC(fh) << 8l;
121   return w;
122}
123
124static void
125put16(short w, FILE *fh)
126{
127   PUTC((char)(w), fh);
128   PUTC((char)(w >> 8l), fh);
129}
130
131static char *
132baseleaf_from_fnm(const char *fnm)
133{
134   const char *p;
135   const char *q;
136   char * res;
137   size_t len;
138
139   p = fnm;
140   q = strrchr(p, '/');
141   if (q) p = q + 1;
142   q = strrchr(p, '\\');
143   if (q) p = q + 1;
144
145   q = strrchr(p, FNM_SEP_EXT);
146   if (q) len = (const char *)q - p; else len = strlen(p);
147
148   res = (char *)xosmalloc(len + 1);
149   if (!res) return NULL;
150   memcpy(res, p, len);
151   res[len] = '\0';
152   return res;
153}
154#endif
155
156static char * my_strdup(const char *str);
157
158static time_t
159mktime_with_tz(struct tm * tm, const char * tz)
160{
161    time_t r;
162    char * old_tz = getenv("TZ");
163#ifdef _MSC_VER
164    if (old_tz) {
165        old_tz = my_strdup(old_tz);
166        if (!old_tz)
167            return (time_t)-1;
168    }
169    if (_putenv_s("TZ", tz) != 0) {
170        osfree(old_tz);
171        return (time_t)-1;
172    }
173#elif defined HAVE_SETENV
174    if (old_tz) {
175        old_tz = my_strdup(old_tz);
176        if (!old_tz)
177            return (time_t)-1;
178    }
179    if (setenv("TZ", tz, 1) < 0) {
180        osfree(old_tz);
181        return (time_t)-1;
182    }
183#else
184    char * p;
185    if (old_tz) {
186        size_t len = strlen(old_tz) + 1;
187        p = (char *)xosmalloc(len + 3);
188        if (!p)
189            return (time_t)-1;
190        memcpy(p, "TZ=", 3);
191        memcpy(p + 3, tz, len);
192        old_tz = p;
193    }
194    p = (char *)xosmalloc(strlen(tz) + 4);
195    if (!p) {
196        osfree(old_tz);
197        return (time_t)-1;
198    }
199    memcpy(p, "TZ=", 3);
200    strcpy(p + 3, tz);
201    if (putenv(p) != 0) {
202        osfree(p);
203        osfree(old_tz);
204        return (time_t)-1;
205    }
206#define CLEANUP() osfree(p)
207#endif
208    tzset();
209    r = mktime(tm);
210    if (old_tz) {
211#ifdef _MSC_VER
212        _putenv_s("TZ", old_tz);
213#elif !defined HAVE_SETENV
214        putenv(old_tz);
215#else
216        setenv("TZ", old_tz, 1);
217#endif
218        osfree(old_tz);
219    } else {
220#ifdef _MSC_VER
221        _putenv_s("TZ", "");
222#elif !defined HAVE_UNSETENV
223        putenv((char*)"TZ");
224#else
225        unsetenv("TZ");
226#endif
227    }
228#ifdef CLEANUP
229    CLEANUP();
230#undef CLEANUP
231#endif
232    return r;
233}
234
235static unsigned short
236getu16(FILE *fh)
237{
238   return (unsigned short)get16(fh);
239}
240
241#include <math.h>
242
243#if !defined HAVE_LROUND && !defined HAVE_DECL_LROUND
244/* The autoconf tests are not in use, but C99 and C++11 both added lround(),
245 * so set HAVE_LROUND and HAVE_DECL_LROUND conservatively based on the language
246 * standard version the compiler claims to support. */
247# if (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L) || \
248     (defined __cplusplus && __cplusplus >= 201103L)
249#  define HAVE_LROUND 1
250#  define HAVE_DECL_LROUND 1
251# endif
252#endif
253
254#ifdef HAVE_LROUND
255# if defined HAVE_DECL_LROUND && !HAVE_DECL_LROUND
256/* On older systems, the prototype may be missing. */
257extern long lround(double);
258# endif
259# define my_lround lround
260#else
261static long
262my_lround(double x) {
263   return (x >= 0.0) ? (long)(x + 0.5) : -(long)(0.5 - x);
264}
265#endif
266
267/* portable case insensitive string compare */
268#if defined(strcasecmp) || defined(HAVE_STRCASECMP)
269# define my_strcasecmp strcasecmp
270#else
271static int my_strcasecmp(const char *s1, const char *s2) {
272   unsigned char c1, c2;
273   do {
274      c1 = *s1++;
275      c2 = *s2++;
276   } while (c1 && toupper(c1) == toupper(c2));
277   /* now calculate real difference */
278   return c1 - c2;
279}
280#endif
281
282unsigned int img_output_version = IMG_VERSION_MAX;
283
284static img_errcode img_errno = IMG_NONE;
285
286#define FILEID "Survex 3D Image File"
287
288#define EXT_PLT "plt"
289#define EXT_PLF "plf"
290
291/* Attempt to string paste to ensure we are passed a literal string */
292#define LITLEN(S) (sizeof(S"") - 1)
293
294/* Fake "version numbers" for non-3d formats we can read. */
295#define VERSION_CMAP_SHOT       -4
296#define VERSION_CMAP_STATION    -3
297#define VERSION_COMPASS_PLT     -2
298#define VERSION_SURVEX_POS      -1
299
300static char *
301my_strdup(const char *str)
302{
303   char *p;
304   size_t len = strlen(str) + 1;
305   p = (char *)xosmalloc(len);
306   if (p) memcpy(p, str, len);
307   return p;
308}
309
310static char *
311getline_alloc(FILE *fh)
312{
313   int ch;
314   size_t i = 0;
315   size_t len = 16;
316   char *buf = (char *)xosmalloc(len);
317   if (!buf) return NULL;
318
319   ch = GETC(fh);
320   while (ch != '\n' && ch != '\r' && ch != EOF) {
321      buf[i++] = ch;
322      if (i == len - 1) {
323         char *p;
324         len += len;
325         p = (char *)xosrealloc(buf, len);
326         if (!p) {
327            osfree(buf);
328            return NULL;
329         }
330         buf = p;
331      }
332      ch = GETC(fh);
333   }
334   if (ch == '\n' || ch == '\r') {
335      int otherone = ch ^ ('\n' ^ '\r');
336      ch = GETC(fh);
337      /* if it's not the other eol character, put it back */
338      if (ch != otherone) ungetc(ch, fh);
339   }
340   buf[i++] = '\0';
341   return buf;
342}
343
344img_errcode
345img_error(void)
346{
347   return img_errno;
348}
349
350static int
351check_label_space(img *pimg, size_t len)
352{
353   if (len > pimg->buf_len) {
354      char *b = (char *)xosrealloc(pimg->label_buf, len);
355      if (!b) return 0;
356      pimg->label = (pimg->label - pimg->label_buf) + b;
357      pimg->label_buf = b;
358      pimg->buf_len = len;
359   }
360   return 1;
361}
362
363#define has_ext(F,L,E) ((L) > LITLEN(E) + 1 &&\
364                        (F)[(L) - LITLEN(E) - 1] == FNM_SEP_EXT &&\
365                        my_strcasecmp((F) + (L) - LITLEN(E), E) == 0)
366
367img *
368img_open_survey(const char *fnm, const char *survey)
369{
370   img *pimg;
371   size_t len;
372   char buf[LITLEN(FILEID) + 9];
373   int ch;
374
375   if (fDirectory(fnm)) {
376      img_errno = IMG_DIRECTORY;
377      return NULL;
378   }
379
380   pimg = osnew(img);
381   if (pimg == NULL) {
382      img_errno = IMG_OUTOFMEMORY;
383      return NULL;
384   }
385
386   pimg->buf_len = 257;
387   pimg->label_buf = (char *)xosmalloc(pimg->buf_len);
388   if (!pimg->label_buf) {
389      osfree(pimg);
390      img_errno = IMG_OUTOFMEMORY;
391      return NULL;
392   }
393
394   pimg->fh = fopenWithPthAndExt("", fnm, EXT_SVX_3D, "rb", &(pimg->filename_opened));
395   if (pimg->fh == NULL) {
396      osfree(pimg->label_buf);
397      osfree(pimg);
398      img_errno = IMG_FILENOTFOUND;
399      return NULL;
400   }
401
402   pimg->fRead = 1; /* reading from this file */
403   img_errno = IMG_NONE;
404
405   pimg->flags = 0;
406
407   /* for version >= 3 we use label_buf to store the prefix for reuse */
408   /* for VERSION_COMPASS_PLT, 0 value indicates we haven't
409    * entered a survey yet */
410   /* for VERSION_CMAP_SHOT, we store the last station here
411    * to detect whether we MOVE or LINE */
412   pimg->label_len = 0;
413   pimg->label_buf[0] = '\0';
414
415   pimg->survey = NULL;
416   pimg->survey_len = 0;
417   pimg->separator = '.';
418#if IMG_API_VERSION == 0
419   pimg->date1 = pimg->date2 = 0;
420#else /* IMG_API_VERSION == 1 */
421   pimg->days1 = pimg->days2 = -1;
422#endif
423   pimg->is_extended_elevation = 0;
424
425   pimg->style = pimg->oldstyle = img_STYLE_UNKNOWN;
426
427   pimg->l = pimg->r = pimg->u = pimg->d = -1.0;
428
429   pimg->title = pimg->datestamp = NULL;
430   pimg->datestamp_numeric = (time_t)-1;
431
432   if (survey) {
433      len = strlen(survey);
434      if (len) {
435         if (survey[len - 1] == '.') len--;
436         if (len) {
437            char *p;
438            pimg->survey = (char *)xosmalloc(len + 2);
439            if (!pimg->survey) {
440               img_errno = IMG_OUTOFMEMORY;
441               goto error;
442            }
443            memcpy(pimg->survey, survey, len);
444            /* Set title to leaf survey name */
445            pimg->survey[len] = '\0';
446            p = strchr(pimg->survey, '.');
447            if (p) p++; else p = pimg->survey;
448            pimg->title = my_strdup(p);
449            if (!pimg->title) {
450               img_errno = IMG_OUTOFMEMORY;
451               goto error;
452            }
453            pimg->survey[len] = '.';
454            pimg->survey[len + 1] = '\0';
455         }
456      }
457      pimg->survey_len = len;
458   }
459
460   /* [VERSION_COMPASS_PLT, VERSION_CMAP_STATION, VERSION_CMAP_SHOT] pending
461    * IMG_LINE or IMG_MOVE - both have 4 added.
462    * [VERSION_SURVEX_POS] already skipped heading line, or there wasn't one
463    * [version 0] not in the middle of a 'LINE' command
464    * [version >= 3] not in the middle of turning a LINE into a MOVE
465    */
466   pimg->pending = 0;
467
468   len = strlen(fnm);
469   if (has_ext(fnm, len, EXT_SVX_POS)) {
470pos_file:
471      pimg->version = VERSION_SURVEX_POS;
472      if (!pimg->survey) pimg->title = baseleaf_from_fnm(fnm);
473      pimg->datestamp = my_strdup(TIMENA);
474      if (!pimg->datestamp) {
475         img_errno = IMG_OUTOFMEMORY;
476         goto error;
477      }
478      pimg->start = 0;
479      return pimg;
480   }
481
482   if (has_ext(fnm, len, EXT_PLT) || has_ext(fnm, len, EXT_PLF)) {
483      long fpos;
484plt_file:
485      pimg->version = VERSION_COMPASS_PLT;
486      /* Spaces aren't legal in Compass station names, but dots are, so
487       * use space as the level separator */
488      pimg->separator = ' ';
489      pimg->start = 0;
490      if (!pimg->survey) pimg->title = baseleaf_from_fnm(fnm);
491      pimg->datestamp = my_strdup(TIMENA);
492      if (!pimg->datestamp) {
493         img_errno = IMG_OUTOFMEMORY;
494         goto error;
495      }
496      while (1) {
497         ch = GETC(pimg->fh);
498         switch (ch) {
499          case '\x1a':
500            fseek(pimg->fh, -1, SEEK_CUR);
501            /* FALL THRU */
502          case EOF:
503            pimg->start = ftell(pimg->fh);
504            return pimg;
505          case 'N': {
506            char *line, *q;
507            fpos = ftell(pimg->fh) - 1;
508            if (!pimg->survey) {
509               /* FIXME : if there's only one survey in the file, it'd be nice
510                * to use its description as the title here...
511                */
512               ungetc('N', pimg->fh);
513               pimg->start = fpos;
514               return pimg;
515            }
516            line = getline_alloc(pimg->fh);
517            if (!line) {
518               img_errno = IMG_OUTOFMEMORY;
519               goto error;
520            }
521            len = 0;
522            while (line[len] > 32) ++len;
523            if (pimg->survey_len != len ||
524                memcmp(line, pimg->survey, len) != 0) {
525               osfree(line);
526               continue;
527            }
528            q = strchr(line + len, 'C');
529            if (q && q[1]) {
530                osfree(pimg->title);
531                pimg->title = my_strdup(q + 1);
532            } else if (!pimg->title) {
533                pimg->title = my_strdup(pimg->label);
534            }
535            osfree(line);
536            if (!pimg->title) {
537                img_errno = IMG_OUTOFMEMORY;
538                goto error;
539            }
540            if (!pimg->start) pimg->start = fpos;
541            fseek(pimg->fh, pimg->start, SEEK_SET);
542            return pimg;
543          }
544          case 'M': case 'D':
545            pimg->start = ftell(pimg->fh) - 1;
546            break;
547         }
548         while (ch != '\n' && ch != '\r') {
549            ch = GETC(pimg->fh);
550         }
551      }
552   }
553
554   /* Although these are often referred to as "CMAP .XYZ files", it seems
555    * that actually, the extension .XYZ isn't used, rather .SHT (shot
556    * variant, produced by CMAP v16 and later), .UNA (unadjusted) and
557    * .ADJ (adjusted) extensions are.  Since img has long checked for
558    * .XYZ, we continue to do so in case anyone is relying on it.
559    */
560   if (has_ext(fnm, len, "sht") ||
561       has_ext(fnm, len, "adj") ||
562       has_ext(fnm, len, "una") ||
563       has_ext(fnm, len, "xyz")) {
564      char *line;
565xyz_file:
566      /* Spaces aren't legal in CMAP station names, but dots are, so
567       * use space as the level separator. */
568      pimg->separator = ' ';
569      line = getline_alloc(pimg->fh);
570      if (!line) {
571         img_errno = IMG_OUTOFMEMORY;
572         goto error;
573      }
574      /* There doesn't seem to be a spec for what happens after 1999 with cmap
575       * files, so this code allows for:
576       *  * 21xx -> xx (up to 2150)
577       *  * 21xx -> 1xx (up to 2199)
578       *  * full year being specified instead of 2 digits
579       */
580      len = strlen(line);
581      if (len > 59) {
582         /* Don't just truncate at column 59, allow for a > 2 digit year. */
583         char * p = strstr(line + len, "Page");
584         if (p) {
585            while (p > line && p[-1] == ' ')
586               --p;
587            *p = '\0';
588            len = p - line;
589         } else {
590            line[59] = '\0';
591         }
592      }
593      if (len > 45) {
594         /* YY/MM/DD HH:MM */
595         struct tm tm;
596         unsigned long v;
597         char * p;
598         pimg->datestamp = my_strdup(line + 45);
599         p = pimg->datestamp;
600         v = strtoul(p, &p, 10);
601         if (v <= 50) {
602            /* In the absence of a spec for cmap files, assume <= 50 means 21st
603             * century. */
604            v += 2000;
605         } else if (v < 200) {
606            /* Map 100-199 to 21st century. */
607            v += 1900;
608         }
609         if (v == ULONG_MAX || *p++ != '/')
610            goto bad_cmap_date;
611         tm.tm_year = v - 1900;
612         v = strtoul(p, &p, 10);
613         if (v < 1 || v > 12 || *p++ != '/')
614            goto bad_cmap_date;
615         tm.tm_mon = v - 1;
616         v = strtoul(p, &p, 10);
617         if (v < 1 || v > 31 || *p++ != ' ')
618            goto bad_cmap_date;
619         tm.tm_mday = v;
620         v = strtoul(p, &p, 10);
621         if (v >= 24 || *p++ != ':')
622            goto bad_cmap_date;
623         tm.tm_hour = v;
624         v = strtoul(p, &p, 10);
625         if (v >= 60)
626            goto bad_cmap_date;
627         tm.tm_min = v;
628         if (*p == ':') {
629            v = strtoul(p + 1, &p, 10);
630            if (v > 60)
631               goto bad_cmap_date;
632            tm.tm_sec = v;
633         } else {
634            tm.tm_sec = 0;
635         }
636         tm.tm_isdst = 0;
637         /* We have no indication of what timezone this timestamp is in.  It's
638          * probably local time for whoever processed the data, so just assume
639          * UTC, which is at least fairly central in the possibilities.
640          */
641         pimg->datestamp_numeric = mktime_with_tz(&tm, "");
642      } else {
643         pimg->datestamp = my_strdup(TIMENA);
644      }
645bad_cmap_date:
646      if (strncmp(line, "  Cave Survey Data Processed by CMAP ",
647                  LITLEN("  Cave Survey Data Processed by CMAP ")) == 0) {
648         len = 0;
649      } else {
650         if (len > 45) {
651            line[45] = '\0';
652            len = 45;
653         }
654         while (len > 2 && line[len - 1] == ' ') --len;
655         if (len > 2) {
656            line[len] = '\0';
657            pimg->title = my_strdup(line + 2);
658         }
659      }
660      if (len <= 2) pimg->title = baseleaf_from_fnm(fnm);
661      osfree(line);
662      if (!pimg->datestamp || !pimg->title) {
663         img_errno = IMG_OUTOFMEMORY;
664         goto error;
665      }
666      line = getline_alloc(pimg->fh);
667      if (!line) {
668         img_errno = IMG_OUTOFMEMORY;
669         goto error;
670      }
671      if (line[0] != ' ' || (line[1] != 'S' && line[1] != 'O')) {
672         img_errno = IMG_BADFORMAT;
673         goto error;
674      }
675      if (line[1] == 'S') {
676         pimg->version = VERSION_CMAP_STATION;
677      } else {
678         pimg->version = VERSION_CMAP_SHOT;
679      }
680      osfree(line);
681      line = getline_alloc(pimg->fh);
682      if (!line) {
683         img_errno = IMG_OUTOFMEMORY;
684         goto error;
685      }
686      if (line[0] != ' ' || line[1] != '-') {
687         img_errno = IMG_BADFORMAT;
688         goto error;
689      }
690      osfree(line);
691      pimg->start = ftell(pimg->fh);
692      return pimg;
693   }
694
695   if (fread(buf, LITLEN(FILEID) + 1, 1, pimg->fh) != 1 ||
696       memcmp(buf, FILEID"\n", LITLEN(FILEID) + 1) != 0) {
697      if (fread(buf + LITLEN(FILEID) + 1, 8, 1, pimg->fh) == 1 &&
698          memcmp(buf, FILEID"\r\nv0.01\r\n", LITLEN(FILEID) + 9) == 0) {
699         /* v0 3d file with DOS EOLs */
700         pimg->version = 0;
701         goto v03d;
702      }
703      rewind(pimg->fh);
704      if (buf[1] == ' ') {
705         if (buf[0] == ' ') {
706            /* Looks like a CMAP .xyz file ... */
707            goto xyz_file;
708         } else if (strchr("ZSNF", buf[0])) {
709            /* Looks like a Compass .plt file ... */
710            /* Almost certainly it'll start "Z " */
711            goto plt_file;
712         }
713      }
714      if (buf[0] == '(') {
715         /* Looks like a Survex .pos file ... */
716         goto pos_file;
717      }
718      img_errno = IMG_BADFORMAT;
719      goto error;
720   }
721
722   /* check file format version */
723   ch = GETC(pimg->fh);
724   pimg->version = 0;
725   if (tolower(ch) == 'b') {
726      /* binary file iff B/b prefix */
727      pimg->version = 1;
728      ch = GETC(pimg->fh);
729   }
730   if (ch != 'v') {
731      img_errno = IMG_BADFORMAT;
732      goto error;
733   }
734   ch = GETC(pimg->fh);
735   if (ch == '0') {
736      if (fread(buf, 4, 1, pimg->fh) != 1 || memcmp(buf, ".01\n", 4) != 0) {
737         img_errno = IMG_BADFORMAT;
738         goto error;
739      }
740      /* nothing special to do */
741   } else if (pimg->version == 0) {
742      if (ch < '2' || ch > '0' + IMG_VERSION_MAX || GETC(pimg->fh) != '\n') {
743         img_errno = IMG_TOONEW;
744         goto error;
745      }
746      pimg->version = ch - '0';
747   } else {
748      img_errno = IMG_BADFORMAT;
749      goto error;
750   }
751
752v03d:
753   if (!pimg->title)
754       pimg->title = getline_alloc(pimg->fh);
755   else
756       osfree(getline_alloc(pimg->fh));
757   pimg->datestamp = getline_alloc(pimg->fh);
758   if (!pimg->title || !pimg->datestamp) {
759      img_errno = IMG_OUTOFMEMORY;
760      error:
761      osfree(pimg->title);
762      osfree(pimg->datestamp);
763      osfree(pimg->filename_opened);
764      fclose(pimg->fh);
765      osfree(pimg);
766      return NULL;
767   }
768
769   if (pimg->version >= 8) {
770      int flags = GETC(pimg->fh);
771      if (flags & img_FFLAG_EXTENDED) pimg->is_extended_elevation = 1;
772   } else {
773      len = strlen(pimg->title);
774      if (len > 11 && strcmp(pimg->title + len - 11, " (extended)") == 0) {
775          pimg->title[len - 11] = '\0';
776          pimg->is_extended_elevation = 1;
777      }
778   }
779
780   if (pimg->datestamp[0] == '@') {
781      unsigned long v;
782      char * p;
783      errno = 0;
784      v = strtoul(pimg->datestamp + 1, &p, 10);
785      if (errno == 0 && *p == '\0')
786         pimg->datestamp_numeric = v;
787      /* FIXME: We're assuming here that the C time_t epoch is 1970, which is
788       * true for Unix-like systems, Mac OS X and Windows, but isn't guaranteed
789       * by ISO C.
790       */
791   } else {
792      /* %a,%Y.%m.%d %H:%M:%S %Z */
793      struct tm tm;
794      unsigned long v;
795      char * p = pimg->datestamp;
796      while (isalpha((unsigned char)*p)) ++p;
797      if (*p == ',') ++p;
798      while (isspace((unsigned char)*p)) ++p;
799      v = strtoul(p, &p, 10);
800      if (v == ULONG_MAX || *p++ != '.')
801         goto bad_3d_date;
802      tm.tm_year = v - 1900;
803      v = strtoul(p, &p, 10);
804      if (v < 1 || v > 12 || *p++ != '.')
805         goto bad_3d_date;
806      tm.tm_mon = v - 1;
807      v = strtoul(p, &p, 10);
808      if (v < 1 || v > 31 || *p++ != ' ')
809         goto bad_3d_date;
810      tm.tm_mday = v;
811      v = strtoul(p, &p, 10);
812      if (v >= 24 || *p++ != ':')
813         goto bad_3d_date;
814      tm.tm_hour = v;
815      v = strtoul(p, &p, 10);
816      if (v >= 60 || *p++ != ':')
817         goto bad_3d_date;
818      tm.tm_min = v;
819      v = strtoul(p, &p, 10);
820      if (v > 60)
821         goto bad_3d_date;
822      tm.tm_sec = v;
823      tm.tm_isdst = 0;
824      while (isspace((unsigned char)*p)) ++p;
825      /* p now points to the timezone string.
826       *
827       * However, it's likely to be a string like "BST", and such strings can
828       * be ambiguous (BST could be UTC+1 or UTC+6), so it is impossible to
829       * reliably convert in all cases.  Just pass what we have to tzset() - if
830       * it doesn't handle it, UTC will be used.
831       */
832      pimg->datestamp_numeric = mktime_with_tz(&tm, p);
833   }
834bad_3d_date:
835
836   pimg->start = ftell(pimg->fh);
837
838   return pimg;
839}
840
841int
842img_rewind(img *pimg)
843{
844   if (!pimg->fRead) {
845      img_errno = IMG_WRITEERROR;
846      return 0;
847   }
848   if (fseek(pimg->fh, pimg->start, SEEK_SET) != 0) {
849      img_errno = IMG_READERROR;
850      return 0;
851   }
852   clearerr(pimg->fh);
853   /* [VERSION_SURVEX_POS] already skipped heading line, or there wasn't one
854    * [version 0] not in the middle of a 'LINE' command
855    * [version >= 3] not in the middle of turning a LINE into a MOVE */
856   pimg->pending = 0;
857
858   img_errno = IMG_NONE;
859
860   /* for version >= 3 we use label_buf to store the prefix for reuse */
861   /* for VERSION_COMPASS_PLT, 0 value indicates we haven't entered a survey
862    * yet */
863   /* for VERSION_CMAP_SHOT, we store the last station here to detect whether
864    * we MOVE or LINE */
865   pimg->label_len = 0;
866   pimg->style = img_STYLE_UNKNOWN;
867   return 1;
868}
869
870img *
871img_open_write(const char *fnm, char *title, int flags)
872{
873   time_t tm;
874   img *pimg;
875
876   if (fDirectory(fnm)) {
877      img_errno = IMG_DIRECTORY;
878      return NULL;
879   }
880
881   pimg = osnew(img);
882   if (pimg == NULL) {
883      img_errno = IMG_OUTOFMEMORY;
884      return NULL;
885   }
886
887   pimg->buf_len = 257;
888   pimg->label_buf = (char *)xosmalloc(pimg->buf_len);
889   if (!pimg->label_buf) {
890      osfree(pimg);
891      img_errno = IMG_OUTOFMEMORY;
892      return NULL;
893   }
894
895   pimg->fh = fopen(fnm, "wb");
896   if (!pimg->fh) {
897      osfree(pimg->label_buf);
898      osfree(pimg);
899      img_errno = IMG_CANTOPENOUT;
900      return NULL;
901   }
902
903   pimg->filename_opened = NULL;
904
905   /* Output image file header */
906   fputs("Survex 3D Image File\n", pimg->fh); /* file identifier string */
907   if (img_output_version < 2) {
908      pimg->version = 1;
909      fputs("Bv0.01\n", pimg->fh); /* binary file format version number */
910   } else {
911      pimg->version = (img_output_version > IMG_VERSION_MAX) ? IMG_VERSION_MAX : img_output_version;
912      fprintf(pimg->fh, "v%d\n", pimg->version); /* file format version no. */
913   }
914
915   fputs(title, pimg->fh);
916   if (pimg->version < 8 && (flags & img_FFLAG_EXTENDED)) {
917      /* Older format versions append " (extended)" to the title to mark
918       * extended elevations. */
919      size_t len = strlen(title);
920      if (len < 11 || strcmp(title + len - 11, " (extended)") != 0)
921         fputs(" (extended)", pimg->fh);
922   }
923   PUTC('\n', pimg->fh);
924
925   tm = time(NULL);
926   if (tm == (time_t)-1) {
927      fputsnl(TIMENA, pimg->fh);
928   } else if (pimg->version <= 7) {
929      char date[256];
930      /* output current date and time in format specified */
931      strftime(date, 256, TIMEFMT, localtime(&tm));
932      fputsnl(date, pimg->fh);
933   } else {
934      fprintf(pimg->fh, "@%ld\n", (long)tm);
935   }
936
937   if (pimg->version >= 8) {
938      /* Clear bit one in case anyone has been passing true for fBinary. */
939      flags &=~ 1;
940      PUTC(flags, pimg->fh);
941   }
942
943#if 0
944   if (img_output_version >= 5) {
945       static const unsigned char codelengths[32] = {
946           4,  8,  8,  16, 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
947           0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0
948       };
949       fwrite(codelengths, 32, 1, pimg->fh);
950   }
951#endif
952   pimg->fRead = 0; /* writing to this file */
953   img_errno = IMG_NONE;
954
955   /* for version >= 3 we use label_buf to store the prefix for reuse */
956   pimg->label_buf[0] = '\0';
957   pimg->label_len = 0;
958
959#if IMG_API_VERSION == 0
960   pimg->date1 = pimg->date2 = 0;
961   pimg->olddate1 = pimg->olddate2 = 0;
962#else /* IMG_API_VERSION == 1 */
963   pimg->days1 = pimg->days2 = -1;
964   pimg->olddays1 = pimg->olddays2 = -1;
965#endif
966   pimg->style = pimg->oldstyle = img_STYLE_UNKNOWN;
967
968   pimg->l = pimg->r = pimg->u = pimg->d = -1.0;
969
970   pimg->n_legs = 0;
971   pimg->length = 0.0;
972   pimg->E = pimg->H = pimg->V = 0.0;
973
974   /* Don't check for write errors now - let img_close() report them... */
975   return pimg;
976}
977
978static void
979read_xyz_station_coords(img_point *pt, const char *line)
980{
981   char num[12];
982   memcpy(num, line + 6, 9);
983   num[9] = '\0';
984   pt->x = atof(num) / METRES_PER_FOOT;
985   memcpy(num, line + 15, 9);
986   pt->y = atof(num) / METRES_PER_FOOT;
987   memcpy(num, line + 24, 8);
988   num[8] = '\0';
989   pt->z = atof(num) / METRES_PER_FOOT;
990}
991
992static void
993read_xyz_shot_coords(img_point *pt, const char *line)
994{
995   char num[12];
996   memcpy(num, line + 40, 10);
997   num[10] = '\0';
998   pt->x = atof(num) / METRES_PER_FOOT;
999   memcpy(num, line + 50, 10);
1000   pt->y = atof(num) / METRES_PER_FOOT;
1001   memcpy(num, line + 60, 9);
1002   num[9] = '\0';
1003   pt->z = atof(num) / METRES_PER_FOOT;
1004}
1005
1006static void
1007subtract_xyz_shot_deltas(img_point *pt, const char *line)
1008{
1009   char num[12];
1010   memcpy(num, line + 15, 9);
1011   num[9] = '\0';
1012   pt->x -= atof(num) / METRES_PER_FOOT;
1013   memcpy(num, line + 24, 8);
1014   num[8] = '\0';
1015   pt->y -= atof(num) / METRES_PER_FOOT;
1016   memcpy(num, line + 32, 8);
1017   pt->z -= atof(num) / METRES_PER_FOOT;
1018}
1019
1020static int
1021read_coord(FILE *fh, img_point *pt)
1022{
1023   SVX_ASSERT(fh);
1024   SVX_ASSERT(pt);
1025   pt->x = get32(fh) / 100.0;
1026   pt->y = get32(fh) / 100.0;
1027   pt->z = get32(fh) / 100.0;
1028   if (ferror(fh) || feof(fh)) {
1029      img_errno = feof(fh) ? IMG_BADFORMAT : IMG_READERROR;
1030      return 0;
1031   }
1032   return 1;
1033}
1034
1035static int
1036skip_coord(FILE *fh)
1037{
1038    return (fseek(fh, 12, SEEK_CUR) == 0);
1039}
1040
1041static int
1042read_v3label(img *pimg)
1043{
1044   char *q;
1045   long len = GETC(pimg->fh);
1046   if (len == EOF) {
1047      img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1048      return img_BAD;
1049   }
1050   if (len == 0xfe) {
1051      len += get16(pimg->fh);
1052      if (feof(pimg->fh)) {
1053         img_errno = IMG_BADFORMAT;
1054         return img_BAD;
1055      }
1056      if (ferror(pimg->fh)) {
1057         img_errno = IMG_READERROR;
1058         return img_BAD;
1059      }
1060   } else if (len == 0xff) {
1061      len = get32(pimg->fh);
1062      if (ferror(pimg->fh)) {
1063         img_errno = IMG_READERROR;
1064         return img_BAD;
1065      }
1066      if (feof(pimg->fh) || len < 0xfe + 0xffff) {
1067         img_errno = IMG_BADFORMAT;
1068         return img_BAD;
1069      }
1070   }
1071
1072   if (!check_label_space(pimg, pimg->label_len + len + 1)) {
1073      img_errno = IMG_OUTOFMEMORY;
1074      return img_BAD;
1075   }
1076   q = pimg->label_buf + pimg->label_len;
1077   pimg->label_len += len;
1078   if (len && fread(q, len, 1, pimg->fh) != 1) {
1079      img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1080      return img_BAD;
1081   }
1082   q[len] = '\0';
1083   return 0;
1084}
1085
1086static int
1087read_v8label(img *pimg, int common_flag, size_t common_val)
1088{
1089   char *q;
1090   size_t del, add;
1091   if (common_flag) {
1092      if (common_val == 0) return 0;
1093      add = del = common_val;
1094   } else {
1095      int ch = GETC(pimg->fh);
1096      if (ch == EOF) {
1097         img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1098         return img_BAD;
1099      }
1100      if (ch != 0x00) {
1101         del = ch >> 4;
1102         add = ch & 0x0f;
1103      } else {
1104         ch = GETC(pimg->fh);
1105         if (ch == EOF) {
1106            img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1107            return img_BAD;
1108         }
1109         if (ch != 0xff) {
1110            del = ch;
1111         } else {
1112            del = get32(pimg->fh);
1113            if (ferror(pimg->fh)) {
1114               img_errno = IMG_READERROR;
1115               return img_BAD;
1116            }
1117         }
1118         ch = GETC(pimg->fh);
1119         if (ch == EOF) {
1120            img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1121            return img_BAD;
1122         }
1123         if (ch != 0xff) {
1124            add = ch;
1125         } else {
1126            add = get32(pimg->fh);
1127            if (ferror(pimg->fh)) {
1128               img_errno = IMG_READERROR;
1129               return img_BAD;
1130            }
1131         }
1132      }
1133
1134      if (add > del && !check_label_space(pimg, pimg->label_len + add - del + 1)) {
1135         img_errno = IMG_OUTOFMEMORY;
1136         return img_BAD;
1137      }
1138   }
1139   if (del > pimg->label_len) {
1140      img_errno = IMG_BADFORMAT;
1141      return img_BAD;
1142   }
1143   pimg->label_len -= del;
1144   q = pimg->label_buf + pimg->label_len;
1145   pimg->label_len += add;
1146   if (add && fread(q, add, 1, pimg->fh) != 1) {
1147      img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1148      return img_BAD;
1149   }
1150   q[add] = '\0';
1151   return 0;
1152}
1153
1154static int img_read_item_new(img *pimg, img_point *p);
1155static int img_read_item_v3to7(img *pimg, img_point *p);
1156static int img_read_item_ancient(img *pimg, img_point *p);
1157static int img_read_item_ascii_wrapper(img *pimg, img_point *p);
1158static int img_read_item_ascii(img *pimg, img_point *p);
1159
1160int
1161img_read_item(img *pimg, img_point *p)
1162{
1163   pimg->flags = 0;
1164
1165   if (pimg->version >= 8) {
1166      return img_read_item_new(pimg, p);
1167   } else if (pimg->version >= 3) {
1168      return img_read_item_v3to7(pimg, p);
1169   } else if (pimg->version >= 1) {
1170      return img_read_item_ancient(pimg, p);
1171   } else {
1172      return img_read_item_ascii_wrapper(pimg, p);
1173   }
1174}
1175
1176static int
1177img_read_item_new(img *pimg, img_point *p)
1178{
1179   int result;
1180   int opt;
1181   pimg->l = pimg->r = pimg->u = pimg->d = -1.0;
1182   if (pimg->pending >= 0x40) {
1183      if (pimg->pending == 256) {
1184         pimg->pending = 0;
1185         return img_XSECT_END;
1186      }
1187      *p = pimg->mv;
1188      pimg->flags = (int)(pimg->pending) & 0x3f;
1189      pimg->pending = 0;
1190      return img_LINE;
1191   }
1192   again3: /* label to goto if we get a prefix, date, or lrud */
1193   pimg->label = pimg->label_buf;
1194   opt = GETC(pimg->fh);
1195   if (opt == EOF) {
1196      img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1197      return img_BAD;
1198   }
1199   if (opt >> 6 == 0) {
1200      if (opt <= 4) {
1201         if (opt == 0 && pimg->style == 0)
1202            return img_STOP; /* end of data marker */
1203         /* STYLE */
1204         pimg->style = opt;
1205         goto again3;
1206      }
1207      if (opt >= 0x10) {
1208          switch (opt) {
1209              case 0x10: { /* No date info */
1210#if IMG_API_VERSION == 0
1211                  pimg->date1 = pimg->date2 = 0;
1212#else /* IMG_API_VERSION == 1 */
1213                  pimg->days1 = pimg->days2 = -1;
1214#endif
1215                  break;
1216              }
1217              case 0x11: { /* Single date */
1218                  int days1 = (int)getu16(pimg->fh);
1219#if IMG_API_VERSION == 0
1220                  pimg->date2 = pimg->date1 = (days1 - 25567) * 86400;
1221#else /* IMG_API_VERSION == 1 */
1222                  pimg->days2 = pimg->days1 = days1;
1223#endif
1224                  break;
1225              }
1226              case 0x12: { /* Date range (short) */
1227                  int days1 = (int)getu16(pimg->fh);
1228                  int days2 = days1 + GETC(pimg->fh) + 1;
1229#if IMG_API_VERSION == 0
1230                  pimg->date1 = (days1 - 25567) * 86400;
1231                  pimg->date2 = (days2 - 25567) * 86400;
1232#else /* IMG_API_VERSION == 1 */
1233                  pimg->days1 = days1;
1234                  pimg->days2 = days2;
1235#endif
1236                  break;
1237              }
1238              case 0x13: { /* Date range (long) */
1239                  int days1 = (int)getu16(pimg->fh);
1240                  int days2 = (int)getu16(pimg->fh);
1241#if IMG_API_VERSION == 0
1242                  pimg->date1 = (days1 - 25567) * 86400;
1243                  pimg->date2 = (days2 - 25567) * 86400;
1244#else /* IMG_API_VERSION == 1 */
1245                  pimg->days1 = days1;
1246                  pimg->days2 = days2;
1247#endif
1248                  break;
1249              }
1250              case 0x1f: /* Error info */
1251                  pimg->n_legs = get32(pimg->fh);
1252                  pimg->length = get32(pimg->fh) / 100.0;
1253                  pimg->E = get32(pimg->fh) / 100.0;
1254                  pimg->H = get32(pimg->fh) / 100.0;
1255                  pimg->V = get32(pimg->fh) / 100.0;
1256                  return img_ERROR_INFO;
1257              case 0x30: case 0x31: /* LRUD */
1258              case 0x32: case 0x33: /* Big LRUD! */
1259                  if (read_v8label(pimg, 0, 0) == img_BAD) return img_BAD;
1260                  pimg->flags = (int)opt & 0x01;
1261                  if (opt < 0x32) {
1262                      pimg->l = get16(pimg->fh) / 100.0;
1263                      pimg->r = get16(pimg->fh) / 100.0;
1264                      pimg->u = get16(pimg->fh) / 100.0;
1265                      pimg->d = get16(pimg->fh) / 100.0;
1266                  } else {
1267                      pimg->l = get32(pimg->fh) / 100.0;
1268                      pimg->r = get32(pimg->fh) / 100.0;
1269                      pimg->u = get32(pimg->fh) / 100.0;
1270                      pimg->d = get32(pimg->fh) / 100.0;
1271                  }
1272                  if (pimg->survey_len) {
1273                      size_t l = pimg->survey_len;
1274                      const char *s = pimg->label_buf;
1275                      if (strncmp(pimg->survey, s, l + 1) != 0) {
1276                          return img_XSECT_END;
1277                      }
1278                      pimg->label += l;
1279                      /* skip the dot if there */
1280                      if (*pimg->label) pimg->label++;
1281                  }
1282                  /* If this is the last cross-section in this passage, set
1283                   * pending so we return img_XSECT_END next time. */
1284                  if (pimg->flags & 0x01) {
1285                      pimg->pending = 256;
1286                      pimg->flags &= ~0x01;
1287                  }
1288                  return img_XSECT;
1289              default: /* 0x25 - 0x2f and 0x34 - 0x3f are currently unallocated. */
1290                  img_errno = IMG_BADFORMAT;
1291                  return img_BAD;
1292          }
1293          goto again3;
1294      }
1295      if (opt != 15) {
1296         /* 1-14 and 16-31 reserved */
1297         img_errno = IMG_BADFORMAT;
1298         return img_BAD;
1299      }
1300      result = img_MOVE;
1301   } else if (opt >= 0x80) {
1302      if (read_v8label(pimg, 0, 0) == img_BAD) return img_BAD;
1303
1304      result = img_LABEL;
1305
1306      if (pimg->survey_len) {
1307         size_t l = pimg->survey_len;
1308         const char *s = pimg->label_buf;
1309         if (strncmp(pimg->survey, s, l + 1) != 0) {
1310            if (!skip_coord(pimg->fh)) return img_BAD;
1311            pimg->pending = 0;
1312            goto again3;
1313         }
1314         pimg->label += l;
1315         /* skip the dot if there */
1316         if (*pimg->label) pimg->label++;
1317      }
1318
1319      pimg->flags = (int)opt & 0x7f;
1320   } else if ((opt >> 6) == 1) {
1321      if (read_v8label(pimg, opt & 0x20, 0) == img_BAD) return img_BAD;
1322
1323      result = img_LINE;
1324
1325      if (pimg->survey_len) {
1326         size_t l = pimg->survey_len;
1327         const char *s = pimg->label_buf;
1328         if (strncmp(pimg->survey, s, l) != 0 ||
1329             !(s[l] == '.' || s[l] == '\0')) {
1330            if (!read_coord(pimg->fh, &(pimg->mv))) return img_BAD;
1331            pimg->pending = 15;
1332            goto again3;
1333         }
1334         pimg->label += l;
1335         /* skip the dot if there */
1336         if (*pimg->label) pimg->label++;
1337      }
1338
1339      if (pimg->pending) {
1340         *p = pimg->mv;
1341         if (!read_coord(pimg->fh, &(pimg->mv))) return img_BAD;
1342         pimg->pending = opt;
1343         return img_MOVE;
1344      }
1345      pimg->flags = (int)opt & 0x1f;
1346   } else {
1347      img_errno = IMG_BADFORMAT;
1348      return img_BAD;
1349   }
1350   if (!read_coord(pimg->fh, p)) return img_BAD;
1351   pimg->pending = 0;
1352   return result;
1353}
1354
1355static int
1356img_read_item_v3to7(img *pimg, img_point *p)
1357{
1358   int result;
1359   int opt;
1360   pimg->l = pimg->r = pimg->u = pimg->d = -1.0;
1361   if (pimg->pending == 256) {
1362      pimg->pending = 0;
1363      return img_XSECT_END;
1364   }
1365   if (pimg->pending >= 0x80) {
1366      *p = pimg->mv;
1367      pimg->flags = (int)(pimg->pending) & 0x3f;
1368      pimg->pending = 0;
1369      return img_LINE;
1370   }
1371   again3: /* label to goto if we get a prefix, date, or lrud */
1372   pimg->label = pimg->label_buf;
1373   opt = GETC(pimg->fh);
1374   if (opt == EOF) {
1375      img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1376      return img_BAD;
1377   }
1378   switch (opt >> 6) {
1379    case 0:
1380      if (opt == 0) {
1381         if (!pimg->label_len) return img_STOP; /* end of data marker */
1382         pimg->label_len = 0;
1383         goto again3;
1384      }
1385      if (opt < 15) {
1386         /* 1-14 mean trim that many levels from current prefix */
1387         int c;
1388         if (pimg->label_len <= 17) {
1389            /* zero prefix using "0" */
1390            img_errno = IMG_BADFORMAT;
1391            return img_BAD;
1392         }
1393         /* extra - 1 because label_len points to one past the end */
1394         c = pimg->label_len - 17 - 1;
1395         while (pimg->label_buf[c] != '.' || --opt > 0) {
1396            if (--c < 0) {
1397               /* zero prefix using "0" */
1398               img_errno = IMG_BADFORMAT;
1399               return img_BAD;
1400            }
1401         }
1402         c++;
1403         pimg->label_len = c;
1404         goto again3;
1405      }
1406      if (opt == 15) {
1407         result = img_MOVE;
1408         break;
1409      }
1410      if (opt >= 0x20) {
1411          switch (opt) {
1412              case 0x20: /* Single date */
1413                  if (pimg->version < 7) {
1414                      int date1 = get32(pimg->fh);
1415#if IMG_API_VERSION == 0
1416                      pimg->date2 = pimg->date1 = date1;
1417#else /* IMG_API_VERSION == 1 */
1418                      if (date1 != 0) {
1419                          pimg->days2 = pimg->days1 = (date1 / 86400) + 25567;
1420                      } else {
1421                          pimg->days2 = pimg->days1 = -1;
1422                      }
1423#endif
1424                  } else {
1425                      int days1 = (int)getu16(pimg->fh);
1426#if IMG_API_VERSION == 0
1427                      pimg->date2 = pimg->date1 = (days1 - 25567) * 86400;
1428#else /* IMG_API_VERSION == 1 */
1429                      pimg->days2 = pimg->days1 = days1;
1430#endif
1431                  }
1432                  break;
1433              case 0x21: /* Date range (short for v7+) */
1434                  if (pimg->version < 7) {
1435                      INT32_T date1 = get32(pimg->fh);
1436                      INT32_T date2 = get32(pimg->fh);
1437#if IMG_API_VERSION == 0
1438                      pimg->date1 = date1;
1439                      pimg->date2 = date2;
1440#else /* IMG_API_VERSION == 1 */
1441                      pimg->days1 = (date1 / 86400) + 25567;
1442                      pimg->days2 = (date2 / 86400) + 25567;
1443#endif
1444                  } else {
1445                      int days1 = (int)getu16(pimg->fh);
1446                      int days2 = days1 + GETC(pimg->fh) + 1;
1447#if IMG_API_VERSION == 0
1448                      pimg->date1 = (days1 - 25567) * 86400;
1449                      pimg->date2 = (days2 - 25567) * 86400;
1450#else /* IMG_API_VERSION == 1 */
1451                      pimg->days1 = days1;
1452                      pimg->days2 = days2;
1453#endif
1454                  }
1455                  break;
1456              case 0x22: /* Error info */
1457                  pimg->n_legs = get32(pimg->fh);
1458                  pimg->length = get32(pimg->fh) / 100.0;
1459                  pimg->E = get32(pimg->fh) / 100.0;
1460                  pimg->H = get32(pimg->fh) / 100.0;
1461                  pimg->V = get32(pimg->fh) / 100.0;
1462                  if (feof(pimg->fh)) {
1463                      img_errno = IMG_BADFORMAT;
1464                      return img_BAD;
1465                  }
1466                  if (ferror(pimg->fh)) {
1467                      img_errno = IMG_READERROR;
1468                      return img_BAD;
1469                  }
1470                  return img_ERROR_INFO;
1471              case 0x23: { /* v7+: Date range (long) */
1472                  if (pimg->version < 7) {
1473                      img_errno = IMG_BADFORMAT;
1474                      return img_BAD;
1475                  }
1476                  int days1 = (int)getu16(pimg->fh);
1477                  int days2 = (int)getu16(pimg->fh);
1478                  if (feof(pimg->fh)) {
1479                      img_errno = IMG_BADFORMAT;
1480                      return img_BAD;
1481                  }
1482                  if (ferror(pimg->fh)) {
1483                      img_errno = IMG_READERROR;
1484                      return img_BAD;
1485                  }
1486#if IMG_API_VERSION == 0
1487                  pimg->date1 = (days1 - 25567) * 86400;
1488                  pimg->date2 = (days2 - 25567) * 86400;
1489#else /* IMG_API_VERSION == 1 */
1490                  pimg->days1 = days1;
1491                  pimg->days2 = days2;
1492#endif
1493                  break;
1494              }
1495              case 0x24: { /* v7+: No date info */
1496#if IMG_API_VERSION == 0
1497                  pimg->date1 = pimg->date2 = 0;
1498#else /* IMG_API_VERSION == 1 */
1499                  pimg->days1 = pimg->days2 = -1;
1500#endif
1501                  break;
1502              }
1503              case 0x30: case 0x31: /* LRUD */
1504              case 0x32: case 0x33: /* Big LRUD! */
1505                  if (read_v3label(pimg) == img_BAD) return img_BAD;
1506                  pimg->flags = (int)opt & 0x01;
1507                  if (opt < 0x32) {
1508                      pimg->l = get16(pimg->fh) / 100.0;
1509                      pimg->r = get16(pimg->fh) / 100.0;
1510                      pimg->u = get16(pimg->fh) / 100.0;
1511                      pimg->d = get16(pimg->fh) / 100.0;
1512                  } else {
1513                      pimg->l = get32(pimg->fh) / 100.0;
1514                      pimg->r = get32(pimg->fh) / 100.0;
1515                      pimg->u = get32(pimg->fh) / 100.0;
1516                      pimg->d = get32(pimg->fh) / 100.0;
1517                  }
1518                  if (feof(pimg->fh)) {
1519                      img_errno = IMG_BADFORMAT;
1520                      return img_BAD;
1521                  }
1522                  if (ferror(pimg->fh)) {
1523                      img_errno = IMG_READERROR;
1524                      return img_BAD;
1525                  }
1526                  if (pimg->survey_len) {
1527                      size_t l = pimg->survey_len;
1528                      const char *s = pimg->label_buf;
1529                      if (strncmp(pimg->survey, s, l + 1) != 0) {
1530                          return img_XSECT_END;
1531                      }
1532                      pimg->label += l;
1533                      /* skip the dot if there */
1534                      if (*pimg->label) pimg->label++;
1535                  }
1536                  /* If this is the last cross-section in this passage, set
1537                   * pending so we return img_XSECT_END next time. */
1538                  if (pimg->flags & 0x01) {
1539                      pimg->pending = 256;
1540                      pimg->flags &= ~0x01;
1541                  }
1542                  return img_XSECT;
1543              default: /* 0x25 - 0x2f and 0x34 - 0x3f are currently unallocated. */
1544                  img_errno = IMG_BADFORMAT;
1545                  return img_BAD;
1546          }
1547          if (feof(pimg->fh)) {
1548              img_errno = IMG_BADFORMAT;
1549              return img_BAD;
1550          }
1551          if (ferror(pimg->fh)) {
1552              img_errno = IMG_READERROR;
1553              return img_BAD;
1554          }
1555          goto again3;
1556      }
1557      /* 16-31 mean remove (n - 15) characters from the prefix */
1558      /* zero prefix using 0 */
1559      if (pimg->label_len <= (size_t)(opt - 15)) {
1560         img_errno = IMG_BADFORMAT;
1561         return img_BAD;
1562      }
1563      pimg->label_len -= (opt - 15);
1564      goto again3;
1565    case 1:
1566      if (read_v3label(pimg) == img_BAD) return img_BAD;
1567
1568      result = img_LABEL;
1569
1570      if (pimg->survey_len) {
1571         size_t l = pimg->survey_len;
1572         const char *s = pimg->label_buf;
1573         if (strncmp(pimg->survey, s, l + 1) != 0) {
1574            if (!skip_coord(pimg->fh)) return img_BAD;
1575            pimg->pending = 0;
1576            goto again3;
1577         }
1578         pimg->label += l;
1579         /* skip the dot if there */
1580         if (*pimg->label) pimg->label++;
1581      }
1582
1583      pimg->flags = (int)opt & 0x3f;
1584      break;
1585    case 2:
1586      if (read_v3label(pimg) == img_BAD) return img_BAD;
1587
1588      result = img_LINE;
1589
1590      if (pimg->survey_len) {
1591         size_t l = pimg->survey_len;
1592         const char *s = pimg->label_buf;
1593         if (strncmp(pimg->survey, s, l) != 0 ||
1594             !(s[l] == '.' || s[l] == '\0')) {
1595            if (!read_coord(pimg->fh, &(pimg->mv))) return img_BAD;
1596            pimg->pending = 15;
1597            goto again3;
1598         }
1599         pimg->label += l;
1600         /* skip the dot if there */
1601         if (*pimg->label) pimg->label++;
1602      }
1603
1604      if (pimg->pending) {
1605         *p = pimg->mv;
1606         if (!read_coord(pimg->fh, &(pimg->mv))) return img_BAD;
1607         pimg->pending = opt;
1608         return img_MOVE;
1609      }
1610      pimg->flags = (int)opt & 0x3f;
1611      break;
1612    default:
1613      img_errno = IMG_BADFORMAT;
1614      return img_BAD;
1615   }
1616   if (!read_coord(pimg->fh, p)) return img_BAD;
1617   pimg->pending = 0;
1618   return result;
1619}
1620
1621static int
1622img_read_item_ancient(img *pimg, img_point *p)
1623{
1624   int result;
1625   static long opt_lookahead = 0;
1626   static img_point pt = { 0.0, 0.0, 0.0 };
1627   long opt;
1628
1629   pimg->label = pimg->label_buf;
1630
1631   again: /* label to goto if we get a cross */
1632   pimg->label[0] = '\0';
1633
1634   if (pimg->version == 1) {
1635      if (opt_lookahead) {
1636         opt = opt_lookahead;
1637         opt_lookahead = 0;
1638      } else {
1639         opt = get32(pimg->fh);
1640      }
1641   } else {
1642      opt = GETC(pimg->fh);
1643   }
1644
1645   if (feof(pimg->fh)) {
1646      img_errno = IMG_BADFORMAT;
1647      return img_BAD;
1648   }
1649   if (ferror(pimg->fh)) {
1650      img_errno = IMG_READERROR;
1651      return img_BAD;
1652   }
1653
1654   switch (opt) {
1655    case -1: case 0:
1656      return img_STOP; /* end of data marker */
1657    case 1:
1658      /* skip coordinates */
1659      if (!skip_coord(pimg->fh)) {
1660         img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1661         return img_BAD;
1662      }
1663      goto again;
1664    case 2: case 3: {
1665      char *q;
1666      int ch;
1667      result = img_LABEL;
1668      ch = GETC(pimg->fh);
1669      if (ch == EOF) {
1670         img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1671         return img_BAD;
1672      }
1673      if (ch != '\\') ungetc(ch, pimg->fh);
1674      fgets(pimg->label_buf, 257, pimg->fh);
1675      if (feof(pimg->fh)) {
1676         img_errno = IMG_BADFORMAT;
1677         return img_BAD;
1678      }
1679      if (ferror(pimg->fh)) {
1680         img_errno = IMG_READERROR;
1681         return img_BAD;
1682      }
1683      q = pimg->label_buf + strlen(pimg->label_buf) - 1;
1684      if (*q != '\n') {
1685         img_errno = IMG_BADFORMAT;
1686         return img_BAD;
1687      }
1688      /* Ignore empty labels in some .3d files (caused by a bug) */
1689      if (q == pimg->label_buf) goto again;
1690      *q = '\0';
1691      pimg->flags = img_SFLAG_UNDERGROUND; /* no flags given... */
1692      if (opt == 2) goto done;
1693      break;
1694    }
1695    case 6: case 7: {
1696      long len;
1697      result = img_LABEL;
1698
1699      if (opt == 7)
1700         pimg->flags = GETC(pimg->fh);
1701      else
1702         pimg->flags = img_SFLAG_UNDERGROUND; /* no flags given... */
1703
1704      len = get32(pimg->fh);
1705
1706      if (feof(pimg->fh)) {
1707         img_errno = IMG_BADFORMAT;
1708         return img_BAD;
1709      }
1710      if (ferror(pimg->fh)) {
1711         img_errno = IMG_READERROR;
1712         return img_BAD;
1713      }
1714
1715      /* Ignore empty labels in some .3d files (caused by a bug) */
1716      if (len == 0) goto again;
1717      if (!check_label_space(pimg, len + 1)) {
1718         img_errno = IMG_OUTOFMEMORY;
1719         return img_BAD;
1720      }
1721      if (fread(pimg->label_buf, len, 1, pimg->fh) != 1) {
1722         img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1723         return img_BAD;
1724      }
1725      pimg->label_buf[len] = '\0';
1726      break;
1727    }
1728    case 4:
1729      result = img_MOVE;
1730      break;
1731    case 5:
1732      result = img_LINE;
1733      break;
1734    default:
1735      switch ((int)opt & 0xc0) {
1736       case 0x80:
1737         pimg->flags = (int)opt & 0x3f;
1738         result = img_LINE;
1739         break;
1740       case 0x40: {
1741         char *q;
1742         pimg->flags = (int)opt & 0x3f;
1743         result = img_LABEL;
1744         if (!fgets(pimg->label_buf, 257, pimg->fh)) {
1745            img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1746            return img_BAD;
1747         }
1748         q = pimg->label_buf + strlen(pimg->label_buf) - 1;
1749         /* Ignore empty-labels in some .3d files (caused by a bug) */
1750         if (q == pimg->label_buf) goto again;
1751         if (*q != '\n') {
1752            img_errno = IMG_BADFORMAT;
1753            return img_BAD;
1754         }
1755         *q = '\0';
1756         break;
1757       }
1758       case 0xc0:
1759         /* use this for an extra leg or station flag if we need it */
1760       default:
1761         img_errno = IMG_BADFORMAT;
1762         return img_BAD;
1763      }
1764      break;
1765   }
1766
1767   if (!read_coord(pimg->fh, &pt)) return img_BAD;
1768
1769   if (result == img_LABEL && pimg->survey_len) {
1770      if (strncmp(pimg->label_buf, pimg->survey, pimg->survey_len + 1) != 0)
1771         goto again;
1772      pimg->label += pimg->survey_len + 1;
1773   }
1774
1775   done:
1776   *p = pt;
1777
1778   if (result == img_MOVE && pimg->version == 1) {
1779      /* peek at next code and see if it's an old-style label */
1780      opt_lookahead = get32(pimg->fh);
1781
1782      if (feof(pimg->fh)) {
1783         img_errno = IMG_BADFORMAT;
1784         return img_BAD;
1785      }
1786      if (ferror(pimg->fh)) {
1787         img_errno = IMG_READERROR;
1788         return img_BAD;
1789      }
1790
1791      if (opt_lookahead == 2) return img_read_item(pimg, p);
1792   }
1793
1794   return result;
1795}
1796
1797static int
1798img_read_item_ascii_wrapper(img *pimg, img_point *p)
1799{
1800   /* We need to set the default locale for fscanf() to work on
1801    * numbers with "." as decimal point. */
1802   int result;
1803   char * current_locale = my_strdup(setlocale(LC_NUMERIC, NULL));
1804   setlocale(LC_NUMERIC, "C");
1805   result = img_read_item_ascii(pimg, p);
1806   setlocale(LC_NUMERIC, current_locale);
1807   free(current_locale);
1808   return result;
1809}
1810
1811/* Handle all ASCII formats. */
1812static int
1813img_read_item_ascii(img *pimg, img_point *p)
1814{
1815   int result;
1816   pimg->label = pimg->label_buf;
1817   if (pimg->version == 0) {
1818      ascii_again:
1819      pimg->label[0] = '\0';
1820      if (feof(pimg->fh)) return img_STOP;
1821      if (pimg->pending) {
1822         pimg->pending = 0;
1823         result = img_LINE;
1824      } else {
1825         char cmd[7];
1826         /* Stop if nothing found */
1827         if (fscanf(pimg->fh, "%6s", cmd) < 1) return img_STOP;
1828         if (strcmp(cmd, "move") == 0)
1829            result = img_MOVE;
1830         else if (strcmp(cmd, "draw") == 0)
1831            result = img_LINE;
1832         else if (strcmp(cmd, "line") == 0) {
1833            /* set flag to indicate to process second triplet as LINE */
1834            pimg->pending = 1;
1835            result = img_MOVE;
1836         } else if (strcmp(cmd, "cross") == 0) {
1837            if (fscanf(pimg->fh, "%lf%lf%lf", &p->x, &p->y, &p->z) < 3) {
1838               img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1839               return img_BAD;
1840            }
1841            goto ascii_again;
1842         } else if (strcmp(cmd, "name") == 0) {
1843            size_t off = 0;
1844            int ch = GETC(pimg->fh);
1845            if (ch == ' ') ch = GETC(pimg->fh);
1846            while (ch != ' ') {
1847               if (ch == '\n' || ch == EOF) {
1848                  img_errno = ferror(pimg->fh) ? IMG_READERROR : IMG_BADFORMAT;
1849                  return img_BAD;
1850               }
1851               if (off == pimg->buf_len) {
1852                  if (!check_label_space(pimg, pimg->buf_len * 2)) {
1853                     img_errno = IMG_OUTOFMEMORY;
1854                     return img_BAD;
1855                  }
1856               }
1857               pimg->label_buf[off++] = ch;
1858               ch = GETC(pimg->fh);
1859            }
1860            pimg->label_buf[off] = '\0';
1861
1862            pimg->label = pimg->label_buf;
1863            if (pimg->label[0] == '\\') pimg->label++;
1864
1865            pimg->flags = img_SFLAG_UNDERGROUND; /* default flags */
1866
1867            result = img_LABEL;
1868         } else {
1869            img_errno = IMG_BADFORMAT;
1870            return img_BAD; /* unknown keyword */
1871         }
1872      }
1873
1874      if (fscanf(pimg->fh, "%lf%lf%lf", &p->x, &p->y, &p->z) < 3) {
1875         img_errno = ferror(pimg->fh) ? IMG_READERROR : IMG_BADFORMAT;
1876         return img_BAD;
1877      }
1878
1879      if (result == img_LABEL && pimg->survey_len) {
1880         if (strncmp(pimg->label, pimg->survey, pimg->survey_len + 1) != 0)
1881            goto ascii_again;
1882         pimg->label += pimg->survey_len + 1;
1883      }
1884
1885      return result;
1886   } else if (pimg->version == VERSION_SURVEX_POS) {
1887      /* Survex .pos file */
1888      int ch;
1889      size_t off;
1890      pimg->flags = img_SFLAG_UNDERGROUND; /* default flags */
1891      againpos:
1892      off = 0;
1893      while (fscanf(pimg->fh, "(%lf,%lf,%lf )", &p->x, &p->y, &p->z) != 3) {
1894         if (ferror(pimg->fh)) {
1895            img_errno = IMG_READERROR;
1896            return img_BAD;
1897         }
1898         if (feof(pimg->fh)) return img_STOP;
1899         if (pimg->pending) {
1900            img_errno = IMG_BADFORMAT;
1901            return img_BAD;
1902         }
1903         pimg->pending = 1;
1904         /* ignore rest of line */
1905         do {
1906            ch = GETC(pimg->fh);
1907         } while (ch != '\n' && ch != '\r' && ch != EOF);
1908      }
1909
1910      pimg->label_buf[0] = '\0';
1911      do {
1912          ch = GETC(pimg->fh);
1913      } while (ch == ' ' || ch == '\t');
1914      if (ch == '\n' || ch == EOF) {
1915          /* If there's no label, set img_SFLAG_ANON. */
1916          pimg->flags |= img_SFLAG_ANON;
1917          return img_LABEL;
1918      }
1919      pimg->label_buf[0] = ch;
1920      off = 1;
1921      while (!feof(pimg->fh)) {
1922         if (!fgets(pimg->label_buf + off, pimg->buf_len - off, pimg->fh)) {
1923            img_errno = IMG_READERROR;
1924            return img_BAD;
1925         }
1926
1927         off += strlen(pimg->label_buf + off);
1928         if (off && pimg->label_buf[off - 1] == '\n') {
1929            pimg->label_buf[off - 1] = '\0';
1930            break;
1931         }
1932         if (!check_label_space(pimg, pimg->buf_len * 2)) {
1933            img_errno = IMG_OUTOFMEMORY;
1934            return img_BAD;
1935         }
1936      }
1937
1938      pimg->label = pimg->label_buf;
1939
1940      if (pimg->label[0] == '\\') pimg->label++;
1941
1942      if (pimg->survey_len) {
1943         size_t l = pimg->survey_len + 1;
1944         if (strncmp(pimg->survey, pimg->label, l) != 0) goto againpos;
1945         pimg->label += l;
1946      }
1947
1948      return img_LABEL;
1949   } else if (pimg->version == VERSION_COMPASS_PLT) {
1950      /* Compass .plt file */
1951      if (pimg->pending > 0) {
1952         /* -1 signals we've entered the first survey we want to
1953          * read, and need to fudge lots if the first action is 'D'...
1954          */
1955         /* pending MOVE or LINE */
1956         int r = pimg->pending - 4;
1957         pimg->pending = 0;
1958         pimg->flags = 0;
1959         pimg->label[pimg->label_len] = '\0';
1960         return r;
1961      }
1962
1963      while (1) {
1964         char *line;
1965         char *q;
1966         size_t len = 0;
1967         int ch = GETC(pimg->fh);
1968
1969         switch (ch) {
1970            case '\x1a': case EOF: /* Don't insist on ^Z at end of file */
1971               return img_STOP;
1972            case 'X': case 'F': case 'S':
1973               /* bounding boX (marks end of survey), Feature survey, or
1974                * new Section - skip to next survey */
1975               if (pimg->survey) return img_STOP;
1976skip_to_N:
1977               while (1) {
1978                  do {
1979                     ch = GETC(pimg->fh);
1980                  } while (ch != '\n' && ch != '\r' && ch != EOF);
1981                  while (ch == '\n' || ch == '\r') ch = GETC(pimg->fh);
1982                  if (ch == 'N') break;
1983                  if (ch == '\x1a' || ch == EOF) return img_STOP;
1984               }
1985               /* FALLTHRU */
1986            case 'N':
1987               line = getline_alloc(pimg->fh);
1988               if (!line) {
1989                  img_errno = IMG_OUTOFMEMORY;
1990                  return img_BAD;
1991               }
1992               while (line[len] > 32) ++len;
1993               if (pimg->label_len == 0) pimg->pending = -1;
1994               if (!check_label_space(pimg, len + 1)) {
1995                  osfree(line);
1996                  img_errno = IMG_OUTOFMEMORY;
1997                  return img_BAD;
1998               }
1999               pimg->label_len = len;
2000               pimg->label = pimg->label_buf;
2001               memcpy(pimg->label, line, len);
2002               pimg->label[len] = '\0';
2003               osfree(line);
2004               break;
2005            case 'M': case 'D': {
2006               /* Move or Draw */
2007               long fpos = -1;
2008               if (pimg->survey && pimg->label_len == 0) {
2009                  /* We're only holding onto this line in case the first line
2010                   * of the 'N' is a 'D', so skip it for now...
2011                   */
2012                  goto skip_to_N;
2013               }
2014               if (ch == 'D' && pimg->pending == -1) {
2015                  if (pimg->survey) {
2016                     fpos = ftell(pimg->fh) - 1;
2017                     fseek(pimg->fh, pimg->start, SEEK_SET);
2018                     ch = GETC(pimg->fh);
2019                     pimg->pending = 0;
2020                  } else {
2021                     /* If a file actually has a 'D' before any 'M', then
2022                      * pretend the 'D' is an 'M' - one of the examples
2023                      * in the docs was like this! */
2024                     ch = 'M';
2025                  }
2026               }
2027               line = getline_alloc(pimg->fh);
2028               if (!line) {
2029                  img_errno = IMG_OUTOFMEMORY;
2030                  return img_BAD;
2031               }
2032               /* Compass stores coordinates as North, East, Up = (y,x,z)! */
2033               if (sscanf(line, "%lf%lf%lf", &p->y, &p->x, &p->z) != 3) {
2034                  osfree(line);
2035                  if (ferror(pimg->fh)) {
2036                     img_errno = IMG_READERROR;
2037                  } else {
2038                     img_errno = IMG_BADFORMAT;
2039                  }
2040                  return img_BAD;
2041               }
2042               p->x *= METRES_PER_FOOT;
2043               p->y *= METRES_PER_FOOT;
2044               p->z *= METRES_PER_FOOT;
2045               q = strchr(line, 'S');
2046               if (!q) {
2047                  osfree(line);
2048                  img_errno = IMG_BADFORMAT;
2049                  return img_BAD;
2050               }
2051               ++q;
2052               len = 0;
2053               while (q[len] > ' ') ++len;
2054               q[len] = '\0';
2055               len += 2; /* ' ' and '\0' */
2056               if (!check_label_space(pimg, pimg->label_len + len)) {
2057                  img_errno = IMG_OUTOFMEMORY;
2058                  return img_BAD;
2059               }
2060               pimg->label = pimg->label_buf;
2061               if (pimg->label_len) {
2062                   pimg->label[pimg->label_len] = ' ';
2063                   memcpy(pimg->label + pimg->label_len + 1, q, len - 1);
2064               } else {
2065                   memcpy(pimg->label, q, len - 1);
2066               }
2067               q += len - 1;
2068               /* Now read LRUD.  Technically, this is optional but virtually
2069                * all PLT files have it (with dummy negative values if no LRUD
2070                * was measured) and some versions of Compass can't read PLT
2071                * files without it!
2072                */
2073               while (*q && *q <= ' ') q++;
2074               if (*q == 'P') {
2075                   if (sscanf(q + 1, "%lf%lf%lf%lf",
2076                              &pimg->l, &pimg->r, &pimg->u, &pimg->d) != 4) {
2077                       osfree(line);
2078                       if (ferror(pimg->fh)) {
2079                           img_errno = IMG_READERROR;
2080                       } else {
2081                           img_errno = IMG_BADFORMAT;
2082                       }
2083                       return img_BAD;
2084                   }
2085                   pimg->l *= METRES_PER_FOOT;
2086                   pimg->r *= METRES_PER_FOOT;
2087                   pimg->u *= METRES_PER_FOOT;
2088                   pimg->d *= METRES_PER_FOOT;
2089               } else {
2090                   pimg->l = pimg->r = pimg->u = pimg->d = -1;
2091               }
2092               osfree(line);
2093               pimg->flags = img_SFLAG_UNDERGROUND; /* default flags */
2094               if (fpos != -1) {
2095                  fseek(pimg->fh, fpos, SEEK_SET);
2096               } else {
2097                  pimg->pending = (ch == 'M' ? img_MOVE : img_LINE) + 4;
2098               }
2099               return img_LABEL;
2100            }
2101            default:
2102               img_errno = IMG_BADFORMAT;
2103               return img_BAD;
2104         }
2105      }
2106   } else {
2107      /* CMAP .xyz file */
2108      char *line = NULL;
2109      char *q;
2110      size_t len;
2111
2112      if (pimg->pending) {
2113         /* pending MOVE or LINE or LABEL or STOP */
2114         int r = pimg->pending - 4;
2115         /* Set label to empty - don't use "" as we adjust label relative
2116          * to label_buf when label_buf is reallocated. */
2117         pimg->label = pimg->label_buf + strlen(pimg->label_buf);
2118         pimg->flags = 0;
2119         if (r == img_LABEL) {
2120            /* nasty magic */
2121            read_xyz_shot_coords(p, pimg->label_buf + 16);
2122            subtract_xyz_shot_deltas(p, pimg->label_buf + 16);
2123            pimg->pending = img_STOP + 4;
2124            return img_MOVE;
2125         }
2126
2127         pimg->pending = 0;
2128
2129         if (r == img_STOP) {
2130            /* nasty magic */
2131            read_xyz_shot_coords(p, pimg->label_buf + 16);
2132            return img_LINE;
2133         }
2134
2135         return r;
2136      }
2137
2138      pimg->label = pimg->label_buf;
2139      do {
2140         osfree(line);
2141         if (feof(pimg->fh)) return img_STOP;
2142         line = getline_alloc(pimg->fh);
2143         if (!line) {
2144            img_errno = IMG_OUTOFMEMORY;
2145            return img_BAD;
2146         }
2147      } while (line[0] == ' ' || line[0] == '\0');
2148      if (line[0] == '\x1a') return img_STOP;
2149
2150      len = strlen(line);
2151      if (pimg->version == VERSION_CMAP_STATION) {
2152         /* station variant */
2153         if (len < 37) {
2154            osfree(line);
2155            img_errno = IMG_BADFORMAT;
2156            return img_BAD;
2157         }
2158         memcpy(pimg->label, line, 6);
2159         q = (char *)memchr(pimg->label, ' ', 6);
2160         if (!q) q = pimg->label + 6;
2161         *q = '\0';
2162
2163         read_xyz_station_coords(p, line);
2164
2165         /* FIXME: look at prev for lines (line + 32, 5) */
2166         /* FIXME: duplicate stations... */
2167         return img_LABEL;
2168      } else {
2169         /* Shot variant (VERSION_CMAP_SHOT) */
2170         char old[8], new_[8];
2171         if (len < 61) {
2172            osfree(line);
2173            img_errno = IMG_BADFORMAT;
2174            return img_BAD;
2175         }
2176
2177         memcpy(old, line, 7);
2178         q = (char *)memchr(old, ' ', 7);
2179         if (!q) q = old + 7;
2180         *q = '\0';
2181
2182         memcpy(new_, line + 7, 7);
2183         q = (char *)memchr(new_, ' ', 7);
2184         if (!q) q = new_ + 7;
2185         *q = '\0';
2186
2187         pimg->flags = img_SFLAG_UNDERGROUND;
2188
2189         if (strcmp(old, new_) == 0) {
2190            pimg->pending = img_MOVE + 4;
2191            read_xyz_shot_coords(p, line);
2192            strcpy(pimg->label, new_);
2193            osfree(line);
2194            return img_LABEL;
2195         }
2196
2197         if (strcmp(old, pimg->label) == 0) {
2198            pimg->pending = img_LINE + 4;
2199            read_xyz_shot_coords(p, line);
2200            strcpy(pimg->label, new_);
2201            osfree(line);
2202            return img_LABEL;
2203         }
2204
2205         pimg->pending = img_LABEL + 4;
2206         read_xyz_shot_coords(p, line);
2207         strcpy(pimg->label, new_);
2208         memcpy(pimg->label + 16, line, 70);
2209
2210         osfree(line);
2211         return img_LABEL;
2212      }
2213   }
2214}
2215
2216static void
2217write_coord(FILE *fh, double x, double y, double z)
2218{
2219   SVX_ASSERT(fh);
2220   /* Output in cm */
2221   static INT32_T X_, Y_, Z_;
2222   INT32_T X = my_lround(x * 100.0);
2223   INT32_T Y = my_lround(y * 100.0);
2224   INT32_T Z = my_lround(z * 100.0);
2225
2226   X_ -= X;
2227   Y_ -= Y;
2228   Z_ -= Z;
2229   put32(X, fh);
2230   put32(Y, fh);
2231   put32(Z, fh);
2232   X_ = X; Y_ = Y; Z_ = Z;
2233}
2234
2235static int
2236write_v3label(img *pimg, int opt, const char *s)
2237{
2238   size_t len, n, dot;
2239
2240   /* find length of common prefix */
2241   dot = 0;
2242   for (len = 0; s[len] == pimg->label_buf[len] && s[len] != '\0'; len++) {
2243      if (s[len] == '.') dot = len + 1;
2244   }
2245
2246   SVX_ASSERT(len <= pimg->label_len);
2247   n = pimg->label_len - len;
2248   if (len == 0) {
2249      if (pimg->label_len) PUTC(0, pimg->fh);
2250   } else if (n <= 16) {
2251      if (n) PUTC(n + 15, pimg->fh);
2252   } else if (dot == 0) {
2253      if (pimg->label_len) PUTC(0, pimg->fh);
2254      len = 0;
2255   } else {
2256      const char *p = pimg->label_buf + dot;
2257      n = 1;
2258      for (len = pimg->label_len - dot - 17; len; len--) {
2259         if (*p++ == '.') n++;
2260      }
2261      if (n <= 14) {
2262         PUTC(n, pimg->fh);
2263         len = dot;
2264      } else {
2265         if (pimg->label_len) PUTC(0, pimg->fh);
2266         len = 0;
2267      }
2268   }
2269
2270   n = strlen(s + len);
2271   PUTC(opt, pimg->fh);
2272   if (n < 0xfe) {
2273      PUTC(n, pimg->fh);
2274   } else if (n < 0xffff + 0xfe) {
2275      PUTC(0xfe, pimg->fh);
2276      put16((short)(n - 0xfe), pimg->fh);
2277   } else {
2278      PUTC(0xff, pimg->fh);
2279      put32(n, pimg->fh);
2280   }
2281   fwrite(s + len, n, 1, pimg->fh);
2282
2283   n += len;
2284   pimg->label_len = n;
2285   if (!check_label_space(pimg, n + 1))
2286      return 0; /* FIXME: distinguish out of memory... */
2287   memcpy(pimg->label_buf + len, s + len, n - len + 1);
2288
2289   return !ferror(pimg->fh);
2290}
2291
2292static int
2293write_v8label(img *pimg, int opt, int common_flag, size_t common_val,
2294              const char *s)
2295{
2296   size_t len, del, add;
2297
2298   /* find length of common prefix */
2299   for (len = 0; s[len] == pimg->label_buf[len] && s[len] != '\0'; len++) {
2300   }
2301
2302   SVX_ASSERT(len <= pimg->label_len);
2303   del = pimg->label_len - len;
2304   add = strlen(s + len);
2305
2306   if (add == common_val && del == common_val) {
2307      PUTC(opt | common_flag, pimg->fh);
2308   } else {
2309      PUTC(opt, pimg->fh);
2310      if (del <= 15 && add <= 15 && (del || add)) {
2311         PUTC((del << 4) | add, pimg->fh);
2312      } else {
2313         PUTC(0x00, pimg->fh);
2314         if (del < 0xff) {
2315            PUTC(del, pimg->fh);
2316         } else {
2317            PUTC(0xff, pimg->fh);
2318            put32(del, pimg->fh);
2319         }
2320         if (add < 0xff) {
2321            PUTC(add, pimg->fh);
2322         } else {
2323            PUTC(0xff, pimg->fh);
2324            put32(add, pimg->fh);
2325         }
2326      }
2327   }
2328
2329   if (add)
2330      fwrite(s + len, add, 1, pimg->fh);
2331
2332   pimg->label_len = len + add;
2333   if (add > del && !check_label_space(pimg, pimg->label_len + 1))
2334      return 0; /* FIXME: distinguish out of memory... */
2335
2336   memcpy(pimg->label_buf + len, s + len, add + 1);
2337
2338   return !ferror(pimg->fh);
2339}
2340
2341static void
2342img_write_item_date_new(img *pimg)
2343{
2344    int same, unset;
2345    /* Only write dates when they've changed. */
2346#if IMG_API_VERSION == 0
2347    if (pimg->date1 == pimg->olddate1 && pimg->date2 == pimg->olddate2)
2348        return;
2349
2350    same = (pimg->date1 == pimg->date2);
2351    unset = (pimg->date1 == 0);
2352#else /* IMG_API_VERSION == 1 */
2353    if (pimg->days1 == pimg->olddays1 && pimg->days2 == pimg->olddays2)
2354        return;
2355
2356    same = (pimg->days1 == pimg->days2);
2357    unset = (pimg->days1 == -1);
2358#endif
2359
2360    if (same) {
2361        if (unset) {
2362            PUTC(0x10, pimg->fh);
2363        } else {
2364            PUTC(0x11, pimg->fh);
2365#if IMG_API_VERSION == 0
2366            put16(pimg->date1 / 86400 + 25567, pimg->fh);
2367#else /* IMG_API_VERSION == 1 */
2368            put16(pimg->days1, pimg->fh);
2369#endif
2370        }
2371    } else {
2372#if IMG_API_VERSION == 0
2373        int diff = (pimg->date2 - pimg->date1) / 86400;
2374        if (diff > 0 && diff <= 256) {
2375            PUTC(0x12, pimg->fh);
2376            put16(pimg->date1 / 86400 + 25567, pimg->fh);
2377            PUTC(diff - 1, pimg->fh);
2378        } else {
2379            PUTC(0x13, pimg->fh);
2380            put16(pimg->date1 / 86400 + 25567, pimg->fh);
2381            put16(pimg->date2 / 86400 + 25567, pimg->fh);
2382        }
2383#else /* IMG_API_VERSION == 1 */
2384        int diff = pimg->days2 - pimg->days1;
2385        if (diff > 0 && diff <= 256) {
2386            PUTC(0x12, pimg->fh);
2387            put16(pimg->days1, pimg->fh);
2388            PUTC(diff - 1, pimg->fh);
2389        } else {
2390            PUTC(0x13, pimg->fh);
2391            put16(pimg->days1, pimg->fh);
2392            put16(pimg->days2, pimg->fh);
2393        }
2394#endif
2395    }
2396#if IMG_API_VERSION == 0
2397    pimg->olddate1 = pimg->date1;
2398    pimg->olddate2 = pimg->date2;
2399#else /* IMG_API_VERSION == 1 */
2400    pimg->olddays1 = pimg->days1;
2401    pimg->olddays2 = pimg->days2;
2402#endif
2403}
2404
2405static void
2406img_write_item_date(img *pimg)
2407{
2408    int same, unset;
2409    /* Only write dates when they've changed. */
2410#if IMG_API_VERSION == 0
2411    if (pimg->date1 == pimg->olddate1 && pimg->date2 == pimg->olddate2)
2412        return;
2413
2414    same = (pimg->date1 == pimg->date2);
2415    unset = (pimg->date1 == 0);
2416#else /* IMG_API_VERSION == 1 */
2417    if (pimg->days1 == pimg->olddays1 && pimg->days2 == pimg->olddays2)
2418        return;
2419
2420    same = (pimg->days1 == pimg->days2);
2421    unset = (pimg->days1 == -1);
2422#endif
2423
2424    if (same) {
2425        if (img_output_version < 7) {
2426            PUTC(0x20, pimg->fh);
2427#if IMG_API_VERSION == 0
2428            put32(pimg->date1, pimg->fh);
2429#else /* IMG_API_VERSION == 1 */
2430            put32((pimg->days1 - 25567) * 86400, pimg->fh);
2431#endif
2432        } else {
2433            if (unset) {
2434                PUTC(0x24, pimg->fh);
2435            } else {
2436                PUTC(0x20, pimg->fh);
2437#if IMG_API_VERSION == 0
2438                put16(pimg->date1 / 86400 + 25567, pimg->fh);
2439#else /* IMG_API_VERSION == 1 */
2440                put16(pimg->days1, pimg->fh);
2441#endif
2442            }
2443        }
2444    } else {
2445        if (img_output_version < 7) {
2446            PUTC(0x21, pimg->fh);
2447#if IMG_API_VERSION == 0
2448            put32(pimg->date1, pimg->fh);
2449            put32(pimg->date2, pimg->fh);
2450#else /* IMG_API_VERSION == 1 */
2451            put32((pimg->days1 - 25567) * 86400, pimg->fh);
2452            put32((pimg->days2 - 25567) * 86400, pimg->fh);
2453#endif
2454        } else {
2455#if IMG_API_VERSION == 0
2456            int diff = (pimg->date2 - pimg->date1) / 86400;
2457            if (diff > 0 && diff <= 256) {
2458                PUTC(0x21, pimg->fh);
2459                put16(pimg->date1 / 86400 + 25567, pimg->fh);
2460                PUTC(diff - 1, pimg->fh);
2461            } else {
2462                PUTC(0x23, pimg->fh);
2463                put16(pimg->date1 / 86400 + 25567, pimg->fh);
2464                put16(pimg->date2 / 86400 + 25567, pimg->fh);
2465            }
2466#else /* IMG_API_VERSION == 1 */
2467            int diff = pimg->days2 - pimg->days1;
2468            if (diff > 0 && diff <= 256) {
2469                PUTC(0x21, pimg->fh);
2470                put16(pimg->days1, pimg->fh);
2471                PUTC(diff - 1, pimg->fh);
2472            } else {
2473                PUTC(0x23, pimg->fh);
2474                put16(pimg->days1, pimg->fh);
2475                put16(pimg->days2, pimg->fh);
2476            }
2477#endif
2478        }
2479    }
2480#if IMG_API_VERSION == 0
2481    pimg->olddate1 = pimg->date1;
2482    pimg->olddate2 = pimg->date2;
2483#else /* IMG_API_VERSION == 1 */
2484    pimg->olddays1 = pimg->days1;
2485    pimg->olddays2 = pimg->days2;
2486#endif
2487}
2488
2489static void
2490img_write_item_new(img *pimg, int code, int flags, const char *s,
2491                   double x, double y, double z);
2492static void
2493img_write_item_v3to7(img *pimg, int code, int flags, const char *s,
2494                     double x, double y, double z);
2495static void
2496img_write_item_ancient(img *pimg, int code, int flags, const char *s,
2497                       double x, double y, double z);
2498
2499void
2500img_write_item(img *pimg, int code, int flags, const char *s,
2501               double x, double y, double z)
2502{
2503   if (!pimg) return;
2504   if (pimg->version >= 8) {
2505      img_write_item_new(pimg, code, flags, s, x, y, z);
2506   } else if (pimg->version >= 3) {
2507      img_write_item_v3to7(pimg, code, flags, s, x, y, z);
2508   } else {
2509      img_write_item_ancient(pimg, code, flags, s, x, y, z);
2510   }
2511}
2512
2513static void
2514img_write_item_new(img *pimg, int code, int flags, const char *s,
2515                   double x, double y, double z)
2516{
2517   switch (code) {
2518    case img_LABEL:
2519      write_v8label(pimg, 0x80 | flags, 0, -1, s);
2520      break;
2521    case img_XSECT: {
2522      INT32_T l, r, u, d, max_dim;
2523      img_write_item_date_new(pimg);
2524      l = (INT32_T)my_lround(pimg->l * 100.0);
2525      r = (INT32_T)my_lround(pimg->r * 100.0);
2526      u = (INT32_T)my_lround(pimg->u * 100.0);
2527      d = (INT32_T)my_lround(pimg->d * 100.0);
2528      if (l < 0) l = -1;
2529      if (r < 0) r = -1;
2530      if (u < 0) u = -1;
2531      if (d < 0) d = -1;
2532      max_dim = max(max(l, r), max(u, d));
2533      flags = (flags & img_XFLAG_END) ? 1 : 0;
2534      if (max_dim >= 32768) flags |= 2;
2535      write_v8label(pimg, 0x30 | flags, 0, -1, s);
2536      if (flags & 2) {
2537         /* Big passage!  Need to use 4 bytes. */
2538         put32(l, pimg->fh);
2539         put32(r, pimg->fh);
2540         put32(u, pimg->fh);
2541         put32(d, pimg->fh);
2542      } else {
2543         put16(l, pimg->fh);
2544         put16(r, pimg->fh);
2545         put16(u, pimg->fh);
2546         put16(d, pimg->fh);
2547      }
2548      return;
2549    }
2550    case img_MOVE:
2551      PUTC(15, pimg->fh);
2552      break;
2553    case img_LINE:
2554      img_write_item_date_new(pimg);
2555      if (pimg->style != pimg->oldstyle) {
2556          switch (pimg->style) {
2557            case img_STYLE_NORMAL:
2558            case img_STYLE_DIVING:
2559            case img_STYLE_CARTESIAN:
2560            case img_STYLE_CYLPOLAR:
2561            case img_STYLE_NOSURVEY:
2562               PUTC(pimg->style, pimg->fh);
2563               break;
2564          }
2565          pimg->oldstyle = pimg->style;
2566      }
2567      write_v8label(pimg, 0x40 | flags, 0x20, 0x00, s ? s : "");
2568      break;
2569    default: /* ignore for now */
2570      return;
2571   }
2572   write_coord(pimg->fh, x, y, z);
2573}
2574
2575static void
2576img_write_item_v3to7(img *pimg, int code, int flags, const char *s,
2577                     double x, double y, double z)
2578{
2579   switch (code) {
2580    case img_LABEL:
2581      write_v3label(pimg, 0x40 | flags, s);
2582      break;
2583    case img_XSECT: {
2584      INT32_T l, r, u, d, max_dim;
2585      /* Need at least version 5 for img_XSECT. */
2586      if (pimg->version < 5) return;
2587      img_write_item_date(pimg);
2588      l = (INT32_T)my_lround(pimg->l * 100.0);
2589      r = (INT32_T)my_lround(pimg->r * 100.0);
2590      u = (INT32_T)my_lround(pimg->u * 100.0);
2591      d = (INT32_T)my_lround(pimg->d * 100.0);
2592      if (l < 0) l = -1;
2593      if (r < 0) r = -1;
2594      if (u < 0) u = -1;
2595      if (d < 0) d = -1;
2596      max_dim = max(max(l, r), max(u, d));
2597      flags = (flags & img_XFLAG_END) ? 1 : 0;
2598      if (max_dim >= 32768) flags |= 2;
2599      write_v3label(pimg, 0x30 | flags, s);
2600      if (flags & 2) {
2601         /* Big passage!  Need to use 4 bytes. */
2602         put32(l, pimg->fh);
2603         put32(r, pimg->fh);
2604         put32(u, pimg->fh);
2605         put32(d, pimg->fh);
2606      } else {
2607         put16(l, pimg->fh);
2608         put16(r, pimg->fh);
2609         put16(u, pimg->fh);
2610         put16(d, pimg->fh);
2611      }
2612      return;
2613    }
2614    case img_MOVE:
2615      PUTC(15, pimg->fh);
2616      break;
2617    case img_LINE:
2618      if (pimg->version >= 4) {
2619          img_write_item_date(pimg);
2620      }
2621      write_v3label(pimg, 0x80 | flags, s ? s : "");
2622      break;
2623    default: /* ignore for now */
2624      return;
2625   }
2626   write_coord(pimg->fh, x, y, z);
2627}
2628
2629static void
2630img_write_item_ancient(img *pimg, int code, int flags, const char *s,
2631                       double x, double y, double z)
2632{
2633   size_t len;
2634   INT32_T opt = 0;
2635   SVX_ASSERT(pimg->version > 0);
2636   switch (code) {
2637    case img_LABEL:
2638      if (pimg->version == 1) {
2639         /* put a move before each label */
2640         img_write_item(pimg, img_MOVE, 0, NULL, x, y, z);
2641         put32(2, pimg->fh);
2642         fputsnl(s, pimg->fh);
2643         return;
2644      }
2645      len = strlen(s);
2646      if (len > 255 || strchr(s, '\n')) {
2647         /* long label - not in early incarnations of v2 format, but few
2648          * 3d files will need these, so better not to force incompatibility
2649          * with a new version I think... */
2650         PUTC(7, pimg->fh);
2651         PUTC(flags, pimg->fh);
2652         put32(len, pimg->fh);
2653         fputs(s, pimg->fh);
2654      } else {
2655         PUTC(0x40 | (flags & 0x3f), pimg->fh);
2656         fputsnl(s, pimg->fh);
2657      }
2658      opt = 0;
2659      break;
2660    case img_MOVE:
2661      opt = 4;
2662      break;
2663    case img_LINE:
2664      if (pimg->version > 1) {
2665         opt = 0x80 | (flags & 0x3f);
2666         break;
2667      }
2668      opt = 5;
2669      break;
2670    default: /* ignore for now */
2671      return;
2672   }
2673   if (pimg->version == 1) {
2674      put32(opt, pimg->fh);
2675   } else {
2676      if (opt) PUTC(opt, pimg->fh);
2677   }
2678   write_coord(pimg->fh, x, y, z);
2679}
2680
2681/* Write error information for the current traverse
2682 * n_legs is the number of legs in the traverse
2683 * length is the traverse length (in m)
2684 * E is the ratio of the observed misclosure to the theoretical one
2685 * H is the ratio of the observed horizontal misclosure to the theoretical one
2686 * V is the ratio of the observed vertical misclosure to the theoretical one
2687 */
2688void
2689img_write_errors(img *pimg, int n_legs, double length,
2690                 double E, double H, double V)
2691{
2692    PUTC((pimg->version >= 8 ? 0x1f : 0x22), pimg->fh);
2693    put32(n_legs, pimg->fh);
2694    put32((INT32_T)my_lround(length * 100.0), pimg->fh);
2695    put32((INT32_T)my_lround(E * 100.0), pimg->fh);
2696    put32((INT32_T)my_lround(H * 100.0), pimg->fh);
2697    put32((INT32_T)my_lround(V * 100.0), pimg->fh);
2698}
2699
2700int
2701img_close(img *pimg)
2702{
2703   int result = 1;
2704   if (pimg) {
2705      if (pimg->fh) {
2706         if (pimg->fRead) {
2707            osfree(pimg->survey);
2708            osfree(pimg->title);
2709            osfree(pimg->datestamp);
2710         } else {
2711            /* write end of data marker */
2712            switch (pimg->version) {
2713             case 1:
2714               put32((INT32_T)-1, pimg->fh);
2715               break;
2716             default:
2717               if (pimg->version <= 7 ?
2718                   (pimg->label_len != 0) :
2719                   (pimg->style != img_STYLE_NORMAL)) {
2720                  PUTC(0, pimg->fh);
2721               }
2722               /* FALL THROUGH */
2723             case 2:
2724               PUTC(0, pimg->fh);
2725               break;
2726            }
2727         }
2728         if (ferror(pimg->fh)) result = 0;
2729         if (fclose(pimg->fh)) result = 0;
2730         if (!result) img_errno = pimg->fRead ? IMG_READERROR : IMG_WRITEERROR;
2731      }
2732      osfree(pimg->label_buf);
2733      osfree(pimg->filename_opened);
2734      osfree(pimg);
2735   }
2736   return result;
2737}
Note: See TracBrowser for help on using the repository browser.