1/* $Id: tif_open.c,v 1.48 2016-11-20 22:29:47 erouault Exp $ */
2
3/*
4 * Copyright (c) 1988-1997 Sam Leffler
5 * Copyright (c) 1991-1997 Silicon Graphics, Inc.
6 *
7 * Permission to use, copy, modify, distribute, and sell this software and
8 * its documentation for any purpose is hereby granted without fee, provided
9 * that (i) the above copyright notices and this permission notice appear in
10 * all copies of the software and related documentation, and (ii) the names of
11 * Sam Leffler and Silicon Graphics may not be used in any advertising or
12 * publicity relating to the software without the specific, prior written
13 * permission of Sam Leffler and Silicon Graphics.
14 *
15 * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
17 * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
18 *
19 * IN NO EVENT SHALL SAM LEFFLER OR SILICON GRAPHICS BE LIABLE FOR
20 * ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
21 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
22 * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF
23 * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
24 * OF THIS SOFTWARE.
25 */
26
27/*
28 * TIFF Library.
29 */
30#include "tiffiop.h"
31
32/*
33 * Dummy functions to fill the omitted client procedures.
34 */
35static int
36_tiffDummyMapProc(thandle_t fd, void** pbase, toff_t* psize)
37{
38	(void) fd; (void) pbase; (void) psize;
39	return (0);
40}
41
42static void
43_tiffDummyUnmapProc(thandle_t fd, void* base, toff_t size)
44{
45	(void) fd; (void) base; (void) size;
46}
47
48int
49_TIFFgetMode(const char* mode, const char* module)
50{
51	int m = -1;
52
53	switch (mode[0]) {
54	case 'r':
55		m = O_RDONLY;
56		if (mode[1] == '+')
57			m = O_RDWR;
58		break;
59	case 'w':
60	case 'a':
61		m = O_RDWR|O_CREAT;
62		if (mode[0] == 'w')
63			m |= O_TRUNC;
64		break;
65	default:
66		TIFFErrorExt(0, module, "\"%s\": Bad mode", mode);
67		break;
68	}
69	return (m);
70}
71
72TIFF*
73TIFFClientOpen(
74	const char* name, const char* mode,
75	thandle_t clientdata,
76	TIFFReadWriteProc readproc,
77	TIFFReadWriteProc writeproc,
78	TIFFSeekProc seekproc,
79	TIFFCloseProc closeproc,
80	TIFFSizeProc sizeproc,
81	TIFFMapFileProc mapproc,
82	TIFFUnmapFileProc unmapproc
83)
84{
85	static const char module[] = "TIFFClientOpen";
86	TIFF *tif;
87	int m;
88	const char* cp;
89
90	/* The following are configuration checks. They should be redundant, but should not
91	 * compile to any actual code in an optimised release build anyway. If any of them
92	 * fail, (makefile-based or other) configuration is not correct */
93	assert(sizeof(uint8)==1);
94	assert(sizeof(int8)==1);
95	assert(sizeof(uint16)==2);
96	assert(sizeof(int16)==2);
97	assert(sizeof(uint32)==4);
98	assert(sizeof(int32)==4);
99	assert(sizeof(uint64)==8);
100	assert(sizeof(int64)==8);
101	assert(sizeof(tmsize_t)==sizeof(void*));
102	{
103		union{
104			uint8 a8[2];
105			uint16 a16;
106		} n;
107		n.a8[0]=1;
108		n.a8[1]=0;
109		#ifdef WORDS_BIGENDIAN
110		assert(n.a16==256);
111		#else
112		assert(n.a16==1);
113		#endif
114	}
115
116	m = _TIFFgetMode(mode, module);
117	if (m == -1)
118		goto bad2;
119	tif = (TIFF *)_TIFFmalloc((tmsize_t)(sizeof (TIFF) + strlen(name) + 1));
120	if (tif == NULL) {
121		TIFFErrorExt(clientdata, module, "%s: Out of memory (TIFF structure)", name);
122		goto bad2;
123	}
124	_TIFFmemset(tif, 0, sizeof (*tif));
125	tif->tif_name = (char *)tif + sizeof (TIFF);
126	strcpy(tif->tif_name, name);
127	tif->tif_mode = m &~ (O_CREAT|O_TRUNC);
128	tif->tif_curdir = (uint16) -1;		/* non-existent directory */
129	tif->tif_curoff = 0;
130	tif->tif_curstrip = (uint32) -1;	/* invalid strip */
131	tif->tif_row = (uint32) -1;		/* read/write pre-increment */
132	tif->tif_clientdata = clientdata;
133	if (!readproc || !writeproc || !seekproc || !closeproc || !sizeproc) {
134		TIFFErrorExt(clientdata, module,
135		    "One of the client procedures is NULL pointer.");
136		goto bad2;
137	}
138	tif->tif_readproc = readproc;
139	tif->tif_writeproc = writeproc;
140	tif->tif_seekproc = seekproc;
141	tif->tif_closeproc = closeproc;
142	tif->tif_sizeproc = sizeproc;
143	if (mapproc)
144		tif->tif_mapproc = mapproc;
145	else
146		tif->tif_mapproc = _tiffDummyMapProc;
147	if (unmapproc)
148		tif->tif_unmapproc = unmapproc;
149	else
150		tif->tif_unmapproc = _tiffDummyUnmapProc;
151	_TIFFSetDefaultCompressionState(tif);    /* setup default state */
152	/*
153	 * Default is to return data MSB2LSB and enable the
154	 * use of memory-mapped files and strip chopping when
155	 * a file is opened read-only.
156	 */
157	tif->tif_flags = FILLORDER_MSB2LSB;
158	if (m == O_RDONLY )
159		tif->tif_flags |= TIFF_MAPPED;
160
161	#ifdef STRIPCHOP_DEFAULT
162	if (m == O_RDONLY || m == O_RDWR)
163		tif->tif_flags |= STRIPCHOP_DEFAULT;
164	#endif
165
166	/*
167	 * Process library-specific flags in the open mode string.
168	 * The following flags may be used to control intrinsic library
169	 * behaviour that may or may not be desirable (usually for
170	 * compatibility with some application that claims to support
171	 * TIFF but only supports some brain dead idea of what the
172	 * vendor thinks TIFF is):
173	 *
174	 * 'l' use little-endian byte order for creating a file
175	 * 'b' use big-endian byte order for creating a file
176	 * 'L' read/write information using LSB2MSB bit order
177	 * 'B' read/write information using MSB2LSB bit order
178	 * 'H' read/write information using host bit order
179	 * 'M' enable use of memory-mapped files when supported
180	 * 'm' disable use of memory-mapped files
181	 * 'C' enable strip chopping support when reading
182	 * 'c' disable strip chopping support
183	 * 'h' read TIFF header only, do not load the first IFD
184	 * '4' ClassicTIFF for creating a file (default)
185	 * '8' BigTIFF for creating a file
186	 *
187	 * The use of the 'l' and 'b' flags is strongly discouraged.
188	 * These flags are provided solely because numerous vendors,
189	 * typically on the PC, do not correctly support TIFF; they
190	 * only support the Intel little-endian byte order.  This
191	 * support is not configured by default because it supports
192	 * the violation of the TIFF spec that says that readers *MUST*
193	 * support both byte orders.  It is strongly recommended that
194	 * you not use this feature except to deal with busted apps
195	 * that write invalid TIFF.  And even in those cases you should
196	 * bang on the vendors to fix their software.
197	 *
198	 * The 'L', 'B', and 'H' flags are intended for applications
199	 * that can optimize operations on data by using a particular
200	 * bit order.  By default the library returns data in MSB2LSB
201	 * bit order for compatibility with older versions of this
202	 * library.  Returning data in the bit order of the native CPU
203	 * makes the most sense but also requires applications to check
204	 * the value of the FillOrder tag; something they probably do
205	 * not do right now.
206	 *
207	 * The 'M' and 'm' flags are provided because some virtual memory
208	 * systems exhibit poor behaviour when large images are mapped.
209	 * These options permit clients to control the use of memory-mapped
210	 * files on a per-file basis.
211	 *
212	 * The 'C' and 'c' flags are provided because the library support
213	 * for chopping up large strips into multiple smaller strips is not
214	 * application-transparent and as such can cause problems.  The 'c'
215	 * option permits applications that only want to look at the tags,
216	 * for example, to get the unadulterated TIFF tag information.
217	 */
218	for (cp = mode; *cp; cp++)
219		switch (*cp) {
220			case 'b':
221				#ifndef WORDS_BIGENDIAN
222				if (m&O_CREAT)
223					tif->tif_flags |= TIFF_SWAB;
224				#endif
225				break;
226			case 'l':
227				#ifdef WORDS_BIGENDIAN
228				if ((m&O_CREAT))
229					tif->tif_flags |= TIFF_SWAB;
230				#endif
231				break;
232			case 'B':
233				tif->tif_flags = (tif->tif_flags &~ TIFF_FILLORDER) |
234				    FILLORDER_MSB2LSB;
235				break;
236			case 'L':
237				tif->tif_flags = (tif->tif_flags &~ TIFF_FILLORDER) |
238				    FILLORDER_LSB2MSB;
239				break;
240			case 'H':
241				tif->tif_flags = (tif->tif_flags &~ TIFF_FILLORDER) |
242				    HOST_FILLORDER;
243				break;
244			case 'M':
245				if (m == O_RDONLY)
246					tif->tif_flags |= TIFF_MAPPED;
247				break;
248			case 'm':
249				if (m == O_RDONLY)
250					tif->tif_flags &= ~TIFF_MAPPED;
251				break;
252			case 'C':
253				if (m == O_RDONLY)
254					tif->tif_flags |= TIFF_STRIPCHOP;
255				break;
256			case 'c':
257				if (m == O_RDONLY)
258					tif->tif_flags &= ~TIFF_STRIPCHOP;
259				break;
260			case 'h':
261				tif->tif_flags |= TIFF_HEADERONLY;
262				break;
263			case '8':
264				if (m&O_CREAT)
265					tif->tif_flags |= TIFF_BIGTIFF;
266				break;
267		}
268	/*
269	 * Read in TIFF header.
270	 */
271	if ((m & O_TRUNC) ||
272	    !ReadOK(tif, &tif->tif_header, sizeof (TIFFHeaderClassic))) {
273		if (tif->tif_mode == O_RDONLY) {
274			TIFFErrorExt(tif->tif_clientdata, name,
275			    "Cannot read TIFF header");
276			goto bad;
277		}
278		/*
279		 * Setup header and write.
280		 */
281		#ifdef WORDS_BIGENDIAN
282		tif->tif_header.common.tiff_magic = (tif->tif_flags & TIFF_SWAB)
283		    ? TIFF_LITTLEENDIAN : TIFF_BIGENDIAN;
284		#else
285		tif->tif_header.common.tiff_magic = (tif->tif_flags & TIFF_SWAB)
286		    ? TIFF_BIGENDIAN : TIFF_LITTLEENDIAN;
287		#endif
288		if (!(tif->tif_flags&TIFF_BIGTIFF))
289		{
290			tif->tif_header.common.tiff_version = TIFF_VERSION_CLASSIC;
291			tif->tif_header.classic.tiff_diroff = 0;
292			if (tif->tif_flags & TIFF_SWAB)
293				TIFFSwabShort(&tif->tif_header.common.tiff_version);
294			tif->tif_header_size = sizeof(TIFFHeaderClassic);
295		}
296		else
297		{
298			tif->tif_header.common.tiff_version = TIFF_VERSION_BIG;
299			tif->tif_header.big.tiff_offsetsize = 8;
300			tif->tif_header.big.tiff_unused = 0;
301			tif->tif_header.big.tiff_diroff = 0;
302			if (tif->tif_flags & TIFF_SWAB)
303			{
304				TIFFSwabShort(&tif->tif_header.common.tiff_version);
305				TIFFSwabShort(&tif->tif_header.big.tiff_offsetsize);
306			}
307			tif->tif_header_size = sizeof (TIFFHeaderBig);
308		}
309		/*
310		 * The doc for "fopen" for some STD_C_LIBs says that if you
311		 * open a file for modify ("+"), then you must fseek (or
312		 * fflush?) between any freads and fwrites.  This is not
313		 * necessary on most systems, but has been shown to be needed
314		 * on Solaris.
315		 */
316		TIFFSeekFile( tif, 0, SEEK_SET );
317		if (!WriteOK(tif, &tif->tif_header, (tmsize_t)(tif->tif_header_size))) {
318			TIFFErrorExt(tif->tif_clientdata, name,
319			    "Error writing TIFF header");
320			goto bad;
321		}
322		/*
323		 * Setup the byte order handling.
324		 */
325		if (tif->tif_header.common.tiff_magic == TIFF_BIGENDIAN) {
326			#ifndef WORDS_BIGENDIAN
327			tif->tif_flags |= TIFF_SWAB;
328			#endif
329		} else {
330			#ifdef WORDS_BIGENDIAN
331			tif->tif_flags |= TIFF_SWAB;
332			#endif
333		}
334		/*
335		 * Setup default directory.
336		 */
337		if (!TIFFDefaultDirectory(tif))
338			goto bad;
339		tif->tif_diroff = 0;
340		tif->tif_dirlist = NULL;
341		tif->tif_dirlistsize = 0;
342		tif->tif_dirnumber = 0;
343		return (tif);
344	}
345	/*
346	 * Setup the byte order handling.
347	 */
348	if (tif->tif_header.common.tiff_magic != TIFF_BIGENDIAN &&
349	    tif->tif_header.common.tiff_magic != TIFF_LITTLEENDIAN
350	    #if MDI_SUPPORT
351	    &&
352	    #if HOST_BIGENDIAN
353	    tif->tif_header.common.tiff_magic != MDI_BIGENDIAN
354	    #else
355	    tif->tif_header.common.tiff_magic != MDI_LITTLEENDIAN
356	    #endif
357	    ) {
358		TIFFErrorExt(tif->tif_clientdata, name,
359		    "Not a TIFF or MDI file, bad magic number %d (0x%x)",
360	    #else
361	    ) {
362		TIFFErrorExt(tif->tif_clientdata, name,
363		    "Not a TIFF file, bad magic number %d (0x%x)",
364	    #endif
365		    tif->tif_header.common.tiff_magic,
366		    tif->tif_header.common.tiff_magic);
367		goto bad;
368	}
369	if (tif->tif_header.common.tiff_magic == TIFF_BIGENDIAN) {
370		#ifndef WORDS_BIGENDIAN
371		tif->tif_flags |= TIFF_SWAB;
372		#endif
373	} else {
374		#ifdef WORDS_BIGENDIAN
375		tif->tif_flags |= TIFF_SWAB;
376		#endif
377	}
378	if (tif->tif_flags & TIFF_SWAB)
379		TIFFSwabShort(&tif->tif_header.common.tiff_version);
380	if ((tif->tif_header.common.tiff_version != TIFF_VERSION_CLASSIC)&&
381	    (tif->tif_header.common.tiff_version != TIFF_VERSION_BIG)) {
382		TIFFErrorExt(tif->tif_clientdata, name,
383		    "Not a TIFF file, bad version number %d (0x%x)",
384		    tif->tif_header.common.tiff_version,
385		    tif->tif_header.common.tiff_version);
386		goto bad;
387	}
388	if (tif->tif_header.common.tiff_version == TIFF_VERSION_CLASSIC)
389	{
390		if (tif->tif_flags & TIFF_SWAB)
391			TIFFSwabLong(&tif->tif_header.classic.tiff_diroff);
392		tif->tif_header_size = sizeof(TIFFHeaderClassic);
393	}
394	else
395	{
396		if (!ReadOK(tif, ((uint8*)(&tif->tif_header) + sizeof(TIFFHeaderClassic)), (sizeof(TIFFHeaderBig)-sizeof(TIFFHeaderClassic))))
397		{
398			TIFFErrorExt(tif->tif_clientdata, name,
399			    "Cannot read TIFF header");
400			goto bad;
401		}
402		if (tif->tif_flags & TIFF_SWAB)
403		{
404			TIFFSwabShort(&tif->tif_header.big.tiff_offsetsize);
405			TIFFSwabLong8(&tif->tif_header.big.tiff_diroff);
406		}
407		if (tif->tif_header.big.tiff_offsetsize != 8)
408		{
409			TIFFErrorExt(tif->tif_clientdata, name,
410			    "Not a TIFF file, bad BigTIFF offsetsize %d (0x%x)",
411			    tif->tif_header.big.tiff_offsetsize,
412			    tif->tif_header.big.tiff_offsetsize);
413			goto bad;
414		}
415		if (tif->tif_header.big.tiff_unused != 0)
416		{
417			TIFFErrorExt(tif->tif_clientdata, name,
418			    "Not a TIFF file, bad BigTIFF unused %d (0x%x)",
419			    tif->tif_header.big.tiff_unused,
420			    tif->tif_header.big.tiff_unused);
421			goto bad;
422		}
423		tif->tif_header_size = sizeof(TIFFHeaderBig);
424		tif->tif_flags |= TIFF_BIGTIFF;
425	}
426	tif->tif_flags |= TIFF_MYBUFFER;
427	tif->tif_rawcp = tif->tif_rawdata = 0;
428	tif->tif_rawdatasize = 0;
429        tif->tif_rawdataoff = 0;
430        tif->tif_rawdataloaded = 0;
431
432	switch (mode[0]) {
433		case 'r':
434			if (!(tif->tif_flags&TIFF_BIGTIFF))
435				tif->tif_nextdiroff = tif->tif_header.classic.tiff_diroff;
436			else
437				tif->tif_nextdiroff = tif->tif_header.big.tiff_diroff;
438			/*
439			 * Try to use a memory-mapped file if the client
440			 * has not explicitly suppressed usage with the
441			 * 'm' flag in the open mode (see above).
442			 */
443			if (tif->tif_flags & TIFF_MAPPED)
444			{
445				toff_t n;
446				if (TIFFMapFileContents(tif,(void**)(&tif->tif_base),&n))
447				{
448					tif->tif_size=(tmsize_t)n;
449					assert((toff_t)tif->tif_size==n);
450				}
451				else
452					tif->tif_flags &= ~TIFF_MAPPED;
453			}
454			/*
455			 * Sometimes we do not want to read the first directory (for example,
456			 * it may be broken) and want to proceed to other directories. I this
457			 * case we use the TIFF_HEADERONLY flag to open file and return
458			 * immediately after reading TIFF header.
459			 */
460			if (tif->tif_flags & TIFF_HEADERONLY)
461				return (tif);
462
463			/*
464			 * Setup initial directory.
465			 */
466			if (TIFFReadDirectory(tif)) {
467				tif->tif_rawcc = (tmsize_t)-1;
468				tif->tif_flags |= TIFF_BUFFERSETUP;
469				return (tif);
470			}
471			break;
472		case 'a':
473			/*
474			 * New directories are automatically append
475			 * to the end of the directory chain when they
476			 * are written out (see TIFFWriteDirectory).
477			 */
478			if (!TIFFDefaultDirectory(tif))
479				goto bad;
480			return (tif);
481	}
482bad:
483	tif->tif_mode = O_RDONLY;	/* XXX avoid flush */
484        TIFFCleanup(tif);
485bad2:
486	return ((TIFF*)0);
487}
488
489/*
490 * Query functions to access private data.
491 */
492
493/*
494 * Return open file's name.
495 */
496const char *
497TIFFFileName(TIFF* tif)
498{
499	return (tif->tif_name);
500}
501
502/*
503 * Set the file name.
504 */
505const char *
506TIFFSetFileName(TIFF* tif, const char *name)
507{
508	const char* old_name = tif->tif_name;
509	tif->tif_name = (char *)name;
510	return (old_name);
511}
512
513/*
514 * Return open file's I/O descriptor.
515 */
516int
517TIFFFileno(TIFF* tif)
518{
519	return (tif->tif_fd);
520}
521
522/*
523 * Set open file's I/O descriptor, and return previous value.
524 */
525int
526TIFFSetFileno(TIFF* tif, int fd)
527{
528        int old_fd = tif->tif_fd;
529	tif->tif_fd = fd;
530	return old_fd;
531}
532
533/*
534 * Return open file's clientdata.
535 */
536thandle_t
537TIFFClientdata(TIFF* tif)
538{
539	return (tif->tif_clientdata);
540}
541
542/*
543 * Set open file's clientdata, and return previous value.
544 */
545thandle_t
546TIFFSetClientdata(TIFF* tif, thandle_t newvalue)
547{
548	thandle_t m = tif->tif_clientdata;
549	tif->tif_clientdata = newvalue;
550	return m;
551}
552
553/*
554 * Return read/write mode.
555 */
556int
557TIFFGetMode(TIFF* tif)
558{
559	return (tif->tif_mode);
560}
561
562/*
563 * Return read/write mode.
564 */
565int
566TIFFSetMode(TIFF* tif, int mode)
567{
568	int old_mode = tif->tif_mode;
569	tif->tif_mode = mode;
570	return (old_mode);
571}
572
573/*
574 * Return nonzero if file is organized in
575 * tiles; zero if organized as strips.
576 */
577int
578TIFFIsTiled(TIFF* tif)
579{
580	return (isTiled(tif));
581}
582
583/*
584 * Return current row being read/written.
585 */
586uint32
587TIFFCurrentRow(TIFF* tif)
588{
589	return (tif->tif_row);
590}
591
592/*
593 * Return index of the current directory.
594 */
595uint16
596TIFFCurrentDirectory(TIFF* tif)
597{
598	return (tif->tif_curdir);
599}
600
601/*
602 * Return current strip.
603 */
604uint32
605TIFFCurrentStrip(TIFF* tif)
606{
607	return (tif->tif_curstrip);
608}
609
610/*
611 * Return current tile.
612 */
613uint32
614TIFFCurrentTile(TIFF* tif)
615{
616	return (tif->tif_curtile);
617}
618
619/*
620 * Return nonzero if the file has byte-swapped data.
621 */
622int
623TIFFIsByteSwapped(TIFF* tif)
624{
625	return ((tif->tif_flags & TIFF_SWAB) != 0);
626}
627
628/*
629 * Return nonzero if the data is returned up-sampled.
630 */
631int
632TIFFIsUpSampled(TIFF* tif)
633{
634	return (isUpSampled(tif));
635}
636
637/*
638 * Return nonzero if the data is returned in MSB-to-LSB bit order.
639 */
640int
641TIFFIsMSB2LSB(TIFF* tif)
642{
643	return (isFillOrder(tif, FILLORDER_MSB2LSB));
644}
645
646/*
647 * Return nonzero if given file was written in big-endian order.
648 */
649int
650TIFFIsBigEndian(TIFF* tif)
651{
652	return (tif->tif_header.common.tiff_magic == TIFF_BIGENDIAN);
653}
654
655/*
656 * Return pointer to file read method.
657 */
658TIFFReadWriteProc
659TIFFGetReadProc(TIFF* tif)
660{
661	return (tif->tif_readproc);
662}
663
664/*
665 * Return pointer to file write method.
666 */
667TIFFReadWriteProc
668TIFFGetWriteProc(TIFF* tif)
669{
670	return (tif->tif_writeproc);
671}
672
673/*
674 * Return pointer to file seek method.
675 */
676TIFFSeekProc
677TIFFGetSeekProc(TIFF* tif)
678{
679	return (tif->tif_seekproc);
680}
681
682/*
683 * Return pointer to file close method.
684 */
685TIFFCloseProc
686TIFFGetCloseProc(TIFF* tif)
687{
688	return (tif->tif_closeproc);
689}
690
691/*
692 * Return pointer to file size requesting method.
693 */
694TIFFSizeProc
695TIFFGetSizeProc(TIFF* tif)
696{
697	return (tif->tif_sizeproc);
698}
699
700/*
701 * Return pointer to memory mapping method.
702 */
703TIFFMapFileProc
704TIFFGetMapFileProc(TIFF* tif)
705{
706	return (tif->tif_mapproc);
707}
708
709/*
710 * Return pointer to memory unmapping method.
711 */
712TIFFUnmapFileProc
713TIFFGetUnmapFileProc(TIFF* tif)
714{
715	return (tif->tif_unmapproc);
716}
717
718/* vim: set ts=8 sts=8 sw=8 noet: */
719/*
720 * Local Variables:
721 * mode: c
722 * c-basic-offset: 8
723 * fill-column: 78
724 * End:
725 */
726