X-Git-Url: http://git.osdn.net/view?a=blobdiff_plain;f=winsup%2Fcygwin%2Flibc%2Fstrptime.cc;fp=winsup%2Fcygwin%2Flibc%2Fstrptime.cc;h=ca059842a881bd1f0c9776f52660821e23da87bd;hb=cbad6d5ba66a94967eaf14df64463a4abca700d6;hp=e2f710ae982fe9976a4f32ef72713a4277833ea4;hpb=b110964dfb2d0779466e4e604983d3afa83993a4;p=pf3gnuchains%2Fpf3gnuchains4x.git diff --git a/winsup/cygwin/libc/strptime.cc b/winsup/cygwin/libc/strptime.cc index e2f710ae98..ca059842a8 100644 --- a/winsup/cygwin/libc/strptime.cc +++ b/winsup/cygwin/libc/strptime.cc @@ -41,86 +41,286 @@ __RCSID("$NetBSD: strptime.c,v 1.28 2008/04/28 20:23:01 martin Exp $"); #include #endif #include +#include #include #include #include #include +#include "../locale/timelocal.h" #ifdef __weak_alias __weak_alias(strptime,_strptime) #endif -#ifdef __CYGWIN__ -typedef struct { - const char *abday[7]; - const char *day[7]; - const char *abmon[12]; - const char *mon[12]; - const char *am_pm[2]; - const char *d_t_fmt; - const char *d_fmt; - const char *t_fmt; - const char *t_fmt_ampm; -} _TimeLocale; - -_TimeLocale _DefaultTimeLocale = +#define _ctloc(x) (_CurrentTimeLocale->x) + +#define ALT_E 0x01 +#define ALT_O 0x02 +#define LEGAL_ALT(x) { if (alt_format & ~(x)) return NULL; } + +static _CONST int _DAYS_BEFORE_MONTH[12] = +{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; + +#define SET_MDAY 1 +#define SET_MON 2 +#define SET_YEAR 4 +#define SET_WDAY 8 +#define SET_YDAY 16 +#define SET_YMD (SET_YEAR | SET_MON | SET_MDAY) + +static const char gmt[4] = { "GMT" }; + +typedef struct _era_info_t { + size_t num; /* Only in first entry: Number of entries, + 1 otherwise. */ + int dir; /* Direction */ + long offset; /* Number of year closest to start_date in the era. */ + struct tm start; /* Start date of era */ + struct tm end; /* End date of era */ + CHAR *era_C; /* Era string */ + CHAR *era_Y; /* Replacement for %EY */ +} era_info_t; + +static void +free_era_info (era_info_t *era_info) { + size_t num = era_info->num; + + for (size_t i = 0; i < num; ++i) + { + free (era_info[i].era_C); + free (era_info[i].era_Y); + } + free (era_info); +} + +static era_info_t * +get_era_info (const char *era) +{ + char *c; + era_info_t *ei = NULL; + size_t num = 0, cur = 0, len; + + while (*era) + { + ++num; + era_info_t *tmp = (era_info_t *) realloc (ei, num * sizeof (era_info_t)); + if (!tmp) { - "Sun","Mon","Tue","Wed","Thu","Fri","Sat", - }, + ei->num = cur; + free_era_info (ei); + return NULL; + } + ei = tmp; + ei[cur].num = 1; + ei[cur].dir = (*era == '+') ? 1 : -1; + era += 2; + ei[cur].offset = strtol (era, &c, 10); + era = c + 1; + ei[cur].start.tm_year = strtol (era, &c, 10); + /* Adjust offset for negative gregorian dates. */ + if (ei[cur].start.tm_year < 0) + ++ei[cur].start.tm_year; + ei[cur].start.tm_mon = strtol (c + 1, &c, 10); + ei[cur].start.tm_mday = strtol (c + 1, &c, 10); + ei[cur].start.tm_hour = ei[cur].start.tm_min = ei[cur].start.tm_sec = 0; + era = c + 1; + if (era[0] == '-' && era[1] == '*') { - "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", - "Friday", "Saturday" - }, + ei[cur].end = ei[cur].start; + ei[cur].start.tm_year = INT_MIN; + ei[cur].start.tm_mon = ei[cur].start.tm_mday = ei[cur].start.tm_hour + = ei[cur].start.tm_min = ei[cur].start.tm_sec = 0; + era += 3; + } + else if (era[0] == '+' && era[1] == '*') { - "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" - }, + ei[cur].end.tm_year = INT_MAX; + ei[cur].end.tm_mon = 12; + ei[cur].end.tm_mday = 31; + ei[cur].end.tm_hour = 23; + ei[cur].end.tm_min = ei[cur].end.tm_sec = 59; + era += 3; + } + else { - "January", "February", "March", "April", "May", "June", "July", - "August", "September", "October", "November", "December" - }, + ei[cur].end.tm_year = strtol (era, &c, 10); + /* Adjust offset for negative gregorian dates. */ + if (ei[cur].end.tm_year < 0) + ++ei[cur].end.tm_year; + ei[cur].end.tm_mon = strtol (c + 1, &c, 10); + ei[cur].end.tm_mday = strtol (c + 1, &c, 10); + ei[cur].end.tm_mday = 31; + ei[cur].end.tm_hour = 23; + ei[cur].end.tm_min = ei[cur].end.tm_sec = 59; + era = c + 1; + } + /* era_C */ + c = strchr (era, ':'); + len = c - era; + ei[cur].era_C = (CHAR *) malloc ((len + 1) * sizeof (CHAR)); + if (!ei[cur].era_C) { - "AM", "PM" - }, - "%a %b %e %H:%M:%S %Y", - "%m/%d/%y", - "%H:%M:%S", - "%I:%M:%S %p" -}; - -_TimeLocale *_CurrentTimeLocale = &_DefaultTimeLocale; -#endif + ei->num = cur; + free_era_info (ei); + return NULL; + } + strncpy (ei[cur].era_C, era, len); + era += len; + ei[cur].era_C[len] = '\0'; + /* era_Y */ + ++era; + c = strchr (era, ';'); + if (!c) + c = strchr (era, '\0'); + len = c - era; + ei[cur].era_Y = (CHAR *) malloc ((len + 1) * sizeof (CHAR)); + if (!ei[cur].era_Y) + { + free (ei[cur].era_C); + ei->num = cur; + free_era_info (ei); + return NULL; + } + strncpy (ei[cur].era_Y, era, len); + era += len; + ei[cur].era_Y[len] = '\0'; + ++cur; + if (*c) + era = c + 1; + } + ei->num = num; + return ei; +} -#define _ctloc(x) (_CurrentTimeLocale->x) +typedef struct _alt_digits_t { + size_t num; + char **digit; + char *buffer; +} alt_digits_t; -/* - * We do not implement alternate representations. However, we always - * check whether a given modifier is allowed for a certain conversion. - */ -#define ALT_E 0x01 -#define ALT_O 0x02 -#define LEGAL_ALT(x) { if (alt_format & ~(x)) return NULL; } +static alt_digits_t * +get_alt_digits (const char *alt_digits) +{ + alt_digits_t *adi; + const char *a, *e; + char *aa, *ae; + size_t len; + + adi = (alt_digits_t *) calloc (1, sizeof (alt_digits_t)); + if (!adi) + return NULL; + + /* Compute number of alt_digits. */ + adi->num = 1; + for (a = alt_digits; (e = strchr (a, ';')) != NULL; a = e + 1) + ++adi->num; + /* Allocate the `digit' array, which is an array of `num' pointers into + `buffer'. */ + adi->digit = (CHAR **) calloc (adi->num, sizeof (CHAR **)); + if (!adi->digit) + { + free (adi); + return NULL; + } + /* Compute memory required for `buffer'. */ + len = strlen (alt_digits); + /* Allocate it. */ + adi->buffer = (CHAR *) malloc ((len + 1) * sizeof (CHAR)); + if (!adi->buffer) + { + free (adi->digit); + free (adi); + return NULL; + } + /* Store digits in it. */ + strcpy (adi->buffer, alt_digits); + /* Store the pointers into `buffer' into the appropriate `digit' slot. */ + for (len = 0, aa = adi->buffer; (ae = strchr (aa, ';')) != NULL; + ++len, aa = ae + 1) + { + *ae = '\0'; + adi->digit[len] = aa; + } + adi->digit[len] = aa; + return adi; +} -static const char gmt[4] = { "GMT" }; +static void +free_alt_digits (alt_digits_t *adi) +{ + free (adi->digit); + free (adi->buffer); + free (adi); +} + +static const unsigned char * +find_alt_digits (const unsigned char *bp, alt_digits_t *adi, uint *pval) +{ + /* This is rather error-prone, but the entire idea of alt_digits + isn't thought out well. If you start to look for matches at the + start, there's a high probability that you find short matches but + the entire translation is wrong. So we scan the alt_digits array + from the highest to the lowest digits instead, hoping that it's + more likely to catch digits consisting of multiple characters. */ + for (int i = (int) adi->num - 1; i >= 0; --i) + { + size_t len = strlen (adi->digit[i]); + if (!strncmp ((const char *) bp, adi->digit[i], len)) + { + *pval = i; + return bp + len; + } + } + return NULL; +} -static const u_char *conv_num(const unsigned char *, int *, uint, uint); +static int +is_leap_year (int year) +{ + return (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0); +} + +static int +first_day (int year) +{ + int ret = 4; + + while (--year >= 1970) + ret = (ret + 365 + is_leap_year (year)) % 7; + return ret; +} + +/* This simplifies the calls to conv_num enormously. */ +#define ALT_DIGITS ((alt_format & ALT_O) ? *alt_digits : NULL) + +static const u_char *conv_num(const unsigned char *, int *, uint, uint, + alt_digits_t *); static const u_char *find_string(const u_char *, int *, const char * const *, const char * const *, int); - -char * -strptime(const char *buf, const char *fmt, struct tm *tm) +static char * +__strptime(const char *buf, const char *fmt, struct tm *tm, + era_info_t **era_info, alt_digits_t **alt_digits) { unsigned char c; const unsigned char *bp; int alt_format, i, split_year = 0; + era_info_t *era = NULL; + int era_offset, got_eoff = 0; + int saw_padding; + unsigned long width; const char *new_fmt; + uint ulim; + int ymd = 0; bp = (const u_char *)buf; + struct lc_time_T *_CurrentTimeLocale = __get_current_time_locale (); while (bp != NULL && (c = *fmt++) != '\0') { /* Clear `alternate' modifier prior to new conversion. */ + saw_padding = 0; + width = 0; alt_format = 0; i = 0; @@ -150,29 +350,63 @@ literal: case 'E': /* "%E?" alternative conversion modifier. */ LEGAL_ALT(0); alt_format |= ALT_E; + if (!*era_info && *_CurrentTimeLocale->era) + *era_info = get_era_info (_CurrentTimeLocale->era); goto again; case 'O': /* "%O?" alternative conversion modifier. */ LEGAL_ALT(0); alt_format |= ALT_O; + if (!*alt_digits && *_CurrentTimeLocale->alt_digits) + *alt_digits = + get_alt_digits (_CurrentTimeLocale->alt_digits); goto again; - + case '0': + case '+': + LEGAL_ALT(0); + if (saw_padding) + return NULL; + saw_padding = 1; + goto again; + case '1': case '2': case '3': case '4': case '5': + case '6': case '7': case '8': case '9': + /* POSIX-1.2008 maximum field width. Per POSIX, + the width is only defined for the 'C', 'F', and 'Y' + conversion specifiers. */ + LEGAL_ALT(0); + { + char *end; + width = strtoul (fmt - 1, &end, 10); + fmt = (const char *) end; + goto again; + } /* * "Complex" conversion rules, implemented through recursion. */ case 'c': /* Date and time, using the locale's format. */ - new_fmt = _ctloc(d_t_fmt); + new_fmt = (alt_format & ALT_E) + ? _ctloc (era_d_t_fmt) : _ctloc(c_fmt); + LEGAL_ALT(ALT_E); + ymd |= SET_WDAY | SET_YMD; goto recurse; case 'D': /* The date as "%m/%d/%y". */ new_fmt = "%m/%d/%y"; LEGAL_ALT(0); + ymd |= SET_YMD; goto recurse; case 'F': /* The date as "%Y-%m-%d". */ - new_fmt = "%Y-%m-%d"; - LEGAL_ALT(0); - goto recurse; + { + LEGAL_ALT(0); + ymd |= SET_YMD; + char *tmp = __strptime ((const char *) bp, "%Y-%m-%d", + tm, era_info, alt_digits); + if (tmp && (uint) (tmp - (char *) bp) > width) + return NULL; + bp = (const unsigned char *) tmp; + continue; + } case 'R': /* The time as "%H:%M". */ new_fmt = "%H:%M"; @@ -180,7 +414,7 @@ literal: goto recurse; case 'r': /* The time in 12-hour clock representation. */ - new_fmt =_ctloc(t_fmt_ampm); + new_fmt =_ctloc(ampm_fmt); LEGAL_ALT(0); goto recurse; @@ -190,15 +424,20 @@ literal: goto recurse; case 'X': /* The time, using the locale's format. */ - new_fmt =_ctloc(t_fmt); + new_fmt = (alt_format & ALT_E) + ? _ctloc (era_t_fmt) : _ctloc(X_fmt); + LEGAL_ALT(ALT_E); goto recurse; case 'x': /* The date, using the locale's format. */ - new_fmt =_ctloc(d_fmt); - recurse: - bp = (const u_char *)strptime((const char *)bp, - new_fmt, tm); + new_fmt = (alt_format & ALT_E) + ? _ctloc (era_d_fmt) : _ctloc(x_fmt); LEGAL_ALT(ALT_E); + ymd |= SET_YMD; + recurse: + bp = (const u_char *)__strptime((const char *)bp, + new_fmt, tm, + era_info, alt_digits); continue; /* @@ -206,72 +445,103 @@ literal: */ case 'A': /* The day of week, using the locale's form. */ case 'a': - bp = find_string(bp, &tm->tm_wday, _ctloc(day), - _ctloc(abday), 7); + bp = find_string(bp, &tm->tm_wday, _ctloc(weekday), + _ctloc(wday), 7); LEGAL_ALT(0); + ymd |= SET_WDAY; continue; case 'B': /* The month, using the locale's form. */ case 'b': case 'h': - bp = find_string(bp, &tm->tm_mon, _ctloc(mon), - _ctloc(abmon), 12); + bp = find_string(bp, &tm->tm_mon, _ctloc(month), + _ctloc(mon), 12); LEGAL_ALT(0); + ymd |= SET_WDAY; continue; case 'C': /* The century number. */ + LEGAL_ALT(ALT_E); + ymd |= SET_YEAR; + if ((alt_format & ALT_E) && *era_info) + { + /* With E modifier, an era. We potentially + don't know the era offset yet, so we have to + store the value in a local variable. + The final computation of tm_year is only done + right before this function returns. */ + size_t num = (*era_info)->num; + for (size_t i = 0; i < num; ++i) + if (!strncmp ((const char *) bp, + (*era_info)[i].era_C, + strlen ((*era_info)[i].era_C))) + { + era = (*era_info) + i; + bp += strlen (era->era_C); + break; + } + if (!era) + return NULL; + continue; + } i = 20; - bp = conv_num(bp, &i, 0, 99); + for (ulim = 99; width && width < 2; ++width) + ulim /= 10; + bp = conv_num(bp, &i, 0, ulim, NULL); i = i * 100 - TM_YEAR_BASE; if (split_year) i += tm->tm_year % 100; split_year = 1; tm->tm_year = i; - LEGAL_ALT(ALT_E); + era = NULL; + got_eoff = 0; continue; case 'd': /* The day of month. */ case 'e': - bp = conv_num(bp, &tm->tm_mday, 1, 31); LEGAL_ALT(ALT_O); + ymd |= SET_MDAY; + bp = conv_num(bp, &tm->tm_mday, 1, 31, ALT_DIGITS); continue; case 'k': /* The hour (24-hour clock representation). */ LEGAL_ALT(0); /* FALLTHROUGH */ case 'H': - bp = conv_num(bp, &tm->tm_hour, 0, 23); LEGAL_ALT(ALT_O); + bp = conv_num(bp, &tm->tm_hour, 0, 23, ALT_DIGITS); continue; case 'l': /* The hour (12-hour clock representation). */ LEGAL_ALT(0); /* FALLTHROUGH */ case 'I': - bp = conv_num(bp, &tm->tm_hour, 1, 12); + LEGAL_ALT(ALT_O); + bp = conv_num(bp, &tm->tm_hour, 1, 12, ALT_DIGITS); if (tm->tm_hour == 12) tm->tm_hour = 0; - LEGAL_ALT(ALT_O); continue; case 'j': /* The day of year. */ i = 1; - bp = conv_num(bp, &i, 1, 366); + bp = conv_num(bp, &i, 1, 366, NULL); tm->tm_yday = i - 1; LEGAL_ALT(0); + ymd |= SET_YDAY; continue; case 'M': /* The minute. */ - bp = conv_num(bp, &tm->tm_min, 0, 59); LEGAL_ALT(ALT_O); + bp = conv_num(bp, &tm->tm_min, 0, 59, ALT_DIGITS); continue; case 'm': /* The month. */ + LEGAL_ALT(ALT_O); + ymd |= SET_MON; i = 1; - bp = conv_num(bp, &i, 1, 12); + bp = conv_num(bp, &i, 1, 12, ALT_DIGITS); tm->tm_mon = i - 1; - LEGAL_ALT(ALT_O); continue; case 'p': /* The locale's equivalent of AM/PM. */ @@ -283,8 +553,8 @@ literal: continue; case 'S': /* The seconds. */ - bp = conv_num(bp, &tm->tm_sec, 0, 61); LEGAL_ALT(ALT_O); + bp = conv_num(bp, &tm->tm_sec, 0, 61, ALT_DIGITS); continue; case 'U': /* The week of year, beginning on sunday. */ @@ -295,28 +565,76 @@ literal: * point to calculate a real value, so just check the * range for now. */ - bp = conv_num(bp, &i, 0, 53); LEGAL_ALT(ALT_O); + bp = conv_num(bp, &i, 0, 53, ALT_DIGITS); continue; + case 'u': /* The day of week, beginning on monday. */ + LEGAL_ALT(ALT_O); + ymd |= SET_WDAY; + bp = conv_num(bp, &i, 1, 7, ALT_DIGITS); + tm->tm_wday = i % 7; + continue; case 'w': /* The day of week, beginning on sunday. */ - bp = conv_num(bp, &tm->tm_wday, 0, 6); LEGAL_ALT(ALT_O); + ymd |= SET_WDAY; + bp = conv_num(bp, &tm->tm_wday, 0, 6, ALT_DIGITS); continue; case 'Y': /* The year. */ + LEGAL_ALT(ALT_E); + ymd |= SET_YEAR; + if ((alt_format & ALT_E) && *era_info) + { + bool gotit = false; + size_t num = (*era_info)->num; + (*era_info)->num = 1; + for (size_t i = 0; i < num; ++i) + { + era_info_t *tmp_ei = (*era_info) + i; + char *tmp = __strptime ((const char *) bp, + tmp_ei->era_Y, + tm, &tmp_ei, + alt_digits); + if (tmp) + { + bp = (const unsigned char *) tmp; + gotit = true; + break; + } + } + (*era_info)->num = num; + if (gotit) + continue; + return NULL; + } i = TM_YEAR_BASE; /* just for data sanity... */ - bp = conv_num(bp, &i, 0, 9999); + for (ulim = 9999; width && width < 4; ++width) + ulim /= 10; + bp = conv_num(bp, &i, 0, ulim, NULL); tm->tm_year = i - TM_YEAR_BASE; - LEGAL_ALT(ALT_E); + era = NULL; + got_eoff = 0; continue; case 'y': /* The year within 100 years of the epoch. */ /* LEGAL_ALT(ALT_E | ALT_O); */ - bp = conv_num(bp, &i, 0, 99); - - if (split_year) - /* preserve century */ + ymd |= SET_YEAR; + if ((alt_format & ALT_E) && *era_info) + { + /* With E modifier, the offset to the start date + of the era specified with %EC. We potentially + don't know the era yet, so we have to store the + value in a local variable, just like era itself. + The final computation of tm_year is only done + right before this function returns. */ + bp = conv_num(bp, &era_offset, 0, UINT_MAX, NULL); + got_eoff = 1; + continue; + } + bp = conv_num(bp, &i, 0, 99, ALT_DIGITS); + + if (split_year) /* preserve century */ i += (tm->tm_year / 100) * 100; else { split_year = 1; @@ -326,6 +644,8 @@ literal: i = i + 1900 - TM_YEAR_BASE; } tm->tm_year = i; + era = NULL; + got_eoff = 0; continue; case 'Z': @@ -374,29 +694,112 @@ literal: } } + if (bp && (era || got_eoff)) + { + /* Default to current era. */ + if (!era) + era = *era_info; + /* Default to first year of era if offset is missing */ + if (!got_eoff) + era_offset = era->offset; + tm->tm_year = (era->start.tm_year != INT_MIN + ? era->start.tm_year : era->end.tm_year) + + (era_offset - era->offset) * era->dir; + /* Check if year falls into the era. If not, it's an + invalid combination of era and offset. */ + if (era->start.tm_year > tm->tm_year + || era->end.tm_year < tm->tm_year) + return NULL; + tm->tm_year -= TM_YEAR_BASE; + } + + if ((ymd & SET_YMD) == SET_YMD) + { + /* all of tm_year, tm_mon and tm_mday, but... */ + if (!(ymd & SET_YDAY)) + { + /* ...not tm_yday, so fill it in */ + tm->tm_yday = _DAYS_BEFORE_MONTH[tm->tm_mon] + tm->tm_mday; + if (!is_leap_year (tm->tm_year + TM_YEAR_BASE) + || tm->tm_mon < 2) + tm->tm_yday--; + ymd |= SET_YDAY; + } + } + else if ((ymd & (SET_YEAR | SET_YDAY)) == (SET_YEAR | SET_YDAY)) + { + /* both of tm_year and tm_yday, but... */ + if (!(ymd & SET_MON)) + { + /* ...not tm_mon, so fill it in, and/or... */ + if (tm->tm_yday < _DAYS_BEFORE_MONTH[1]) + tm->tm_mon = 0; + else + { + int leap = is_leap_year (tm->tm_year + TM_YEAR_BASE); + for (i = 2; i < 12; ++i) + if (tm->tm_yday < _DAYS_BEFORE_MONTH[i] + leap) + break; + tm->tm_mon = i - 1; + } + } + if (!(ymd & SET_MDAY)) + { + /* ...not tm_mday, so fill it in */ + tm->tm_mday = tm->tm_yday - _DAYS_BEFORE_MONTH[tm->tm_mon]; + if (!is_leap_year (tm->tm_year + TM_YEAR_BASE) + || tm->tm_mon < 2) + tm->tm_mday++; + } + } + + if ((ymd & (SET_YEAR | SET_YDAY | SET_WDAY)) == (SET_YEAR | SET_YDAY)) + { + /* fill in tm_wday */ + int fday = first_day (tm->tm_year + TM_YEAR_BASE); + tm->tm_wday = (fday + tm->tm_yday) % 7; + } return (char *) bp; } +char * +strptime (const char *buf, const char *fmt, struct tm *tm) +{ + era_info_t *era_info = NULL; + alt_digits_t *alt_digits = NULL; + char *ret = __strptime (buf, fmt, tm, &era_info, &alt_digits); + if (era_info) + free_era_info (era_info); + if (alt_digits) + free_alt_digits (alt_digits); + return ret; +} static const u_char * -conv_num(const unsigned char *buf, int *dest, uint llim, uint ulim) +conv_num(const unsigned char *buf, int *dest, uint llim, uint ulim, + alt_digits_t *alt_digits) { uint result = 0; unsigned char ch; - /* The limit also determines the number of valid digits. */ - uint rulim = ulim; - - ch = *buf; - if (ch < '0' || ch > '9') - return NULL; - - do { - result *= 10; - result += ch - '0'; - rulim /= 10; - ch = *++buf; - } while ((result * 10 <= ulim) && rulim && ch >= '0' && ch <= '9'); + if (alt_digits) + buf = find_alt_digits (buf, alt_digits, &result); + else + { + /* The limit also determines the number of valid digits. */ + uint rulim = ulim; + + ch = *buf; + if (ch < '0' || ch > '9') + return NULL; + + do { + result *= 10; + result += ch - '0'; + rulim /= 10; + ch = *++buf; + } while ((result * 10 <= ulim) && rulim && ch >= '0' && ch <= '9'); + } if (result < llim || result > ulim) return NULL;