1 /* 2 * Copyright 2010 Nexenta Systems, Inc. All rights reserved. 3 * Copyright (c) 2009 The NetBSD Foundation, Inc. 4 * All rights reserved. 5 * 6 * This code is derived from software contributed to The NetBSD Foundation 7 * by Brian Ginsbach. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 19 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 20 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 21 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 22 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 * POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 #include "lint.h" 32 #include "file64.h" 33 #include <sys/types.h> 34 #include <errno.h> 35 #include <stdio.h> 36 #include <stdlib.h> 37 #include <string.h> 38 #include <time.h> 39 #include <unistd.h> 40 #include <sys/stat.h> 41 42 #define TMSENTINEL (-1) 43 44 45 /* 46 * getdate_err is set to one of the following values on error. 47 * 48 * 1 The DATEMSK environment variable is null or undefined. 49 * 2 The template file cannot be opened for reading. 50 * 3 Failed to get file status information. 51 * 4 Template file is not a regular file. 52 * 5 Encountered an error while reading the template file. 53 * 6 Cannot allocate memory. 54 * 7 Input string does not match any line in the template. 55 * 8 Input string is invalid (for example, February 31) or could not 56 * be represented in a time_t. 57 * 58 * Note that on Solaris, getdate_err is possibly a function, to account 59 * for reentrancy. See the code for getdate_err.c for details. 60 */ 61 62 63 #pragma weak _getdate = getdate 64 struct tm * 65 getdate(const char *str) 66 { 67 char *datemsk, *line, *rp; 68 FILE *fp; 69 struct stat sb; 70 static struct tm rtm, tmnow; 71 struct tm *tmp, *rtmp = &rtm; 72 time_t now; 73 char buf[514]; 74 75 if (((datemsk = getenv("DATEMSK")) == NULL) || *datemsk == '\0') { 76 getdate_err = 1; 77 return (NULL); 78 } 79 80 if (stat(datemsk, &sb) < 0) { 81 getdate_err = 3; 82 return (NULL); 83 } 84 85 if ((sb.st_mode & S_IFMT) != S_IFREG) { 86 getdate_err = 4; 87 return (NULL); 88 } 89 90 if ((fp = fopen(datemsk, "r")) == NULL) { 91 getdate_err = 2; 92 return (NULL); 93 } 94 95 /* loop through datemsk file */ 96 errno = 0; 97 rp = NULL; 98 99 /* 100 * The NetBSD implementation supports a rich flexible file format 101 * with embedded escapes, etc. We don't need any of that. Solaris 102 * just reads the template file and (undocumented!) requires that 103 * each line not exceed 512 bytes, using a fixed buffer. We could 104 * improve on that, but this may grow the stack unreasonably, so 105 * we keep it to the same 512 limit. Some day we can be smarter. 106 * (Note FreeBSD doesn't even have getdate(), and IMO nobody sane 107 * should be using this crufty API. strptime is better.) 108 */ 109 110 (void) memset(buf, 0, sizeof (buf)); 111 while ((line = fgets(buf, sizeof (buf), fp)) != NULL) { 112 /* 113 * If the buffer consumed the entire string, then 114 * the input line was too long. We just check to 115 * see if the 2nd to last byte is set. If it isn't, 116 * then we hit a null byte first, and the line is 117 * short enough. 118 */ 119 if (buf[sizeof (buf) - 2] != 0) { 120 getdate_err = 5; 121 (void) fclose(fp); 122 return (NULL); 123 } 124 /* initialize tmp with sentinels */ 125 rtm.tm_sec = rtm.tm_min = rtm.tm_hour = TMSENTINEL; 126 rtm.tm_mday = rtm.tm_mon = rtm.tm_year = TMSENTINEL; 127 rtm.tm_wday = rtm.tm_yday = rtm.tm_isdst = TMSENTINEL; 128 rp = strptime(str, line, rtmp); 129 if (rp != NULL) 130 break; 131 errno = 0; 132 } 133 if (errno != 0 || ferror(fp)) { 134 if (errno == ENOMEM) 135 getdate_err = 6; 136 else 137 getdate_err = 5; 138 (void) fclose(fp); 139 return (NULL); 140 } 141 if (feof(fp) || (rp != NULL && *rp != '\0')) { 142 getdate_err = 7; 143 return (NULL); 144 } 145 (void) fclose(fp); 146 147 (void) time(&now); 148 tmp = localtime(&now); 149 tmnow = *tmp; 150 151 /* 152 * This implementation does not accept setting the broken-down time 153 * to anything other than the localtime(). It is not possible to 154 * change the scanned timezone with %Z. 155 * 156 * Note IRIX and Solaris accept only the current zone for %Z. 157 * XXX Is there any implementation that matches the standard? 158 * XXX (Or am I reading the standard wrong?) 159 * 160 * Note: Neither XPG 6 (POSIX 2004) nor XPG 7 (POSIX 2008) 161 * requires strptime(3) support for %Z. 162 */ 163 164 /* 165 * Given only a weekday find the first matching weekday starting 166 * with the current weekday and moving into the future. 167 */ 168 if (rtm.tm_wday != TMSENTINEL && rtm.tm_year == TMSENTINEL && 169 rtm.tm_mon == TMSENTINEL && rtm.tm_mday == TMSENTINEL) { 170 rtm.tm_year = tmnow.tm_year; 171 rtm.tm_mon = tmnow.tm_mon; 172 rtm.tm_mday = tmnow.tm_mday + 173 (rtm.tm_wday - tmnow.tm_wday + 7) % 7; 174 } 175 176 /* 177 * Given only a month (and no year) find the first matching month 178 * starting with the current month and moving into the future. 179 */ 180 if (rtm.tm_mon != TMSENTINEL) { 181 if (rtm.tm_year == TMSENTINEL) { 182 rtm.tm_year = tmnow.tm_year + 183 ((rtm.tm_mon < tmnow.tm_mon)? 1 : 0); 184 } 185 if (rtm.tm_mday == TMSENTINEL) { 186 /* assume the first of the month */ 187 rtm.tm_mday = 1; 188 /* 189 * XXX This isn't documented! Just observed behavior. 190 * 191 * Given the weekday find the first matching weekday 192 * starting with the weekday of the first day of the 193 * the month and moving into the future. 194 */ 195 if (rtm.tm_wday != TMSENTINEL) { 196 struct tm tm; 197 198 (void) memset(&tm, 0, sizeof (struct tm)); 199 tm.tm_year = rtm.tm_year; 200 tm.tm_mon = rtm.tm_mon; 201 tm.tm_mday = 1; 202 (void) mktime(&tm); 203 rtm.tm_mday += 204 (rtm.tm_wday - tm.tm_wday + 7) % 7; 205 } 206 } 207 } 208 209 /* 210 * Given no time of day assume the current time of day. 211 */ 212 if (rtm.tm_hour == TMSENTINEL && 213 rtm.tm_min == TMSENTINEL && rtm.tm_sec == TMSENTINEL) { 214 rtm.tm_hour = tmnow.tm_hour; 215 rtm.tm_min = tmnow.tm_min; 216 rtm.tm_sec = tmnow.tm_sec; 217 } 218 /* 219 * Given an hour and no date, find the first matching hour starting 220 * with the current hour and moving into the future 221 */ 222 if (rtm.tm_hour != TMSENTINEL && 223 rtm.tm_year == TMSENTINEL && rtm.tm_mon == TMSENTINEL && 224 rtm.tm_mday == TMSENTINEL) { 225 rtm.tm_year = tmnow.tm_year; 226 rtm.tm_mon = tmnow.tm_mon; 227 rtm.tm_mday = tmnow.tm_mday; 228 if (rtm.tm_hour < tmnow.tm_hour) 229 rtm.tm_hour += 24; 230 } 231 232 /* 233 * Set to 'sane' values; mktime(3) does funny things otherwise. 234 * No hours, no minutes, no seconds, no service. 235 */ 236 if (rtm.tm_hour == TMSENTINEL) 237 rtm.tm_hour = 0; 238 if (rtm.tm_min == TMSENTINEL) 239 rtm.tm_min = 0; 240 if (rtm.tm_sec == TMSENTINEL) 241 rtm.tm_sec = 0; 242 243 /* 244 * Given only a year the values of month, day of month, day of year, 245 * week day and is daylight (summer) time are unspecified. 246 * (Specified on the Solaris man page not POSIX.) 247 */ 248 if (rtm.tm_year != TMSENTINEL && 249 rtm.tm_mon == TMSENTINEL && rtm.tm_mday == TMSENTINEL) { 250 rtm.tm_mon = 0; 251 rtm.tm_mday = 1; 252 /* 253 * XXX More undocumented functionality but observed. 254 * 255 * Given the weekday find the first matching weekday 256 * starting with the weekday of the first day of the 257 * month and moving into the future. 258 */ 259 if (rtm.tm_wday != TMSENTINEL) { 260 struct tm tm; 261 262 (void) memset(&tm, 0, sizeof (struct tm)); 263 tm.tm_year = rtm.tm_year; 264 tm.tm_mon = rtm.tm_mon; 265 tm.tm_mday = 1; 266 (void) mktime(&tm); 267 rtm.tm_mday += (rtm.tm_wday - tm.tm_wday + 7) % 7; 268 } 269 } 270 271 /* 272 * Given only the century but no year within, the current year 273 * is assumed. (Specified on the Solaris man page not POSIX.) 274 * 275 * Warning ugly end case 276 * 277 * This is more work since strptime(3) doesn't "do the right thing". 278 */ 279 if (rtm.tm_year != TMSENTINEL && (rtm.tm_year - 1900) >= 0) { 280 rtm.tm_year -= 1900; 281 rtm.tm_year += (tmnow.tm_year % 100); 282 } 283 284 /* 285 * mktime() will normalize all values and also check that the 286 * value will fit into a time_t. 287 * 288 * This is only for POSIX correctness. A date >= 1900 is 289 * really ok, but using a time_t limits things. 290 */ 291 if (mktime(rtmp) < 0) { 292 getdate_err = 8; 293 return (NULL); 294 } 295 296 return (rtmp); 297 } 298