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