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