1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 2006 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 #pragma ident "%Z%%M% %I% %E% SMI" 28 29 /* 30 * The copyright in this file is taken from the original Leach & Salz 31 * UUID specification, from which this implementation is derived. 32 */ 33 34 /* 35 * Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. 36 * Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & 37 * Digital Equipment Corporation, Maynard, Mass. Copyright (c) 1998 38 * Microsoft. To anyone who acknowledges that this file is provided 39 * "AS IS" without any express or implied warranty: permission to use, 40 * copy, modify, and distribute this file for any purpose is hereby 41 * granted without fee, provided that the above copyright notices and 42 * this notice appears in all source code copies, and that none of the 43 * names of Open Software Foundation, Inc., Hewlett-Packard Company, 44 * or Digital Equipment Corporation be used in advertising or 45 * publicity pertaining to distribution of the software without 46 * specific, written prior permission. Neither Open Software 47 * Foundation, Inc., Hewlett-Packard Company, Microsoft, nor Digital 48 * Equipment Corporation makes any representations about the 49 * suitability of this software for any purpose. 50 */ 51 52 /* 53 * Module: uuid.c 54 * 55 * Description: This module is the workhorse for generating abstract 56 * UUIDs. It delegates system-specific tasks (such 57 * as obtaining the node identifier or system time) 58 * to the sysdep module. 59 */ 60 61 #include <ctype.h> 62 #include <sys/param.h> 63 #include <sys/stat.h> 64 #include <errno.h> 65 #include <stdio.h> 66 #include <stdlib.h> 67 #include <strings.h> 68 #include <fcntl.h> 69 #include <unistd.h> 70 #include <uuid/uuid.h> 71 #include <thread.h> 72 #include <synch.h> 73 #include "uuid_misc.h" 74 75 #define STATE_LOCATION "/var/sadm/system/uuid_state" 76 #define URANDOM_PATH "/dev/urandom" 77 #define MAX_RETRY 8 78 #define VER1_MASK 0xefff 79 80 static mutex_t ulock = DEFAULTMUTEX; 81 82 uint16_t _get_random(void); 83 void _get_current_time(uuid_time_t *); 84 void struct_to_string(uuid_t, struct uuid *); 85 void string_to_struct(struct uuid *, uuid_t); 86 int get_ethernet_address(uuid_node_t *); 87 88 /* 89 * local functions 90 */ 91 static int _lock_state(char *); 92 static void _unlock_state(int); 93 static void _read_state(int, uint16_t *, uuid_time_t *, 94 uuid_node_t *); 95 static int _write_state(int, uint16_t, uuid_time_t, uuid_node_t); 96 static void _format_uuid(struct uuid *, uint16_t, uuid_time_t, 97 uuid_node_t); 98 static void fill_random_bytes(uchar_t *, int); 99 static int uuid_create(struct uuid *); 100 101 static void gen_ethernet_address(uuid_node_t *); 102 /* 103 * Name: uuid_create. 104 * 105 * Description: Generates a uuid based on Version 1 format 106 * 107 * Returns: 0 on success, -1 on Error 108 */ 109 static int 110 uuid_create(struct uuid *uuid) 111 { 112 uuid_time_t timestamp, last_time; 113 uint16_t clockseq = 0; 114 uuid_node_t last_node; 115 uuid_node_t system_node; 116 int locked_state_fd; 117 int non_unique = 0; 118 119 if (mutex_lock(&ulock) != 0) { 120 return (-1); 121 } 122 123 gen_ethernet_address(&system_node); 124 /* 125 * acquire system wide lock so we're alone 126 */ 127 locked_state_fd = _lock_state(STATE_LOCATION); 128 if (locked_state_fd < 0) { 129 /* couldn't create and/or lock state; don't have access */ 130 non_unique++; 131 } else { 132 /* read saved state from disk */ 133 _read_state(locked_state_fd, &clockseq, &last_time, 134 &last_node); 135 } 136 137 if (clockseq == 0) { 138 /* couldn't read clock sequence; generate a random one */ 139 clockseq = _get_random(); 140 non_unique++; 141 } 142 if (memcmp(&system_node, &last_node, sizeof (uuid_node_t)) != 0) { 143 clockseq++; 144 } 145 146 /* 147 * get current time 148 */ 149 _get_current_time(×tamp); 150 151 /* 152 * If timestamp is not set or is not in the past, 153 * increment clock sequence. 154 */ 155 if ((last_time == 0) || (last_time >= timestamp)) { 156 clockseq++; 157 last_time = timestamp; 158 } 159 160 if (non_unique) 161 system_node.nodeID[0] |= 0x80; 162 /* 163 * stuff fields into the UUID 164 */ 165 _format_uuid(uuid, clockseq, timestamp, system_node); 166 if ((locked_state_fd >= 0) && 167 (_write_state(locked_state_fd, clockseq, timestamp, 168 system_node) == -1)) { 169 _unlock_state(locked_state_fd); 170 (void) mutex_unlock(&ulock); 171 return (-1); 172 } 173 /* 174 * Unlock system-wide lock 175 */ 176 _unlock_state(locked_state_fd); 177 (void) mutex_unlock(&ulock); 178 return (0); 179 } 180 181 /* 182 * Name: gen_ethernet_address 183 * 184 * Description: Fills system_node with Ethernet address if available, 185 * else fills random numbers 186 * 187 * Returns: Nothing 188 */ 189 static void 190 gen_ethernet_address(uuid_node_t *system_node) 191 { 192 uchar_t node[6]; 193 194 if (get_ethernet_address(system_node) != 0) { 195 fill_random_bytes(node, 6); 196 (void) memcpy(system_node->nodeID, node, 6); 197 /* 198 * use 8:0:20 with the multicast bit set 199 * to avoid namespace collisions. 200 */ 201 system_node->nodeID[0] = 0x88; 202 system_node->nodeID[1] = 0x00; 203 system_node->nodeID[2] = 0x20; 204 } 205 } 206 207 /* 208 * Name: _format_uuid 209 * 210 * Description: Formats a UUID, given the clock_seq timestamp, 211 * and node address. Fills in passed-in pointer with 212 * the resulting uuid. 213 * 214 * Returns: None. 215 */ 216 static void 217 _format_uuid(struct uuid *uuid, uint16_t clock_seq, 218 uuid_time_t timestamp, uuid_node_t node) 219 { 220 221 /* 222 * First set up the first 60 bits from the timestamp 223 */ 224 uuid->time_low = (uint32_t)(timestamp & 0xFFFFFFFF); 225 uuid->time_mid = (uint16_t)((timestamp >> 32) & 0xFFFF); 226 uuid->time_hi_and_version = (uint16_t)((timestamp >> 48) & 227 0x0FFF); 228 229 /* 230 * This is version 1, so say so in the UUID version field (4 bits) 231 */ 232 uuid->time_hi_and_version |= (1 << 12); 233 234 /* 235 * Now do the clock sequence 236 */ 237 uuid->clock_seq_low = clock_seq & 0xFF; 238 239 /* 240 * We must save the most-significant 2 bits for the reserved field 241 */ 242 uuid->clock_seq_hi_and_reserved = (clock_seq & 0x3F00) >> 8; 243 244 /* 245 * The variant for this format is the 2 high bits set to 10, 246 * so here it is 247 */ 248 uuid->clock_seq_hi_and_reserved |= 0x80; 249 250 /* 251 * write result to passed-in pointer 252 */ 253 (void) memcpy(&uuid->node_addr, &node, sizeof (uuid->node_addr)); 254 } 255 256 /* 257 * Name: _read_state 258 * 259 * Description: Reads non-volatile state from a (possibly) saved statefile. 260 * For each non-null pointer passed-in, the corresponding 261 * information from the statefile is filled in. 262 * the resulting uuid. 263 * 264 * Returns: Nothing. 265 */ 266 static void 267 _read_state(int fd, uint16_t *clockseq, 268 uuid_time_t *timestamp, uuid_node_t *node) 269 { 270 uuid_state_t vol_state; 271 272 bzero(node, sizeof (uuid_node_t)); 273 *timestamp = 0; 274 *clockseq = 0; 275 276 if (read(fd, &vol_state, sizeof (uuid_state_t)) < 277 sizeof (uuid_state_t)) { 278 /* This file is being accessed the first time */ 279 } 280 281 *node = vol_state.node; 282 *timestamp = vol_state.ts; 283 *clockseq = vol_state.cs; 284 } 285 286 287 /* 288 * Name: _write_state 289 * 290 * Description: Writes non-volatile state from the passed-in information. 291 * 292 * Returns: -1 on error, 0 otherwise. 293 */ 294 static int 295 _write_state(int fd, uint16_t clockseq, 296 uuid_time_t timestamp, uuid_node_t node) 297 { 298 uuid_state_t vol_state; 299 300 vol_state.cs = clockseq; 301 vol_state.ts = timestamp; 302 vol_state.node = node; 303 /* 304 * seek to beginning of file and write data 305 */ 306 if (lseek(fd, 0, SEEK_SET) != -1) { 307 if (write(fd, &vol_state, sizeof (uuid_state_t)) != -1) { 308 return (0); 309 } 310 } 311 return (-1); 312 } 313 314 315 316 /* 317 * Name: _uuid_print 318 * 319 * Description: Prints a nicely-formatted uuid to stdout. 320 * 321 * Returns: None. 322 * 323 */ 324 void 325 uuid_print(struct uuid u) 326 { 327 int i; 328 329 (void) printf("%8.8x-%4.4x-%4.4x-%2.2x%2.2x-", u.time_low, u.time_mid, 330 u.time_hi_and_version, u.clock_seq_hi_and_reserved, 331 u.clock_seq_low); 332 for (i = 0; i < 6; i++) 333 (void) printf("%2.2x", u.node_addr[i]); 334 (void) printf("\n"); 335 } 336 337 /* 338 * Name: _lock_state 339 * 340 * Description: Locks down the statefile, by first creating the file 341 * if it doesn't exist. 342 * 343 * Returns: A non-negative file descriptor referring to the locked 344 * state file, if it was able to be created and/or locked, 345 * or -1 otherwise. 346 */ 347 static int 348 _lock_state(char *loc) 349 { 350 int fd; 351 struct flock lock; 352 353 fd = open(loc, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR); 354 355 if (fd < 0) { 356 return (-1); 357 } 358 359 lock.l_type = F_WRLCK; 360 lock.l_start = 0; 361 lock.l_whence = SEEK_SET; 362 lock.l_len = 0; 363 364 if (fcntl(fd, F_SETLKW, &lock) == -1) { 365 /* 366 * File could not be locked, bail 367 */ 368 (void) close(fd); 369 return (-1); 370 } 371 return (fd); 372 } 373 374 /* 375 * Name: _unlock_state 376 * 377 * Description: Unlocks a locked statefile, and close()'s the file. 378 * 379 * Returns: Nothing. 380 */ 381 void 382 _unlock_state(int fd) 383 { 384 struct flock lock; 385 386 lock.l_type = F_UNLCK; 387 lock.l_start = 0; 388 lock.l_whence = SEEK_SET; 389 lock.l_len = 0; 390 391 (void) fcntl(fd, F_SETLK, &lock); 392 (void) close(fd); 393 } 394 395 /* 396 * Name: fill_random_bytes 397 * 398 * Description: fills buf with random numbers - nbytes is the number of bytes 399 * to fill-in. Tries to use /dev/urandom random number generator- 400 * if that fails for some reason, it retries MAX_RETRY times. If 401 * it still fails then it uses srand48(3C) 402 * 403 * Returns: Nothing. 404 */ 405 static void 406 fill_random_bytes(uchar_t *buf, int nbytes) 407 { 408 int i, fd, retries = 0; 409 410 fd = open(URANDOM_PATH, O_RDONLY); 411 if (fd >= 0) { 412 while (nbytes > 0) { 413 i = read(fd, buf, nbytes); 414 if ((i < 0) && (errno == EINTR)) { 415 continue; 416 } 417 if (i <= 0) { 418 if (retries++ == MAX_RETRY) 419 break; 420 continue; 421 } 422 nbytes -= i; 423 buf += i; 424 retries = 0; 425 } 426 if (nbytes == 0) { 427 (void) close(fd); 428 return; 429 } 430 } 431 for (i = 0; i < nbytes; i++) { 432 *buf++ = _get_random() & 0xFF; 433 } 434 if (fd >= 0) { 435 (void) close(fd); 436 } 437 } 438 439 /* 440 * Name: struct_to_string 441 * 442 * Description: Unpacks the structure members in "struct uuid" to a char 443 * string "uuid_t". 444 * 445 * Returns: Nothing. 446 */ 447 void 448 struct_to_string(uuid_t ptr, struct uuid *uu) 449 { 450 uint_t tmp; 451 uchar_t *out = ptr; 452 453 tmp = uu->time_low; 454 out[3] = (uchar_t)tmp; 455 tmp >>= 8; 456 out[2] = (uchar_t)tmp; 457 tmp >>= 8; 458 out[1] = (uchar_t)tmp; 459 tmp >>= 8; 460 out[0] = (uchar_t)tmp; 461 462 tmp = uu->time_mid; 463 out[5] = (uchar_t)tmp; 464 tmp >>= 8; 465 out[4] = (uchar_t)tmp; 466 467 tmp = uu->time_hi_and_version; 468 out[7] = (uchar_t)tmp; 469 tmp >>= 8; 470 out[6] = (uchar_t)tmp; 471 472 tmp = uu->clock_seq_hi_and_reserved; 473 out[8] = (uchar_t)tmp; 474 tmp = uu->clock_seq_low; 475 out[9] = (uchar_t)tmp; 476 477 (void) memcpy(out+10, uu->node_addr, 6); 478 479 } 480 481 /* 482 * Name: string_to_struct 483 * 484 * Description: Packs the values in the "uuid_t" string into "struct uuid". 485 * 486 * Returns: Nothing 487 */ 488 void 489 string_to_struct(struct uuid *uuid, uuid_t in) 490 { 491 492 uchar_t *ptr; 493 uint_t tmp; 494 495 ptr = in; 496 497 tmp = *ptr++; 498 tmp = (tmp << 8) | *ptr++; 499 tmp = (tmp << 8) | *ptr++; 500 tmp = (tmp << 8) | *ptr++; 501 uuid->time_low = tmp; 502 503 tmp = *ptr++; 504 tmp = (tmp << 8) | *ptr++; 505 uuid->time_mid = tmp; 506 507 tmp = *ptr++; 508 tmp = (tmp << 8) | *ptr++; 509 uuid->time_hi_and_version = tmp; 510 511 tmp = *ptr++; 512 uuid->clock_seq_hi_and_reserved = tmp; 513 514 tmp = *ptr++; 515 uuid->clock_seq_low = tmp; 516 517 (void) memcpy(uuid->node_addr, ptr, 6); 518 519 } 520 521 /* 522 * Name: uuid_generate_random 523 * 524 * Description: Generates UUID based on DCE Version 4 525 * 526 * Returns: Nothing. uu contains the newly generated UUID 527 */ 528 void 529 uuid_generate_random(uuid_t uu) 530 { 531 532 struct uuid uuid; 533 534 if (uu == NULL) 535 return; 536 537 (void) memset(uu, 0, sizeof (uuid_t)); 538 (void) memset(&uuid, 0, sizeof (struct uuid)); 539 540 fill_random_bytes(uu, sizeof (uuid_t)); 541 string_to_struct(&uuid, uu); 542 /* 543 * This is version 4, so say so in the UUID version field (4 bits) 544 */ 545 uuid.time_hi_and_version |= (1 << 14); 546 /* 547 * we don't want the bit 1 to be set also which is for version 1 548 */ 549 uuid.time_hi_and_version &= VER1_MASK; 550 551 /* 552 * The variant for this format is the 2 high bits set to 10, 553 * so here it is 554 */ 555 uuid.clock_seq_hi_and_reserved |= 0x80; 556 557 /* 558 * Set MSB of Ethernet address to 1 to indicate that it was generated 559 * randomly 560 */ 561 uuid.node_addr[0] |= 0x80; 562 struct_to_string(uu, &uuid); 563 } 564 565 /* 566 * Name: uuid_generate_time 567 * 568 * Description: Generates UUID based on DCE Version 1. 569 * 570 * Returns: Nothing. uu contains the newly generated UUID. 571 */ 572 void 573 uuid_generate_time(uuid_t uu) 574 { 575 struct uuid uuid; 576 577 if (uu == NULL) 578 return; 579 580 if (uuid_create(&uuid) == -1) { 581 uuid_generate_random(uu); 582 return; 583 } 584 struct_to_string(uu, &uuid); 585 } 586 587 /* 588 * Name: uuid_generate 589 * 590 * Description: Creates a new UUID. The uuid will be generated based on 591 * high-quality randomness from /dev/urandom, if available by 592 * calling uuid_generate_random. If it failed to generate UUID 593 * then uuid_generate will call uuid_generate_time. 594 * 595 * Returns: Nothing. uu contains the newly generated UUID. 596 */ 597 void 598 uuid_generate(uuid_t uu) 599 { 600 int fd; 601 602 if (uu == NULL) { 603 return; 604 } 605 fd = open(URANDOM_PATH, O_RDONLY); 606 if (fd >= 0) { 607 (void) close(fd); 608 uuid_generate_random(uu); 609 } else { 610 (void) uuid_generate_time(uu); 611 } 612 } 613 614 /* 615 * Name: uuid_copy 616 * 617 * Description: The uuid_copy function copies the UUID variable src to dst 618 * 619 * Returns: Nothing 620 */ 621 void 622 uuid_copy(uuid_t dst, uuid_t src) 623 { 624 625 (void) memcpy(dst, src, UUID_LEN); 626 } 627 628 /* 629 * Name: uuid_clear 630 * 631 * Description: The uuid_clear function sets the value of the supplied uuid 632 * variable uu, to the NULL value. 633 * 634 * Returns: Nothing 635 */ 636 void 637 uuid_clear(uuid_t uu) 638 { 639 (void) memset(uu, 0, UUID_LEN); 640 } 641 642 /* 643 * Name: uuid_unparse 644 * 645 * Description: This function converts the supplied UUID uu from the internal 646 * binary format into a 36-byte string (plus trailing null char) 647 * and stores this value in the character string pointed to by out 648 * 649 * Returns: Nothing. 650 */ 651 void 652 uuid_unparse(uuid_t uu, char *out) 653 { 654 struct uuid uuid; 655 uint16_t clock_seq; 656 char etheraddr[13]; 657 int index = 0, i; 658 659 /* basic sanity checking */ 660 if (uu == NULL) { 661 return; 662 } 663 664 /* XXX user should have allocated enough memory */ 665 /* 666 * if (strlen(out) < UUID_PRINTABLE_STRING_LENGTH) { 667 * return; 668 * } 669 */ 670 string_to_struct(&uuid, uu); 671 clock_seq = uuid.clock_seq_hi_and_reserved; 672 clock_seq = (clock_seq << 8) | uuid.clock_seq_low; 673 for (i = 0; i < 6; i++) { 674 (void) sprintf(ðeraddr[index++], "%.2x", uuid.node_addr[i]); 675 index++; 676 } 677 etheraddr[index] = '\0'; 678 679 (void) snprintf(out, 25, "%08x-%04x-%04x-%04x-", 680 uuid.time_low, uuid.time_mid, uuid.time_hi_and_version, 681 clock_seq); 682 (void) strlcat(out, etheraddr, UUID_PRINTABLE_STRING_LENGTH); 683 } 684 685 /* 686 * Name: uuid_is_null 687 * 688 * Description: The uuid_is_null function compares the value of the supplied 689 * UUID variable uu to the NULL value. If the value is equal 690 * to the NULL UUID, 1 is returned, otherwise 0 is returned. 691 * 692 * Returns: 0 if uu is NOT null, 1 if uu is NULL. 693 */ 694 int 695 uuid_is_null(uuid_t uu) 696 { 697 int i; 698 uuid_t null_uu; 699 700 (void) memset(null_uu, 0, sizeof (uuid_t)); 701 i = memcmp(uu, null_uu, sizeof (uuid_t)); 702 if (i == 0) { 703 /* uu is NULL uuid */ 704 return (1); 705 } else { 706 return (0); 707 } 708 } 709 710 /* 711 * Name: uuid_parse 712 * 713 * Description: uuid_parse converts the UUID string given by 'in' into the 714 * internal uuid_t format. The input UUID is a string of the form 715 * cefa7a9c-1dd2-11b2-8350-880020adbeef in printf(3C) format. 716 * Upon successfully parsing the input string, UUID is stored 717 * in the location pointed to by uu 718 * 719 * Returns: 0 if the UUID is successfully stored, -1 otherwise. 720 */ 721 int 722 uuid_parse(char *in, uuid_t uu) 723 { 724 725 char *ptr, buf[3]; 726 int i; 727 struct uuid uuid; 728 uint16_t clock_seq; 729 730 /* do some sanity checking */ 731 if ((strlen(in) != 36) || (uu == NULL) || (in[36] != '\0')) { 732 return (-1); 733 } 734 735 ptr = in; 736 for (i = 0; i < 36; i++, ptr++) { 737 if ((i == 8) || (i == 13) || (i == 18) || (i == 23)) { 738 if (*ptr != '-') { 739 return (-1); 740 } 741 } else { 742 if (!isxdigit(*ptr)) { 743 return (-1); 744 } 745 } 746 } 747 748 uuid.time_low = strtoul(in, NULL, 16); 749 uuid.time_mid = strtoul(in+9, NULL, 16); 750 uuid.time_hi_and_version = strtoul(in+14, NULL, 16); 751 clock_seq = strtoul(in+19, NULL, 16); 752 uuid.clock_seq_hi_and_reserved = (clock_seq & 0xFF00) >> 8; 753 uuid.clock_seq_low = (clock_seq & 0xFF); 754 755 ptr = in+24; 756 buf[2] = '\0'; 757 for (i = 0; i < 6; i++) { 758 buf[0] = *ptr++; 759 buf[1] = *ptr++; 760 uuid.node_addr[i] = strtoul(buf, NULL, 16); 761 } 762 struct_to_string(uu, &uuid); 763 return (0); 764 } 765 766 /* 767 * Name: uuid_time 768 * 769 * Description: uuid_time extracts the time at which the supplied UUID uu 770 * was created. This function can only extract the creation 771 * time for UUIDs created with the uuid_generate_time function. 772 * The time at which the UUID was created, in seconds and 773 * microseconds since the epoch is stored in the location 774 * pointed to by ret_tv. 775 * 776 * Returns: The time at which the UUID was created, in seconds since 777 * January 1, 1970 GMT (the epoch). -1 otherwise. 778 */ 779 time_t 780 uuid_time(uuid_t uu, struct timeval *ret_tv) 781 { 782 struct uuid uuid; 783 uint_t high; 784 struct timeval tv; 785 u_longlong_t clock_reg; 786 uint_t tmp; 787 uint8_t clk; 788 789 string_to_struct(&uuid, uu); 790 tmp = (uuid.time_hi_and_version & 0xF000) >> 12; 791 clk = uuid.clock_seq_hi_and_reserved; 792 793 /* check if uu is NULL, Version = 1 of DCE and Variant = 0b10x */ 794 if ((uu == NULL) || ((tmp & 0x01) != 0x01) || ((clk & 0x80) != 0x80)) { 795 return (-1); 796 } 797 high = uuid.time_mid | ((uuid.time_hi_and_version & 0xFFF) << 16); 798 clock_reg = uuid.time_low | ((u_longlong_t)high << 32); 799 800 clock_reg -= (((u_longlong_t)0x01B21DD2) << 32) + 0x13814000; 801 tv.tv_sec = clock_reg / 10000000; 802 tv.tv_usec = (clock_reg % 10000000) / 10; 803 804 if (ret_tv) { 805 *ret_tv = tv; 806 } 807 808 return (tv.tv_sec); 809 } 810