1 /* 2 * Copyright (c) 1999, 2012, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 #include <stdlib.h> 27 #include <stdio.h> 28 #include <strings.h> 29 #include <time.h> 30 #include <limits.h> 31 #include <errno.h> 32 #include <stddef.h> 33 #include <sys/stat.h> 34 #include <sys/types.h> 35 #include <string.h> 36 #include <dirent.h> 37 #include <unistd.h> 38 #ifdef __solaris__ 39 #include <libscf.h> 40 #endif 41 42 #include "jvm.h" 43 44 #define SKIP_SPACE(p) while (*p == ' ' || *p == '\t') p++; 45 46 #if !defined(__solaris__) || defined(__sparcv9) || defined(amd64) 47 #define fileopen fopen 48 #define filegets fgets 49 #define fileclose fclose 50 #endif 51 52 #if defined(__linux__) || defined(_ALLBSD_SOURCE) 53 54 55 static const char *ETC_TIMEZONE_FILE = "/etc/timezone"; 56 static const char *ETC_SYSCONFIG_CLOCK_FILE = "/etc/sysconfig/clock"; 57 static const char *ZONEINFO_DIR = "/usr/share/zoneinfo"; 58 static const char *DEFAULT_ZONEINFO_FILE = "/etc/localtime"; 59 #else 60 static const char *SYS_INIT_FILE = "/etc/default/init"; 61 static const char *ZONEINFO_DIR = "/usr/share/lib/zoneinfo"; 62 static const char *DEFAULT_ZONEINFO_FILE = "/usr/share/lib/zoneinfo/localtime"; 63 #endif /*__linux__*/ 64 65 /* 66 * Returns a pointer to the zone ID portion of the given zoneinfo file 67 * name, or NULL if the given string doesn't contain "zoneinfo/". 68 */ 69 static char * 70 getZoneName(char *str) 71 { 72 static const char *zidir = "zoneinfo/"; 73 74 char *pos = strstr((const char *)str, zidir); 75 if (pos == NULL) { 76 return NULL; 77 } 78 return pos + strlen(zidir); 79 } 80 81 /* 82 * Returns a path name created from the given 'dir' and 'name' under 83 * UNIX. This function allocates memory for the pathname calling 84 * malloc(). NULL is returned if malloc() fails. 85 */ 86 static char * 87 getPathName(const char *dir, const char *name) { 88 char *path; 89 90 path = (char *) malloc(strlen(dir) + strlen(name) + 2); 91 if (path == NULL) { 92 return NULL; 93 } 94 return strcat(strcat(strcpy(path, dir), "/"), name); 95 } 96 97 /* 98 * Reads the entire contents of a file. The return value is negative if 99 * an error occurs. 100 */ 101 static int 102 readEntireFile(const char *file_name, char **contents) { 103 FILE *file = fopen(file_name, "re"); 104 if (!file) { 105 return -ENOENT; 106 } 107 108 char *p = NULL; 109 110 size_t allocated_size = 0; 111 size_t total_read = 0; 112 size_t bytes_read = -1; 113 114 for (;;) { 115 size_t room = allocated_size - total_read; 116 if (room == 0) { 117 allocated_size += 256; 118 char *temp_p = realloc(p, allocated_size); 119 if (temp_p == NULL) { 120 return -ENOMEM; 121 } 122 p = temp_p; 123 room = allocated_size - total_read; 124 } 125 126 bytes_read = fread(p+total_read, 1, room, file); 127 128 if (bytes_read < room) { 129 if (ferror(file) != 0) { 130 goto fail; 131 } 132 break; 133 } 134 135 total_read += bytes_read; 136 } 137 p[allocated_size-1] = '\0'; 138 139 fclose(file); 140 *contents = p; 141 return 0; 142 143 fail: 144 fclose(file); 145 return -1; 146 } 147 148 /* 149 * Reads the value of ZONE variable from the given clock file. 150 */ 151 static char* 152 readTimeZoneFromSysconfigClock(const char* path) { 153 154 /* 155 * the file is a shell-script like file with key/value pairs 156 * that looks like this: 157 * KEY="VALUE" 158 */ 159 160 char *contents = NULL; 161 int r = readEntireFile(path, &contents); 162 if (r < 0) { 163 return NULL; 164 } 165 166 char *start = NULL; 167 start = strstr(contents, "ZONE=\""); 168 if (start == NULL) { 169 goto fail; 170 } 171 start = start + strlen("ZONE=\""); 172 char *end; 173 end = strchr(start, '"'); 174 if (end == NULL) { 175 goto fail; 176 } 177 char *tz_name = strndup(start, (end-start)); 178 free(contents); 179 return tz_name; 180 181 fail: 182 free(contents); 183 return NULL; 184 } 185 186 /* 187 * Scans the specified directory and its subdirectories to find a 188 * zoneinfo file which has the same content as /etc/localtime on Linux 189 * or /usr/share/lib/zoneinfo/localtime on Solaris given in 'buf'. 190 * If file is symbolic link, then the contents it points to are in buf. 191 * Returns a zone ID if found, otherwise, NULL is returned. 192 */ 193 static char * 194 findZoneinfoFile(char *buf, size_t size, const char *dir) 195 { 196 DIR *dirp = NULL; 197 struct stat statbuf; 198 struct dirent *dp = NULL; 199 struct dirent *entry = NULL; 200 char *pathname = NULL; 201 int fd = -1; 202 char *dbuf = NULL; 203 char *tz = NULL; 204 205 dirp = opendir(dir); 206 if (dirp == NULL) { 207 return NULL; 208 } 209 210 entry = (struct dirent *) malloc((size_t) pathconf(dir, _PC_NAME_MAX)); 211 if (entry == NULL) { 212 (void) closedir(dirp); 213 return NULL; 214 } 215 216 #if defined(__linux__) || defined(MACOSX) || (defined(__solaris__) \ 217 && (defined(_POSIX_PTHREAD_SEMANTICS) || defined(_LP64))) 218 while (readdir_r(dirp, entry, &dp) == 0 && dp != NULL) { 219 #else 220 while ((dp = readdir_r(dirp, entry)) != NULL) { 221 #endif 222 223 /* 224 * Skip '.' and '..' (and possibly other .* files) 225 */ 226 if (dp->d_name[0] == '.') { 227 continue; 228 } 229 230 /* 231 * Skip "ROC", "posixrules", and "localtime". 232 */ 233 if ((strcmp(dp->d_name, "ROC") == 0) 234 || (strcmp(dp->d_name, "posixrules") == 0) 235 #ifdef __solaris__ 236 /* 237 * Skip the "src" and "tab" directories on Solaris. 238 */ 239 || (strcmp(dp->d_name, "src") == 0) 240 || (strcmp(dp->d_name, "tab") == 0) 241 #endif 242 || (strcmp(dp->d_name, "localtime") == 0)) { 243 continue; 244 } 245 246 pathname = getPathName(dir, dp->d_name); 247 if (pathname == NULL) { 248 break; 249 } 250 if (stat(pathname, &statbuf) == -1) { 251 break; 252 } 253 254 if (S_ISDIR(statbuf.st_mode)) { 255 tz = findZoneinfoFile(buf, size, pathname); 256 if (tz != NULL) { 257 break; 258 } 259 } else if (S_ISREG(statbuf.st_mode) && (size_t)statbuf.st_size == size) { 260 dbuf = (char *) malloc(size); 261 if (dbuf == NULL) { 262 break; 263 } 264 if ((fd = open(pathname, O_RDONLY)) == -1) { 265 break; 266 } 267 if (read(fd, dbuf, size) != (ssize_t) size) { 268 break; 269 } 270 if (memcmp(buf, dbuf, size) == 0) { 271 tz = getZoneName(pathname); 272 if (tz != NULL) { 273 tz = strdup(tz); 274 } 275 break; 276 } 277 free((void *) dbuf); 278 dbuf = NULL; 279 (void) close(fd); 280 fd = -1; 281 } 282 free((void *) pathname); 283 pathname = NULL; 284 } 285 286 if (entry != NULL) { 287 free((void *) entry); 288 } 289 if (dirp != NULL) { 290 (void) closedir(dirp); 291 } 292 if (pathname != NULL) { 293 free((void *) pathname); 294 } 295 if (fd != -1) { 296 (void) close(fd); 297 } 298 if (dbuf != NULL) { 299 free((void *) dbuf); 300 } 301 return tz; 302 } 303 304 #if defined(__linux__) || defined(MACOSX) 305 306 /* 307 * Performs Linux specific mapping and returns a zone ID 308 * if found. Otherwise, NULL is returned. 309 */ 310 static char * 311 getPlatformTimeZoneID() 312 { 313 struct stat statbuf; 314 char *tz = NULL; 315 FILE *fp; 316 int fd; 317 char *buf; 318 size_t size; 319 320 #ifdef __linux__ 321 /* 322 * Try reading the /etc/timezone file for Debian distros. There's 323 * no spec of the file format available. This parsing assumes that 324 * there's one line of an Olson tzid followed by a '\n', no 325 * leading or trailing spaces, no comments. 326 */ 327 if ((fp = fopen(ETC_TIMEZONE_FILE, "r")) != NULL) { 328 char line[256]; 329 330 if (fgets(line, sizeof(line), fp) != NULL) { 331 char *p = strchr(line, '\n'); 332 if (p != NULL) { 333 *p = '\0'; 334 } 335 if (strlen(line) > 0) { 336 tz = strdup(line); 337 } 338 } 339 (void) fclose(fp); 340 if (tz != NULL) { 341 return tz; 342 } 343 } 344 345 /* 346 * Try reading the /etc/sysconfig/clock file for Red Hat-like distros. 347 */ 348 if ((tz = readTimeZoneFromSysconfigClock(ETC_SYSCONFIG_CLOCK_FILE)) != NULL) { 349 return tz; 350 } 351 352 #endif /* __linux__ */ 353 354 /* 355 * Next, try /etc/localtime to find the zone ID. 356 */ 357 if (lstat(DEFAULT_ZONEINFO_FILE, &statbuf) == -1) { 358 return NULL; 359 } 360 361 /* 362 * If it's a symlink, get the link name and its zone ID part. (The 363 * older versions of timeconfig created a symlink as described in 364 * the Red Hat man page. It was changed in 1999 to create a copy 365 * of a zoneinfo file. It's no longer possible to get the zone ID 366 * from /etc/localtime.) 367 */ 368 if (S_ISLNK(statbuf.st_mode)) { 369 char linkbuf[PATH_MAX+1]; 370 int len; 371 372 if ((len = readlink(DEFAULT_ZONEINFO_FILE, linkbuf, sizeof(linkbuf)-1)) == -1) { 373 jio_fprintf(stderr, (const char *) "can't get a symlink of %s\n", 374 DEFAULT_ZONEINFO_FILE); 375 return NULL; 376 } 377 linkbuf[len] = '\0'; 378 tz = getZoneName(linkbuf); 379 if (tz != NULL) { 380 tz = strdup(tz); 381 return tz; 382 } 383 } 384 385 /* 386 * If it's a regular file, we need to find out the same zoneinfo file 387 * that has been copied as /etc/localtime. 388 * If initial symbolic link resolution failed, we should treat target 389 * file as a regular file. 390 */ 391 if ((fd = open(DEFAULT_ZONEINFO_FILE, O_RDONLY)) == -1) { 392 return NULL; 393 } 394 if (fstat(fd, &statbuf) == -1) { 395 (void) close(fd); 396 return NULL; 397 } 398 size = (size_t) statbuf.st_size; 399 buf = (char *) malloc(size); 400 if (buf == NULL) { 401 (void) close(fd); 402 return NULL; 403 } 404 405 if (read(fd, buf, size) != (ssize_t) size) { 406 (void) close(fd); 407 free((void *) buf); 408 return NULL; 409 } 410 (void) close(fd); 411 412 tz = findZoneinfoFile(buf, size, ZONEINFO_DIR); 413 free((void *) buf); 414 return tz; 415 } 416 #else 417 #ifdef __solaris__ 418 #if !defined(__sparcv9) && !defined(amd64) 419 420 /* 421 * Those file* functions mimic the UNIX stream io functions. This is 422 * because of the limitation of the number of open files on Solaris 423 * (32-bit mode only) due to the System V ABI. 424 */ 425 426 #define BUFFER_SIZE 4096 427 428 static struct iobuffer { 429 int magic; /* -1 to distinguish from the real FILE */ 430 int fd; /* file descriptor */ 431 char *buffer; /* pointer to buffer */ 432 char *ptr; /* current read pointer */ 433 char *endptr; /* end pointer */ 434 }; 435 436 static int 437 fileclose(FILE *stream) 438 { 439 struct iobuffer *iop = (struct iobuffer *) stream; 440 441 if (iop->magic != -1) { 442 return fclose(stream); 443 } 444 445 if (iop == NULL) { 446 return 0; 447 } 448 close(iop->fd); 449 free((void *)iop->buffer); 450 free((void *)iop); 451 return 0; 452 } 453 454 static FILE * 455 fileopen(const char *fname, const char *fmode) 456 { 457 FILE *fp; 458 int fd; 459 struct iobuffer *iop; 460 461 if ((fp = fopen(fname, fmode)) != NULL) { 462 return fp; 463 } 464 465 /* 466 * It assumes read open. 467 */ 468 if ((fd = open(fname, O_RDONLY)) == -1) { 469 return NULL; 470 } 471 472 /* 473 * Allocate struct iobuffer and its buffer 474 */ 475 iop = malloc(sizeof(struct iobuffer)); 476 if (iop == NULL) { 477 (void) close(fd); 478 errno = ENOMEM; 479 return NULL; 480 } 481 iop->magic = -1; 482 iop->fd = fd; 483 iop->buffer = malloc(BUFFER_SIZE); 484 if (iop->buffer == NULL) { 485 (void) close(fd); 486 free((void *) iop); 487 errno = ENOMEM; 488 return NULL; 489 } 490 iop->ptr = iop->buffer; 491 iop->endptr = iop->buffer; 492 return (FILE *)iop; 493 } 494 495 /* 496 * This implementation assumes that n is large enough and the line 497 * separator is '\n'. 498 */ 499 static char * 500 filegets(char *s, int n, FILE *stream) 501 { 502 struct iobuffer *iop = (struct iobuffer *) stream; 503 char *p; 504 505 if (iop->magic != -1) { 506 return fgets(s, n, stream); 507 } 508 509 p = s; 510 for (;;) { 511 char c; 512 513 if (iop->ptr == iop->endptr) { 514 ssize_t len; 515 516 if ((len = read(iop->fd, (void *)iop->buffer, BUFFER_SIZE)) == -1) { 517 return NULL; 518 } 519 if (len == 0) { 520 *p = 0; 521 if (s == p) { 522 return NULL; 523 } 524 return s; 525 } 526 iop->ptr = iop->buffer; 527 iop->endptr = iop->buffer + len; 528 } 529 c = *iop->ptr++; 530 *p++ = c; 531 if ((p - s) == (n - 1)) { 532 *p = 0; 533 return s; 534 } 535 if (c == '\n') { 536 *p = 0; 537 return s; 538 } 539 } 540 /*NOTREACHED*/ 541 } 542 #endif /* not __sparcv9 */ 543 544 545 /* 546 * Performs Solaris dependent mapping. Returns a zone ID if 547 * found. Otherwise, NULL is returned. Solaris libc looks up 548 * "/etc/default/init" to get the default TZ value if TZ is not defined 549 * as an environment variable. 550 */ 551 static char * 552 getPlatformTimeZoneID() 553 { 554 char *tz = NULL; 555 FILE *fp; 556 557 /* 558 * Try the TZ entry in /etc/default/init. 559 */ 560 if ((fp = fileopen(SYS_INIT_FILE, "r")) != NULL) { 561 char line[256]; 562 char quote = '\0'; 563 564 while (filegets(line, sizeof(line), fp) != NULL) { 565 char *p = line; 566 char *s; 567 char c; 568 569 /* quick check for comment lines */ 570 if (*p == '#') { 571 continue; 572 } 573 if (strncmp(p, "TZ=", 3) == 0) { 574 p += 3; 575 SKIP_SPACE(p); 576 c = *p; 577 if (c == '"' || c == '\'') { 578 quote = c; 579 p++; 580 } 581 582 /* 583 * PSARC/2001/383: quoted string support 584 */ 585 for (s = p; (c = *s) != '\0' && c != '\n'; s++) { 586 /* No '\\' is supported here. */ 587 if (c == quote) { 588 quote = '\0'; 589 break; 590 } 591 if (c == ' ' && quote == '\0') { 592 break; 593 } 594 } 595 if (quote != '\0') { 596 jio_fprintf(stderr, "ZoneInfo: unterminated time zone name in /etc/TIMEZONE\n"); 597 } 598 *s = '\0'; 599 tz = strdup(p); 600 break; 601 } 602 } 603 (void) fileclose(fp); 604 } 605 return tz; 606 } 607 608 #define TIMEZONE_FMRI "svc:/system/timezone:default" 609 #define TIMEZONE_PG "timezone" 610 #define LOCALTIME_PROP "localtime" 611 612 static void 613 cleanupScf(scf_handle_t *h, 614 scf_snapshot_t *snap, 615 scf_instance_t *inst, 616 scf_propertygroup_t *pg, 617 scf_property_t *prop, 618 scf_value_t *val, 619 char *buf) { 620 if (buf != NULL) { 621 free(buf); 622 } 623 if (snap != NULL) { 624 scf_snapshot_destroy(snap); 625 } 626 if (val != NULL) { 627 scf_value_destroy(val); 628 } 629 if (prop != NULL) { 630 scf_property_destroy(prop); 631 } 632 if (pg != NULL) { 633 scf_pg_destroy(pg); 634 } 635 if (inst != NULL) { 636 scf_instance_destroy(inst); 637 } 638 if (h != NULL) { 639 scf_handle_destroy(h); 640 } 641 } 642 643 /* 644 * Retruns a zone ID of Solaris when the TZ value is "localtime". 645 * First, it tries scf. If scf fails, it looks for the same file as 646 * /usr/share/lib/zoneinfo/localtime under /usr/share/lib/zoneinfo/. 647 */ 648 static char * 649 getSolarisDefaultZoneID() { 650 char *tz = NULL; 651 struct stat statbuf; 652 size_t size; 653 char *buf; 654 int fd; 655 /* scf specific variables */ 656 scf_handle_t *h = NULL; 657 scf_snapshot_t *snap = NULL; 658 scf_instance_t *inst = NULL; 659 scf_propertygroup_t *pg = NULL; 660 scf_property_t *prop = NULL; 661 scf_value_t *val = NULL; 662 663 if ((h = scf_handle_create(SCF_VERSION)) != NULL 664 && scf_handle_bind(h) == 0 665 && (inst = scf_instance_create(h)) != NULL 666 && (snap = scf_snapshot_create(h)) != NULL 667 && (pg = scf_pg_create(h)) != NULL 668 && (prop = scf_property_create(h)) != NULL 669 && (val = scf_value_create(h)) != NULL 670 && scf_handle_decode_fmri(h, TIMEZONE_FMRI, NULL, NULL, inst, 671 NULL, NULL, SCF_DECODE_FMRI_REQUIRE_INSTANCE) == 0 672 && scf_instance_get_snapshot(inst, "running", snap) == 0 673 && scf_instance_get_pg_composed(inst, snap, TIMEZONE_PG, pg) == 0 674 && scf_pg_get_property(pg, LOCALTIME_PROP, prop) == 0 675 && scf_property_get_value(prop, val) == 0) { 676 ssize_t len; 677 678 /* Gets the length of the zone ID string */ 679 len = scf_value_get_astring(val, NULL, 0); 680 if (len != -1) { 681 tz = malloc(++len); /* +1 for a null byte */ 682 if (tz != NULL && scf_value_get_astring(val, tz, len) != -1) { 683 cleanupScf(h, snap, inst, pg, prop, val, NULL); 684 return tz; 685 } 686 } 687 } 688 cleanupScf(h, snap, inst, pg, prop, val, tz); 689 690 if (stat(DEFAULT_ZONEINFO_FILE, &statbuf) == -1) { 691 return NULL; 692 } 693 size = (size_t) statbuf.st_size; 694 buf = malloc(size); 695 if (buf == NULL) { 696 return NULL; 697 } 698 if ((fd = open(DEFAULT_ZONEINFO_FILE, O_RDONLY)) == -1) { 699 free((void *) buf); 700 return NULL; 701 } 702 703 if (read(fd, buf, size) != (ssize_t) size) { 704 (void) close(fd); 705 free((void *) buf); 706 return NULL; 707 } 708 (void) close(fd); 709 tz = findZoneinfoFile(buf, size, ZONEINFO_DIR); 710 free((void *) buf); 711 return tz; 712 } 713 #endif /*__solaris__*/ 714 #endif /*__linux__*/ 715 716 /* 717 * findJavaTZ_md() maps platform time zone ID to Java time zone ID 718 * using <java_home>/lib/tzmappings. If the TZ value is not found, it 719 * trys some libc implementation dependent mappings. If it still 720 * can't map to a Java time zone ID, it falls back to the GMT+/-hh:mm 721 * form. `country', which can be null, is not used for UNIX platforms. 722 */ 723 /*ARGSUSED1*/ 724 char * 725 findJavaTZ_md(const char *java_home_dir, const char *country) 726 { 727 char *tz; 728 char *javatz = NULL; 729 char *freetz = NULL; 730 731 tz = getenv("TZ"); 732 733 #if defined(__linux__) || defined(_ALLBSD_SOURCE) 734 if (tz == NULL) { 735 #else 736 #ifdef __solaris__ 737 if (tz == NULL || *tz == '\0') { 738 #endif 739 #endif 740 tz = getPlatformTimeZoneID(); 741 freetz = tz; 742 } 743 744 /* 745 * Remove any preceding ':' 746 */ 747 if (tz != NULL && *tz == ':') { 748 tz++; 749 } 750 751 #ifdef __solaris__ 752 if (tz != NULL && strcmp(tz, "localtime") == 0) { 753 tz = getSolarisDefaultZoneID(); 754 freetz = tz; 755 } 756 #endif 757 758 if (tz != NULL) { 759 #ifdef __linux__ 760 /* 761 * Ignore "posix/" prefix. 762 */ 763 if (strncmp(tz, "posix/", 6) == 0) { 764 tz += 6; 765 } 766 #endif 767 javatz = strdup(tz); 768 if (freetz != NULL) { 769 free((void *) freetz); 770 } 771 } 772 return javatz; 773 } 774 /** 775 * Returns a GMT-offset-based zone ID. (e.g., "GMT-08:00") 776 */ 777 778 #ifdef MACOSX 779 780 char * 781 getGMTOffsetID() 782 { 783 time_t offset; 784 char sign, buf[32]; 785 struct tm *local_tm; 786 time_t clock; 787 time_t currenttime; 788 789 clock = time(NULL); 790 tzset(); 791 local_tm = localtime(&clock); 792 if (local_tm->tm_gmtoff >= 0) { 793 offset = (time_t) local_tm->tm_gmtoff; 794 sign = "+"; 795 } else { 796 offset = (time_t) -local_tm->tm_gmtoff; 797 sign = "-"; 798 } 799 sprintf(buf, (const char *)"GMT%c%02d:%02d", 800 sign, (int)(offset/3600), (int)((offset%3600)/60)); 801 return strdup(buf); 802 } 803 #else 804 805 char * 806 getGMTOffsetID() 807 { 808 time_t offset; 809 char sign, buf[32]; 810 #ifdef __solaris__ 811 struct tm localtm; 812 time_t currenttime; 813 814 currenttime = time(NULL); 815 if (localtime_r(¤ttime, &localtm) == NULL) { 816 return NULL; 817 } 818 819 offset = localtm.tm_isdst ? altzone : timezone; 820 #else 821 offset = timezone; 822 #endif /*__linux__*/ 823 824 if (offset == 0) { 825 return strdup("GMT"); 826 } 827 828 /* Note that the time offset direction is opposite. */ 829 if (offset > 0) { 830 sign = '-'; 831 } else { 832 offset = -offset; 833 sign = '+'; 834 } 835 sprintf(buf, (const char *)"GMT%c%02d:%02d", 836 sign, (int)(offset/3600), (int)((offset%3600)/60)); 837 return strdup(buf); 838 } 839 #endif /* MACOSX */