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