1 /** 2 * A library in the COFF format, used on 32-bit and 64-bit Windows targets. 3 * 4 * Copyright: Copyright (C) 1999-2021 by The D Language Foundation, All Rights Reserved 5 * Authors: $(LINK2 http://www.digitalmars.com, Walter Bright) 6 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 7 * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/libmscoff.d, _libmscoff.d) 8 * Documentation: https://dlang.org/phobos/dmd_libmscoff.html 9 * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/libmscoff.d 10 */ 11 12 module dmd.libmscoff; 13 14 import core.stdc.stdlib; 15 import core.stdc.string; 16 import core.stdc.time; 17 import core.stdc.stdio; 18 import core.stdc.string; 19 20 version (Posix) 21 { 22 import core.sys.posix.sys.stat; 23 import core.sys.posix.unistd; 24 } 25 version (Windows) 26 { 27 import core.sys.windows.stat; 28 } 29 30 import dmd.globals; 31 import dmd.lib; 32 import dmd.utils; 33 34 import dmd.root.array; 35 import dmd.root.file; 36 import dmd.root.filename; 37 import dmd.root.outbuffer; 38 import dmd.root.port; 39 import dmd.root.rmem; 40 import dmd.root.string; 41 import dmd.root.stringtable; 42 43 import dmd.scanmscoff; 44 45 // Entry point (only public symbol in this module). 46 public extern (C++) Library LibMSCoff_factory() 47 { 48 return new LibMSCoff(); 49 } 50 51 private: // for the remainder of this module 52 53 enum LOG = false; 54 55 struct MSCoffObjSymbol 56 { 57 const(char)[] name; // still has a terminating 0 58 MSCoffObjModule* om; 59 60 /// Predicate for `Array.sort`for name comparison 61 static int name_pred (scope const MSCoffObjSymbol** ppe1, scope const MSCoffObjSymbol** ppe2) nothrow @nogc pure 62 { 63 return dstrcmp((**ppe1).name, (**ppe2).name); 64 } 65 66 /// Predicate for `Array.sort`for offset comparison 67 static int offset_pred (scope const MSCoffObjSymbol** ppe1, scope const MSCoffObjSymbol** ppe2) nothrow @nogc pure 68 { 69 return (**ppe1).om.offset - (**ppe2).om.offset; 70 } 71 } 72 73 alias MSCoffObjModules = Array!(MSCoffObjModule*); 74 alias MSCoffObjSymbols = Array!(MSCoffObjSymbol*); 75 76 final class LibMSCoff : Library 77 { 78 MSCoffObjModules objmodules; // MSCoffObjModule[] 79 MSCoffObjSymbols objsymbols; // MSCoffObjSymbol[] 80 81 /*************************************** 82 * Add object module or library to the library. 83 * Examine the buffer to see which it is. 84 * If the buffer is NULL, use module_name as the file name 85 * and load the file. 86 */ 87 override void addObject(const(char)[] module_name, const ubyte[] buffer) 88 { 89 static if (LOG) 90 { 91 printf("LibMSCoff::addObject(%.*s)\n", cast(int)module_name.length, 92 module_name.ptr); 93 } 94 95 void corrupt(int reason) 96 { 97 error("corrupt MS Coff object module %.*s %d", 98 cast(int)module_name.length, module_name.ptr, reason); 99 } 100 101 int fromfile = 0; 102 auto buf = buffer.ptr; 103 auto buflen = buffer.length; 104 if (!buf) 105 { 106 assert(module_name.length, "No module nor buffer provided to `addObject`"); 107 // read file and take buffer ownership 108 auto data = readFile(Loc.initial, module_name).extractSlice(); 109 buf = data.ptr; 110 buflen = data.length; 111 fromfile = 1; 112 } 113 if (buflen < 16) 114 { 115 static if (LOG) 116 { 117 printf("buf = %p, buflen = %d\n", buf, buflen); 118 } 119 return corrupt(__LINE__); 120 } 121 if (memcmp(buf, "!<arch>\n".ptr, 8) == 0) 122 { 123 /* It's a library file. 124 * Pull each object module out of the library and add it 125 * to the object module array. 126 */ 127 static if (LOG) 128 { 129 printf("archive, buf = %p, buflen = %d\n", buf, buflen); 130 } 131 MSCoffLibHeader* flm = null; // first linker member 132 MSCoffLibHeader* slm = null; // second linker member 133 uint number_of_members = 0; 134 uint* member_file_offsets = null; 135 uint number_of_symbols = 0; 136 ushort* indices = null; 137 char* string_table = null; 138 size_t string_table_length = 0; 139 MSCoffLibHeader* lnm = null; // longname member 140 char* longnames = null; 141 size_t longnames_length = 0; 142 size_t offset = 8; 143 size_t mstart = objmodules.dim; 144 while (1) 145 { 146 offset = (offset + 1) & ~1; // round to even boundary 147 if (offset >= buflen) 148 break; 149 if (offset + MSCoffLibHeader.sizeof >= buflen) 150 return corrupt(__LINE__); 151 MSCoffLibHeader* header = cast(MSCoffLibHeader*)(cast(ubyte*)buf + offset); 152 offset += MSCoffLibHeader.sizeof; 153 char* endptr = null; 154 uint size = cast(uint)strtoul(cast(char*)header.file_size, &endptr, 10); 155 if (endptr >= header.file_size.ptr + 10 || *endptr != ' ') 156 return corrupt(__LINE__); 157 if (offset + size > buflen) 158 return corrupt(__LINE__); 159 //printf("header.object_name = '%.*s'\n", cast(int)MSCOFF_OBJECT_NAME_SIZE, header.object_name); 160 if (memcmp(cast(char*)header.object_name, cast(char*)"/ ", MSCOFF_OBJECT_NAME_SIZE) == 0) 161 { 162 if (!flm) 163 { 164 // First Linker Member, which is ignored 165 flm = header; 166 } 167 else if (!slm) 168 { 169 // Second Linker Member, which we require even though the format doesn't require it 170 slm = header; 171 if (size < 4 + 4) 172 return corrupt(__LINE__); 173 number_of_members = Port.readlongLE(cast(char*)buf + offset); 174 member_file_offsets = cast(uint*)(cast(char*)buf + offset + 4); 175 if (size < 4 + number_of_members * 4 + 4) 176 return corrupt(__LINE__); 177 number_of_symbols = Port.readlongLE(cast(char*)buf + offset + 4 + number_of_members * 4); 178 indices = cast(ushort*)(cast(char*)buf + offset + 4 + number_of_members * 4 + 4); 179 string_table = cast(char*)(cast(char*)buf + offset + 4 + number_of_members * 4 + 4 + number_of_symbols * 2); 180 if (size <= (4 + number_of_members * 4 + 4 + number_of_symbols * 2)) 181 return corrupt(__LINE__); 182 string_table_length = size - (4 + number_of_members * 4 + 4 + number_of_symbols * 2); 183 /* The number of strings in the string_table must be number_of_symbols; check it 184 * The strings must also be in ascending lexical order; not checked. 185 */ 186 size_t i = 0; 187 for (uint n = 0; n < number_of_symbols; n++) 188 { 189 while (1) 190 { 191 if (i >= string_table_length) 192 return corrupt(__LINE__); 193 if (!string_table[i++]) 194 break; 195 } 196 } 197 if (i != string_table_length) 198 return corrupt(__LINE__); 199 } 200 } 201 else if (memcmp(cast(char*)header.object_name, cast(char*)"// ", MSCOFF_OBJECT_NAME_SIZE) == 0) 202 { 203 if (!lnm) 204 { 205 lnm = header; 206 longnames = cast(char*)buf + offset; 207 longnames_length = size; 208 } 209 } 210 else 211 { 212 if (!slm) 213 return corrupt(__LINE__); 214 version (none) 215 { 216 // Microsoft Spec says longnames member must appear, but Microsoft Lib says otherwise 217 if (!lnm) 218 return corrupt(__LINE__); 219 } 220 auto om = new MSCoffObjModule(); 221 // Include MSCoffLibHeader in base[0..length], so we don't have to repro it 222 om.base = cast(ubyte*)buf + offset - MSCoffLibHeader.sizeof; 223 om.length = cast(uint)(size + MSCoffLibHeader.sizeof); 224 om.offset = 0; 225 if (header.object_name[0] == '/') 226 { 227 /* Pick long name out of longnames[] 228 */ 229 uint foff = cast(uint)strtoul(cast(char*)header.object_name + 1, &endptr, 10); 230 uint i; 231 for (i = 0; 1; i++) 232 { 233 if (foff + i >= longnames_length) 234 return corrupt(__LINE__); 235 char c = longnames[foff + i]; 236 if (c == 0) 237 break; 238 } 239 char* oname = cast(char*)Mem.check(malloc(i + 1)); 240 memcpy(oname, longnames + foff, i); 241 oname[i] = 0; 242 om.name = oname[0 .. i]; 243 //printf("\tname = '%s'\n", om.name); 244 } 245 else 246 { 247 /* Pick short name out of header 248 */ 249 char* oname = cast(char*)Mem.check(malloc(MSCOFF_OBJECT_NAME_SIZE)); 250 int i; 251 for (i = 0; 1; i++) 252 { 253 if (i == MSCOFF_OBJECT_NAME_SIZE) 254 return corrupt(__LINE__); 255 char c = header.object_name[i]; 256 if (c == '/') 257 { 258 oname[i] = 0; 259 break; 260 } 261 oname[i] = c; 262 } 263 om.name = oname[0 .. i]; 264 } 265 om.file_time = strtoul(cast(char*)header.file_time, &endptr, 10); 266 om.user_id = cast(uint)strtoul(cast(char*)header.user_id, &endptr, 10); 267 om.group_id = cast(uint)strtoul(cast(char*)header.group_id, &endptr, 10); 268 om.file_mode = cast(uint)strtoul(cast(char*)header.file_mode, &endptr, 8); 269 om.scan = 0; // don't scan object module for symbols 270 objmodules.push(om); 271 } 272 offset += size; 273 } 274 if (offset != buflen) 275 return corrupt(__LINE__); 276 /* Scan the library's symbol table, and insert it into our own. 277 * We use this instead of rescanning the object module, because 278 * the library's creator may have a different idea of what symbols 279 * go into the symbol table than we do. 280 * This is also probably faster. 281 */ 282 if (!slm) 283 return corrupt(__LINE__); 284 char* s = string_table; 285 for (uint i = 0; i < number_of_symbols; i++) 286 { 287 const(char)[] name = s.toDString(); 288 s += name.length + 1; 289 uint memi = indices[i] - 1; 290 if (memi >= number_of_members) 291 return corrupt(__LINE__); 292 uint moff = member_file_offsets[memi]; 293 for (size_t m = mstart; 1; m++) 294 { 295 if (m == objmodules.dim) 296 return corrupt(__LINE__); // didn't find it 297 MSCoffObjModule* om = objmodules[m]; 298 //printf("\tom offset = x%x\n", (char *)om.base - (char *)buf); 299 if (moff == cast(char*)om.base - cast(char*)buf) 300 { 301 addSymbol(om, name, 1); 302 //if (mstart == m) 303 // mstart++; 304 break; 305 } 306 } 307 } 308 return; 309 } 310 /* It's an object module 311 */ 312 auto om = new MSCoffObjModule(); 313 om.base = cast(ubyte*)buf; 314 om.length = cast(uint)buflen; 315 om.offset = 0; 316 // remove path, but not extension 317 om.name = global.params.preservePaths ? module_name : FileName.name(module_name); 318 om.scan = 1; 319 if (fromfile) 320 { 321 version (Posix) 322 stat_t statbuf; 323 version (Windows) 324 struct_stat statbuf; 325 int i = module_name.toCStringThen!(name => stat(name.ptr, &statbuf)); 326 if (i == -1) // error, errno is set 327 return corrupt(__LINE__); 328 om.file_time = statbuf.st_ctime; 329 om.user_id = statbuf.st_uid; 330 om.group_id = statbuf.st_gid; 331 om.file_mode = statbuf.st_mode; 332 } 333 else 334 { 335 /* Mock things up for the object module file that never was 336 * actually written out. 337 */ 338 version (Posix) 339 { 340 __gshared uid_t uid; 341 __gshared gid_t gid; 342 __gshared int _init; 343 if (!_init) 344 { 345 _init = 1; 346 uid = getuid(); 347 gid = getgid(); 348 } 349 om.user_id = uid; 350 om.group_id = gid; 351 } 352 version (Windows) 353 { 354 om.user_id = 0; // meaningless on Windows 355 om.group_id = 0; // meaningless on Windows 356 } 357 time_t file_time = 0; 358 time(&file_time); 359 om.file_time = cast(long)file_time; 360 om.file_mode = (1 << 15) | (6 << 6) | (4 << 3) | (4 << 0); // 0100644 361 } 362 objmodules.push(om); 363 } 364 365 /*****************************************************************************/ 366 367 void addSymbol(MSCoffObjModule* om, const(char)[] name, int pickAny = 0) 368 { 369 static if (LOG) 370 { 371 printf("LibMSCoff::addSymbol(%s, %s, %d)\n", om.name.ptr, name, pickAny); 372 } 373 auto os = new MSCoffObjSymbol(); 374 os.name = xarraydup(name); 375 os.om = om; 376 objsymbols.push(os); 377 } 378 379 private: 380 /************************************ 381 * Scan single object module for dictionary symbols. 382 * Send those symbols to LibMSCoff::addSymbol(). 383 */ 384 void scanObjModule(MSCoffObjModule* om) 385 { 386 static if (LOG) 387 { 388 printf("LibMSCoff::scanObjModule(%s)\n", om.name.ptr); 389 } 390 391 extern (D) void addSymbol(const(char)[] name, int pickAny) 392 { 393 this.addSymbol(om, name, pickAny); 394 } 395 396 scanMSCoffObjModule(&addSymbol, om.base[0 .. om.length], om.name.ptr, loc); 397 } 398 399 /*****************************************************************************/ 400 /*****************************************************************************/ 401 /********************************************** 402 * Create and write library to libbuf. 403 * The library consists of: 404 * !<arch>\n 405 * header 406 * 1st Linker Member 407 * Header 408 * 2nd Linker Member 409 * Header 410 * Longnames Member 411 * object modules... 412 */ 413 protected override void WriteLibToBuffer(OutBuffer* libbuf) 414 { 415 static if (LOG) 416 { 417 printf("LibElf::WriteLibToBuffer()\n"); 418 } 419 assert(MSCoffLibHeader.sizeof == 60); 420 /************* Scan Object Modules for Symbols ******************/ 421 for (size_t i = 0; i < objmodules.dim; i++) 422 { 423 MSCoffObjModule* om = objmodules[i]; 424 if (om.scan) 425 { 426 scanObjModule(om); 427 } 428 } 429 /************* Determine longnames size ******************/ 430 /* The longnames section is where we store long file names. 431 */ 432 uint noffset = 0; 433 for (size_t i = 0; i < objmodules.dim; i++) 434 { 435 MSCoffObjModule* om = objmodules[i]; 436 size_t len = om.name.length; 437 if (len >= MSCOFF_OBJECT_NAME_SIZE) 438 { 439 om.name_offset = noffset; 440 noffset += len + 1; 441 } 442 else 443 om.name_offset = -1; 444 } 445 static if (LOG) 446 { 447 printf("\tnoffset = x%x\n", noffset); 448 } 449 /************* Determine string table length ******************/ 450 size_t slength = 0; 451 for (size_t i = 0; i < objsymbols.dim; i++) 452 { 453 MSCoffObjSymbol* os = objsymbols[i]; 454 slength += os.name.length + 1; 455 } 456 /************* Offset of first module ***********************/ 457 size_t moffset = 8; // signature 458 size_t firstLinkerMemberOffset = moffset; 459 moffset += MSCoffLibHeader.sizeof + 4 + objsymbols.dim * 4 + slength; // 1st Linker Member 460 moffset += moffset & 1; 461 size_t secondLinkerMemberOffset = moffset; 462 moffset += MSCoffLibHeader.sizeof + 4 + objmodules.dim * 4 + 4 + objsymbols.dim * 2 + slength; 463 moffset += moffset & 1; 464 size_t LongnamesMemberOffset = moffset; 465 moffset += MSCoffLibHeader.sizeof + noffset; // Longnames Member size 466 static if (LOG) 467 { 468 printf("\tmoffset = x%x\n", moffset); 469 } 470 /************* Offset of each module *************************/ 471 for (size_t i = 0; i < objmodules.dim; i++) 472 { 473 MSCoffObjModule* om = objmodules[i]; 474 moffset += moffset & 1; 475 om.offset = cast(uint)moffset; 476 if (om.scan) 477 moffset += MSCoffLibHeader.sizeof + om.length; 478 else 479 moffset += om.length; 480 } 481 libbuf.reserve(moffset); 482 /************* Write the library ******************/ 483 libbuf.write("!<arch>\n"); 484 MSCoffObjModule om; 485 om.name_offset = -1; 486 om.base = null; 487 om.length = cast(uint)(4 + objsymbols.dim * 4 + slength); 488 om.offset = 8; 489 om.name = ""; 490 time_t file_time = 0; 491 .time(&file_time); 492 om.file_time = cast(long)file_time; 493 om.user_id = 0; 494 om.group_id = 0; 495 om.file_mode = 0; 496 /*** Write out First Linker Member ***/ 497 assert(libbuf.length == firstLinkerMemberOffset); 498 MSCoffLibHeader h; 499 MSCoffOmToHeader(&h, &om); 500 libbuf.write((&h)[0 .. 1]); 501 char[4] buf; 502 Port.writelongBE(cast(uint)objsymbols.dim, buf.ptr); 503 libbuf.write(buf[0 .. 4]); 504 // Sort objsymbols[] in module offset order 505 objsymbols.sort!(MSCoffObjSymbol.offset_pred); 506 uint lastoffset; 507 for (size_t i = 0; i < objsymbols.dim; i++) 508 { 509 MSCoffObjSymbol* os = objsymbols[i]; 510 //printf("objsymbols[%d] = '%s', offset = %u\n", i, os.name, os.om.offset); 511 if (i) 512 { 513 // Should be sorted in module order 514 assert(lastoffset <= os.om.offset); 515 } 516 lastoffset = os.om.offset; 517 Port.writelongBE(lastoffset, buf.ptr); 518 libbuf.write(buf[0 .. 4]); 519 } 520 for (size_t i = 0; i < objsymbols.dim; i++) 521 { 522 MSCoffObjSymbol* os = objsymbols[i]; 523 libbuf.writestring(os.name); 524 libbuf.writeByte(0); 525 } 526 /*** Write out Second Linker Member ***/ 527 if (libbuf.length & 1) 528 libbuf.writeByte('\n'); 529 assert(libbuf.length == secondLinkerMemberOffset); 530 om.length = cast(uint)(4 + objmodules.dim * 4 + 4 + objsymbols.dim * 2 + slength); 531 MSCoffOmToHeader(&h, &om); 532 libbuf.write((&h)[0 .. 1]); 533 Port.writelongLE(cast(uint)objmodules.dim, buf.ptr); 534 libbuf.write(buf[0 .. 4]); 535 for (size_t i = 0; i < objmodules.dim; i++) 536 { 537 MSCoffObjModule* om2 = objmodules[i]; 538 om2.index = cast(ushort)i; 539 Port.writelongLE(om2.offset, buf.ptr); 540 libbuf.write(buf[0 .. 4]); 541 } 542 Port.writelongLE(cast(uint)objsymbols.dim, buf.ptr); 543 libbuf.write(buf[0 .. 4]); 544 // Sort objsymbols[] in lexical order 545 objsymbols.sort!(MSCoffObjSymbol.name_pred); 546 for (size_t i = 0; i < objsymbols.dim; i++) 547 { 548 MSCoffObjSymbol* os = objsymbols[i]; 549 Port.writelongLE(os.om.index + 1, buf.ptr); 550 libbuf.write(buf[0 .. 2]); 551 } 552 for (size_t i = 0; i < objsymbols.dim; i++) 553 { 554 MSCoffObjSymbol* os = objsymbols[i]; 555 libbuf.writestring(os.name); 556 libbuf.writeByte(0); 557 } 558 /*** Write out longnames Member ***/ 559 if (libbuf.length & 1) 560 libbuf.writeByte('\n'); 561 //printf("libbuf %x longnames %x\n", (int)libbuf.length, (int)LongnamesMemberOffset); 562 assert(libbuf.length == LongnamesMemberOffset); 563 // header 564 memset(&h, ' ', MSCoffLibHeader.sizeof); 565 h.object_name[0] = '/'; 566 h.object_name[1] = '/'; 567 size_t len = sprintf(h.file_size.ptr, "%u", noffset); 568 assert(len < 10); 569 h.file_size[len] = ' '; 570 h.trailer[0] = '`'; 571 h.trailer[1] = '\n'; 572 libbuf.write((&h)[0 .. 1]); 573 for (size_t i = 0; i < objmodules.dim; i++) 574 { 575 MSCoffObjModule* om2 = objmodules[i]; 576 if (om2.name_offset >= 0) 577 { 578 libbuf.writestring(om2.name); 579 libbuf.writeByte(0); 580 } 581 } 582 /* Write out each of the object modules 583 */ 584 for (size_t i = 0; i < objmodules.dim; i++) 585 { 586 MSCoffObjModule* om2 = objmodules[i]; 587 if (libbuf.length & 1) 588 libbuf.writeByte('\n'); // module alignment 589 //printf("libbuf %x om %x\n", (int)libbuf.length, (int)om2.offset); 590 assert(libbuf.length == om2.offset); 591 if (om2.scan) 592 { 593 MSCoffOmToHeader(&h, om2); 594 libbuf.write((&h)[0 .. 1]); // module header 595 libbuf.write(om2.base[0 .. om2.length]); // module contents 596 } 597 else 598 { 599 // Header is included in om.base[0..length] 600 libbuf.write(om2.base[0 .. om2.length]); // module contents 601 } 602 } 603 static if (LOG) 604 { 605 printf("moffset = x%x, libbuf.length = x%x\n", cast(uint)moffset, cast(uint)libbuf.length); 606 } 607 assert(libbuf.length == moffset); 608 } 609 } 610 611 /*****************************************************************************/ 612 /*****************************************************************************/ 613 struct MSCoffObjModule 614 { 615 ubyte* base; // where are we holding it in memory 616 uint length; // in bytes 617 uint offset; // offset from start of library 618 ushort index; // index in Second Linker Member 619 const(char)[] name; // module name (file name) terminated with 0 620 int name_offset; // if not -1, offset into string table of name 621 long file_time; // file time 622 uint user_id; 623 uint group_id; 624 uint file_mode; 625 int scan; // 1 means scan for symbols 626 } 627 628 enum MSCOFF_OBJECT_NAME_SIZE = 16; 629 630 struct MSCoffLibHeader 631 { 632 char[MSCOFF_OBJECT_NAME_SIZE] object_name; 633 char[12] file_time; 634 char[6] user_id; 635 char[6] group_id; 636 char[8] file_mode; // in octal 637 char[10] file_size; 638 char[2] trailer; 639 } 640 641 extern (C++) void MSCoffOmToHeader(MSCoffLibHeader* h, MSCoffObjModule* om) 642 { 643 size_t len; 644 if (om.name_offset == -1) 645 { 646 len = om.name.length; 647 memcpy(h.object_name.ptr, om.name.ptr, len); 648 h.object_name[len] = '/'; 649 } 650 else 651 { 652 len = sprintf(h.object_name.ptr, "/%d", om.name_offset); 653 h.object_name[len] = ' '; 654 } 655 assert(len < MSCOFF_OBJECT_NAME_SIZE); 656 memset(h.object_name.ptr + len + 1, ' ', MSCOFF_OBJECT_NAME_SIZE - (len + 1)); 657 /* In the following sprintf's, don't worry if the trailing 0 658 * that sprintf writes goes off the end of the field. It will 659 * write into the next field, which we will promptly overwrite 660 * anyway. (So make sure to write the fields in ascending order.) 661 */ 662 len = sprintf(h.file_time.ptr, "%llu", cast(long)om.file_time); 663 assert(len <= 12); 664 memset(h.file_time.ptr + len, ' ', 12 - len); 665 // Match what MS tools do (set to all blanks) 666 memset(h.user_id.ptr, ' ', (h.user_id).sizeof); 667 memset(h.group_id.ptr, ' ', (h.group_id).sizeof); 668 len = sprintf(h.file_mode.ptr, "%o", om.file_mode); 669 assert(len <= 8); 670 memset(h.file_mode.ptr + len, ' ', 8 - len); 671 len = sprintf(h.file_size.ptr, "%u", om.length); 672 assert(len <= 10); 673 memset(h.file_size.ptr + len, ' ', 10 - len); 674 h.trailer[0] = '`'; 675 h.trailer[1] = '\n'; 676 }