14297a3b0SGarrett D'Amore /*
2*6b5e5868SGarrett D'Amore * Copyright 2010 Nexenta Systems, Inc. All rights reserved.
34297a3b0SGarrett D'Amore * Copyright (c) 2009 The NetBSD Foundation, Inc.
44297a3b0SGarrett D'Amore * All rights reserved.
54297a3b0SGarrett D'Amore *
64297a3b0SGarrett D'Amore * This code is derived from software contributed to The NetBSD Foundation
74297a3b0SGarrett D'Amore * by Brian Ginsbach.
84297a3b0SGarrett D'Amore *
94297a3b0SGarrett D'Amore * Redistribution and use in source and binary forms, with or without
104297a3b0SGarrett D'Amore * modification, are permitted provided that the following conditions
114297a3b0SGarrett D'Amore * are met:
124297a3b0SGarrett D'Amore * 1. Redistributions of source code must retain the above copyright
134297a3b0SGarrett D'Amore * notice, this list of conditions and the following disclaimer.
144297a3b0SGarrett D'Amore * 2. Redistributions in binary form must reproduce the above copyright
154297a3b0SGarrett D'Amore * notice, this list of conditions and the following disclaimer in the
164297a3b0SGarrett D'Amore * documentation and/or other materials provided with the distribution.
174297a3b0SGarrett D'Amore *
184297a3b0SGarrett D'Amore * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
194297a3b0SGarrett D'Amore * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
204297a3b0SGarrett D'Amore * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
214297a3b0SGarrett D'Amore * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
224297a3b0SGarrett D'Amore * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
234297a3b0SGarrett D'Amore * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
244297a3b0SGarrett D'Amore * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
254297a3b0SGarrett D'Amore * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
264297a3b0SGarrett D'Amore * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
274297a3b0SGarrett D'Amore * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
284297a3b0SGarrett D'Amore * POSSIBILITY OF SUCH DAMAGE.
294297a3b0SGarrett D'Amore */
304297a3b0SGarrett D'Amore
314297a3b0SGarrett D'Amore #include "lint.h"
324297a3b0SGarrett D'Amore #include "file64.h"
334297a3b0SGarrett D'Amore #include <sys/types.h>
344297a3b0SGarrett D'Amore #include <errno.h>
354297a3b0SGarrett D'Amore #include <stdio.h>
364297a3b0SGarrett D'Amore #include <stdlib.h>
374297a3b0SGarrett D'Amore #include <string.h>
384297a3b0SGarrett D'Amore #include <time.h>
394297a3b0SGarrett D'Amore #include <unistd.h>
404297a3b0SGarrett D'Amore #include <sys/stat.h>
414297a3b0SGarrett D'Amore
424297a3b0SGarrett D'Amore #define TMSENTINEL (-1)
434297a3b0SGarrett D'Amore
444297a3b0SGarrett D'Amore
454297a3b0SGarrett D'Amore /*
464297a3b0SGarrett D'Amore * getdate_err is set to one of the following values on error.
474297a3b0SGarrett D'Amore *
484297a3b0SGarrett D'Amore * 1 The DATEMSK environment variable is null or undefined.
494297a3b0SGarrett D'Amore * 2 The template file cannot be opened for reading.
504297a3b0SGarrett D'Amore * 3 Failed to get file status information.
514297a3b0SGarrett D'Amore * 4 Template file is not a regular file.
524297a3b0SGarrett D'Amore * 5 Encountered an error while reading the template file.
534297a3b0SGarrett D'Amore * 6 Cannot allocate memory.
544297a3b0SGarrett D'Amore * 7 Input string does not match any line in the template.
554297a3b0SGarrett D'Amore * 8 Input string is invalid (for example, February 31) or could not
564297a3b0SGarrett D'Amore * be represented in a time_t.
574297a3b0SGarrett D'Amore *
584297a3b0SGarrett D'Amore * Note that on Solaris, getdate_err is possibly a function, to account
594297a3b0SGarrett D'Amore * for reentrancy. See the code for getdate_err.c for details.
604297a3b0SGarrett D'Amore */
614297a3b0SGarrett D'Amore
624297a3b0SGarrett D'Amore
634297a3b0SGarrett D'Amore #pragma weak _getdate = getdate
644297a3b0SGarrett D'Amore struct tm *
getdate(const char * str)654297a3b0SGarrett D'Amore getdate(const char *str)
664297a3b0SGarrett D'Amore {
674297a3b0SGarrett D'Amore char *datemsk, *line, *rp;
684297a3b0SGarrett D'Amore FILE *fp;
694297a3b0SGarrett D'Amore struct stat sb;
704297a3b0SGarrett D'Amore static struct tm rtm, tmnow;
714297a3b0SGarrett D'Amore struct tm *tmp, *rtmp = &rtm;
724297a3b0SGarrett D'Amore time_t now;
734297a3b0SGarrett D'Amore char buf[514];
744297a3b0SGarrett D'Amore
754297a3b0SGarrett D'Amore if (((datemsk = getenv("DATEMSK")) == NULL) || *datemsk == '\0') {
764297a3b0SGarrett D'Amore getdate_err = 1;
774297a3b0SGarrett D'Amore return (NULL);
784297a3b0SGarrett D'Amore }
794297a3b0SGarrett D'Amore
804297a3b0SGarrett D'Amore if (stat(datemsk, &sb) < 0) {
814297a3b0SGarrett D'Amore getdate_err = 3;
824297a3b0SGarrett D'Amore return (NULL);
834297a3b0SGarrett D'Amore }
844297a3b0SGarrett D'Amore
854297a3b0SGarrett D'Amore if ((sb.st_mode & S_IFMT) != S_IFREG) {
864297a3b0SGarrett D'Amore getdate_err = 4;
874297a3b0SGarrett D'Amore return (NULL);
884297a3b0SGarrett D'Amore }
894297a3b0SGarrett D'Amore
904297a3b0SGarrett D'Amore if ((fp = fopen(datemsk, "r")) == NULL) {
914297a3b0SGarrett D'Amore getdate_err = 2;
924297a3b0SGarrett D'Amore return (NULL);
934297a3b0SGarrett D'Amore }
944297a3b0SGarrett D'Amore
954297a3b0SGarrett D'Amore /* loop through datemsk file */
964297a3b0SGarrett D'Amore errno = 0;
974297a3b0SGarrett D'Amore rp = NULL;
984297a3b0SGarrett D'Amore
994297a3b0SGarrett D'Amore /*
1004297a3b0SGarrett D'Amore * The NetBSD implementation supports a rich flexible file format
1014297a3b0SGarrett D'Amore * with embedded escapes, etc. We don't need any of that. Solaris
1024297a3b0SGarrett D'Amore * just reads the template file and (undocumented!) requires that
1034297a3b0SGarrett D'Amore * each line not exceed 512 bytes, using a fixed buffer. We could
1044297a3b0SGarrett D'Amore * improve on that, but this may grow the stack unreasonably, so
1054297a3b0SGarrett D'Amore * we keep it to the same 512 limit. Some day we can be smarter.
1064297a3b0SGarrett D'Amore * (Note FreeBSD doesn't even have getdate(), and IMO nobody sane
1074297a3b0SGarrett D'Amore * should be using this crufty API. strptime is better.)
1084297a3b0SGarrett D'Amore */
1094297a3b0SGarrett D'Amore
1104297a3b0SGarrett D'Amore (void) memset(buf, 0, sizeof (buf));
1114297a3b0SGarrett D'Amore while ((line = fgets(buf, sizeof (buf), fp)) != NULL) {
1124297a3b0SGarrett D'Amore /*
1134297a3b0SGarrett D'Amore * If the buffer consumed the entire string, then
1144297a3b0SGarrett D'Amore * the input line was too long. We just check to
1154297a3b0SGarrett D'Amore * see if the 2nd to last byte is set. If it isn't,
1164297a3b0SGarrett D'Amore * then we hit a null byte first, and the line is
1174297a3b0SGarrett D'Amore * short enough.
1184297a3b0SGarrett D'Amore */
1194297a3b0SGarrett D'Amore if (buf[sizeof (buf) - 2] != 0) {
1204297a3b0SGarrett D'Amore getdate_err = 5;
1214297a3b0SGarrett D'Amore (void) fclose(fp);
1224297a3b0SGarrett D'Amore return (NULL);
1234297a3b0SGarrett D'Amore }
1244297a3b0SGarrett D'Amore /* initialize tmp with sentinels */
1254297a3b0SGarrett D'Amore rtm.tm_sec = rtm.tm_min = rtm.tm_hour = TMSENTINEL;
1264297a3b0SGarrett D'Amore rtm.tm_mday = rtm.tm_mon = rtm.tm_year = TMSENTINEL;
1274297a3b0SGarrett D'Amore rtm.tm_wday = rtm.tm_yday = rtm.tm_isdst = TMSENTINEL;
1284297a3b0SGarrett D'Amore rp = strptime(str, line, rtmp);
1294297a3b0SGarrett D'Amore if (rp != NULL)
1304297a3b0SGarrett D'Amore break;
1314297a3b0SGarrett D'Amore errno = 0;
1324297a3b0SGarrett D'Amore }
1334297a3b0SGarrett D'Amore if (errno != 0 || ferror(fp)) {
1344297a3b0SGarrett D'Amore if (errno == ENOMEM)
1354297a3b0SGarrett D'Amore getdate_err = 6;
1364297a3b0SGarrett D'Amore else
1374297a3b0SGarrett D'Amore getdate_err = 5;
1384297a3b0SGarrett D'Amore (void) fclose(fp);
1394297a3b0SGarrett D'Amore return (NULL);
1404297a3b0SGarrett D'Amore }
1414297a3b0SGarrett D'Amore if (feof(fp) || (rp != NULL && *rp != '\0')) {
1424297a3b0SGarrett D'Amore getdate_err = 7;
1434297a3b0SGarrett D'Amore return (NULL);
1444297a3b0SGarrett D'Amore }
1454297a3b0SGarrett D'Amore (void) fclose(fp);
1464297a3b0SGarrett D'Amore
1474297a3b0SGarrett D'Amore (void) time(&now);
1484297a3b0SGarrett D'Amore tmp = localtime(&now);
1494297a3b0SGarrett D'Amore tmnow = *tmp;
1504297a3b0SGarrett D'Amore
1514297a3b0SGarrett D'Amore /*
1524297a3b0SGarrett D'Amore * This implementation does not accept setting the broken-down time
1534297a3b0SGarrett D'Amore * to anything other than the localtime(). It is not possible to
1544297a3b0SGarrett D'Amore * change the scanned timezone with %Z.
1554297a3b0SGarrett D'Amore *
1564297a3b0SGarrett D'Amore * Note IRIX and Solaris accept only the current zone for %Z.
1574297a3b0SGarrett D'Amore * XXX Is there any implementation that matches the standard?
1584297a3b0SGarrett D'Amore * XXX (Or am I reading the standard wrong?)
1594297a3b0SGarrett D'Amore *
1604297a3b0SGarrett D'Amore * Note: Neither XPG 6 (POSIX 2004) nor XPG 7 (POSIX 2008)
1614297a3b0SGarrett D'Amore * requires strptime(3) support for %Z.
1624297a3b0SGarrett D'Amore */
1634297a3b0SGarrett D'Amore
1644297a3b0SGarrett D'Amore /*
1654297a3b0SGarrett D'Amore * Given only a weekday find the first matching weekday starting
1664297a3b0SGarrett D'Amore * with the current weekday and moving into the future.
1674297a3b0SGarrett D'Amore */
1684297a3b0SGarrett D'Amore if (rtm.tm_wday != TMSENTINEL && rtm.tm_year == TMSENTINEL &&
1694297a3b0SGarrett D'Amore rtm.tm_mon == TMSENTINEL && rtm.tm_mday == TMSENTINEL) {
1704297a3b0SGarrett D'Amore rtm.tm_year = tmnow.tm_year;
1714297a3b0SGarrett D'Amore rtm.tm_mon = tmnow.tm_mon;
1724297a3b0SGarrett D'Amore rtm.tm_mday = tmnow.tm_mday +
1734297a3b0SGarrett D'Amore (rtm.tm_wday - tmnow.tm_wday + 7) % 7;
1744297a3b0SGarrett D'Amore }
1754297a3b0SGarrett D'Amore
1764297a3b0SGarrett D'Amore /*
1774297a3b0SGarrett D'Amore * Given only a month (and no year) find the first matching month
1784297a3b0SGarrett D'Amore * starting with the current month and moving into the future.
1794297a3b0SGarrett D'Amore */
1804297a3b0SGarrett D'Amore if (rtm.tm_mon != TMSENTINEL) {
1814297a3b0SGarrett D'Amore if (rtm.tm_year == TMSENTINEL) {
1824297a3b0SGarrett D'Amore rtm.tm_year = tmnow.tm_year +
1834297a3b0SGarrett D'Amore ((rtm.tm_mon < tmnow.tm_mon)? 1 : 0);
1844297a3b0SGarrett D'Amore }
1854297a3b0SGarrett D'Amore if (rtm.tm_mday == TMSENTINEL) {
1864297a3b0SGarrett D'Amore /* assume the first of the month */
1874297a3b0SGarrett D'Amore rtm.tm_mday = 1;
1884297a3b0SGarrett D'Amore /*
1894297a3b0SGarrett D'Amore * XXX This isn't documented! Just observed behavior.
1904297a3b0SGarrett D'Amore *
1914297a3b0SGarrett D'Amore * Given the weekday find the first matching weekday
1924297a3b0SGarrett D'Amore * starting with the weekday of the first day of the
1934297a3b0SGarrett D'Amore * the month and moving into the future.
1944297a3b0SGarrett D'Amore */
1954297a3b0SGarrett D'Amore if (rtm.tm_wday != TMSENTINEL) {
1964297a3b0SGarrett D'Amore struct tm tm;
1974297a3b0SGarrett D'Amore
1984297a3b0SGarrett D'Amore (void) memset(&tm, 0, sizeof (struct tm));
1994297a3b0SGarrett D'Amore tm.tm_year = rtm.tm_year;
2004297a3b0SGarrett D'Amore tm.tm_mon = rtm.tm_mon;
2014297a3b0SGarrett D'Amore tm.tm_mday = 1;
2024297a3b0SGarrett D'Amore (void) mktime(&tm);
2034297a3b0SGarrett D'Amore rtm.tm_mday +=
2044297a3b0SGarrett D'Amore (rtm.tm_wday - tm.tm_wday + 7) % 7;
2054297a3b0SGarrett D'Amore }
2064297a3b0SGarrett D'Amore }
2074297a3b0SGarrett D'Amore }
2084297a3b0SGarrett D'Amore
2094297a3b0SGarrett D'Amore /*
2104297a3b0SGarrett D'Amore * Given no time of day assume the current time of day.
2114297a3b0SGarrett D'Amore */
2124297a3b0SGarrett D'Amore if (rtm.tm_hour == TMSENTINEL &&
2134297a3b0SGarrett D'Amore rtm.tm_min == TMSENTINEL && rtm.tm_sec == TMSENTINEL) {
2144297a3b0SGarrett D'Amore rtm.tm_hour = tmnow.tm_hour;
2154297a3b0SGarrett D'Amore rtm.tm_min = tmnow.tm_min;
2164297a3b0SGarrett D'Amore rtm.tm_sec = tmnow.tm_sec;
2174297a3b0SGarrett D'Amore }
2184297a3b0SGarrett D'Amore /*
2194297a3b0SGarrett D'Amore * Given an hour and no date, find the first matching hour starting
2204297a3b0SGarrett D'Amore * with the current hour and moving into the future
2214297a3b0SGarrett D'Amore */
2224297a3b0SGarrett D'Amore if (rtm.tm_hour != TMSENTINEL &&
2234297a3b0SGarrett D'Amore rtm.tm_year == TMSENTINEL && rtm.tm_mon == TMSENTINEL &&
2244297a3b0SGarrett D'Amore rtm.tm_mday == TMSENTINEL) {
2254297a3b0SGarrett D'Amore rtm.tm_year = tmnow.tm_year;
2264297a3b0SGarrett D'Amore rtm.tm_mon = tmnow.tm_mon;
2274297a3b0SGarrett D'Amore rtm.tm_mday = tmnow.tm_mday;
2284297a3b0SGarrett D'Amore if (rtm.tm_hour < tmnow.tm_hour)
2294297a3b0SGarrett D'Amore rtm.tm_hour += 24;
2304297a3b0SGarrett D'Amore }
2314297a3b0SGarrett D'Amore
2324297a3b0SGarrett D'Amore /*
2334297a3b0SGarrett D'Amore * Set to 'sane' values; mktime(3) does funny things otherwise.
2344297a3b0SGarrett D'Amore * No hours, no minutes, no seconds, no service.
2354297a3b0SGarrett D'Amore */
2364297a3b0SGarrett D'Amore if (rtm.tm_hour == TMSENTINEL)
2374297a3b0SGarrett D'Amore rtm.tm_hour = 0;
2384297a3b0SGarrett D'Amore if (rtm.tm_min == TMSENTINEL)
2394297a3b0SGarrett D'Amore rtm.tm_min = 0;
2404297a3b0SGarrett D'Amore if (rtm.tm_sec == TMSENTINEL)
2414297a3b0SGarrett D'Amore rtm.tm_sec = 0;
2424297a3b0SGarrett D'Amore
2434297a3b0SGarrett D'Amore /*
2444297a3b0SGarrett D'Amore * Given only a year the values of month, day of month, day of year,
2454297a3b0SGarrett D'Amore * week day and is daylight (summer) time are unspecified.
2464297a3b0SGarrett D'Amore * (Specified on the Solaris man page not POSIX.)
2474297a3b0SGarrett D'Amore */
2484297a3b0SGarrett D'Amore if (rtm.tm_year != TMSENTINEL &&
2494297a3b0SGarrett D'Amore rtm.tm_mon == TMSENTINEL && rtm.tm_mday == TMSENTINEL) {
2504297a3b0SGarrett D'Amore rtm.tm_mon = 0;
2514297a3b0SGarrett D'Amore rtm.tm_mday = 1;
2524297a3b0SGarrett D'Amore /*
2534297a3b0SGarrett D'Amore * XXX More undocumented functionality but observed.
2544297a3b0SGarrett D'Amore *
2554297a3b0SGarrett D'Amore * Given the weekday find the first matching weekday
2564297a3b0SGarrett D'Amore * starting with the weekday of the first day of the
2574297a3b0SGarrett D'Amore * month and moving into the future.
2584297a3b0SGarrett D'Amore */
2594297a3b0SGarrett D'Amore if (rtm.tm_wday != TMSENTINEL) {
2604297a3b0SGarrett D'Amore struct tm tm;
2614297a3b0SGarrett D'Amore
2624297a3b0SGarrett D'Amore (void) memset(&tm, 0, sizeof (struct tm));
2634297a3b0SGarrett D'Amore tm.tm_year = rtm.tm_year;
2644297a3b0SGarrett D'Amore tm.tm_mon = rtm.tm_mon;
2654297a3b0SGarrett D'Amore tm.tm_mday = 1;
2664297a3b0SGarrett D'Amore (void) mktime(&tm);
2674297a3b0SGarrett D'Amore rtm.tm_mday += (rtm.tm_wday - tm.tm_wday + 7) % 7;
2684297a3b0SGarrett D'Amore }
2694297a3b0SGarrett D'Amore }
2704297a3b0SGarrett D'Amore
2714297a3b0SGarrett D'Amore /*
2724297a3b0SGarrett D'Amore * Given only the century but no year within, the current year
2734297a3b0SGarrett D'Amore * is assumed. (Specified on the Solaris man page not POSIX.)
2744297a3b0SGarrett D'Amore *
2754297a3b0SGarrett D'Amore * Warning ugly end case
2764297a3b0SGarrett D'Amore *
2774297a3b0SGarrett D'Amore * This is more work since strptime(3) doesn't "do the right thing".
2784297a3b0SGarrett D'Amore */
2794297a3b0SGarrett D'Amore if (rtm.tm_year != TMSENTINEL && (rtm.tm_year - 1900) >= 0) {
2804297a3b0SGarrett D'Amore rtm.tm_year -= 1900;
2814297a3b0SGarrett D'Amore rtm.tm_year += (tmnow.tm_year % 100);
2824297a3b0SGarrett D'Amore }
2834297a3b0SGarrett D'Amore
2844297a3b0SGarrett D'Amore /*
2854297a3b0SGarrett D'Amore * mktime() will normalize all values and also check that the
2864297a3b0SGarrett D'Amore * value will fit into a time_t.
2874297a3b0SGarrett D'Amore *
2884297a3b0SGarrett D'Amore * This is only for POSIX correctness. A date >= 1900 is
2894297a3b0SGarrett D'Amore * really ok, but using a time_t limits things.
2904297a3b0SGarrett D'Amore */
2914297a3b0SGarrett D'Amore if (mktime(rtmp) < 0) {
2924297a3b0SGarrett D'Amore getdate_err = 8;
2934297a3b0SGarrett D'Amore return (NULL);
2944297a3b0SGarrett D'Amore }
2954297a3b0SGarrett D'Amore
2964297a3b0SGarrett D'Amore return (rtmp);
2974297a3b0SGarrett D'Amore }
298