1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2012 Lennart Poettering
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
27 #include <stdio_ext.h>
33 #include "alloc-util.h"
34 #include "calendarspec.h"
37 #include "parse-util.h"
38 #include "process-util.h"
39 #include "string-util.h"
40 #include "time-util.h"
42 #define BITS_WEEKDAYS 127
46 /* An arbitrary limit on the length of the chains of components. We don't want to
47 * build a very long linked list, which would be slow to iterate over and might cause
48 * our stack to overflow. It's unlikely that legitimate uses require more than a few
49 * linked compenents anyway. */
50 #define CALENDARSPEC_COMPONENTS_MAX 240
52 static void free_chain(CalendarComponent
*c
) {
62 CalendarSpec
* calendar_spec_free(CalendarSpec
*c
) {
71 free_chain(c
->minute
);
72 free_chain(c
->microsecond
);
78 static int component_compare(const void *_a
, const void *_b
) {
79 CalendarComponent
* const *a
= _a
, * const *b
= _b
;
81 if ((*a
)->start
< (*b
)->start
)
83 if ((*a
)->start
> (*b
)->start
)
86 if ((*a
)->stop
< (*b
)->stop
)
88 if ((*a
)->stop
> (*b
)->stop
)
91 if ((*a
)->repeat
< (*b
)->repeat
)
93 if ((*a
)->repeat
> (*b
)->repeat
)
99 static void normalize_chain(CalendarComponent
**c
) {
101 CalendarComponent
**b
, *i
, **j
, *next
;
105 for (i
= *c
; i
; i
= i
->next
) {
109 * While we're counting the chain, also normalize `stop`
110 * so the length of the range is a multiple of `repeat`
112 if (i
->stop
> i
->start
&& i
->repeat
> 0)
113 i
->stop
-= (i
->stop
- i
->start
) % i
->repeat
;
120 j
= b
= alloca(sizeof(CalendarComponent
*) * n
);
121 for (i
= *c
; i
; i
= i
->next
)
124 qsort(b
, n
, sizeof(CalendarComponent
*), component_compare
);
129 /* Drop non-unique entries */
130 for (k
= n
-1; k
> 0; k
--) {
131 if (component_compare(&b
[k
-1], &next
) == 0) {
143 static void fix_year(CalendarComponent
*c
) {
144 /* Turns 12 → 2012, 89 → 1989 */
147 if (c
->start
>= 0 && c
->start
< 70)
150 if (c
->stop
>= 0 && c
->stop
< 70)
153 if (c
->start
>= 70 && c
->start
< 100)
156 if (c
->stop
>= 70 && c
->stop
< 100)
163 int calendar_spec_normalize(CalendarSpec
*c
) {
166 if (streq_ptr(c
->timezone
, "UTC")) {
168 c
->timezone
= mfree(c
->timezone
);
171 if (c
->weekdays_bits
<= 0 || c
->weekdays_bits
>= BITS_WEEKDAYS
)
172 c
->weekdays_bits
= -1;
174 if (c
->end_of_month
&& !c
->day
)
175 c
->end_of_month
= false;
179 normalize_chain(&c
->year
);
180 normalize_chain(&c
->month
);
181 normalize_chain(&c
->day
);
182 normalize_chain(&c
->hour
);
183 normalize_chain(&c
->minute
);
184 normalize_chain(&c
->microsecond
);
189 _pure_
static bool chain_valid(CalendarComponent
*c
, int from
, int to
, bool end_of_month
) {
195 /* Forbid dates more than 28 days from the end of the month */
199 if (c
->start
< from
|| c
->start
> to
)
202 /* Avoid overly large values that could cause overflow */
203 if (c
->repeat
> to
- from
)
207 * c->repeat must be short enough so at least one repetition may
208 * occur before the end of the interval. For dates scheduled
209 * relative to the end of the month, c->start and c->stop
210 * correspond to the Nth last day of the month.
213 if (c
->stop
< from
|| c
->stop
> to
)
216 if (c
->start
+ c
->repeat
> c
->stop
)
219 if (end_of_month
&& c
->start
- c
->repeat
< from
)
222 if (!end_of_month
&& c
->start
+ c
->repeat
> to
)
227 return chain_valid(c
->next
, from
, to
, end_of_month
);
232 _pure_
bool calendar_spec_valid(CalendarSpec
*c
) {
235 if (c
->weekdays_bits
> BITS_WEEKDAYS
)
238 if (!chain_valid(c
->year
, MIN_YEAR
, MAX_YEAR
, false))
241 if (!chain_valid(c
->month
, 1, 12, false))
244 if (!chain_valid(c
->day
, 1, 31, c
->end_of_month
))
247 if (!chain_valid(c
->hour
, 0, 23, false))
250 if (!chain_valid(c
->minute
, 0, 59, false))
253 if (!chain_valid(c
->microsecond
, 0, 60*USEC_PER_SEC
-1, false))
259 static void format_weekdays(FILE *f
, const CalendarSpec
*c
) {
260 static const char *const days
[] = {
271 bool need_comma
= false;
275 assert(c
->weekdays_bits
> 0 && c
->weekdays_bits
<= BITS_WEEKDAYS
);
277 for (x
= 0, l
= -1; x
< (int) ELEMENTSOF(days
); x
++) {
279 if (c
->weekdays_bits
& (1 << x
)) {
294 fputs(x
> l
+ 2 ? ".." : ",", f
);
302 if (l
>= 0 && x
> l
+ 1) {
303 fputs(x
> l
+ 2 ? ".." : ",", f
);
308 static void format_chain(FILE *f
, int space
, const CalendarComponent
*c
, bool usec
) {
309 int d
= usec
? (int) USEC_PER_SEC
: 1;
318 if (usec
&& c
->start
== 0 && c
->repeat
== USEC_PER_SEC
&& !c
->next
) {
323 assert(c
->start
>= 0);
325 fprintf(f
, "%0*i", space
, c
->start
/ d
);
326 if (c
->start
% d
> 0)
327 fprintf(f
, ".%06i", c
->start
% d
);
330 fprintf(f
, "..%0*i", space
, c
->stop
/ d
);
332 fprintf(f
, ".%06i", c
->stop
% d
);
334 if (c
->repeat
> 0 && !(c
->stop
> 0 && c
->repeat
== d
))
335 fprintf(f
, "/%i", c
->repeat
/ d
);
336 if (c
->repeat
% d
> 0)
337 fprintf(f
, ".%06i", c
->repeat
% d
);
341 format_chain(f
, space
, c
->next
, usec
);
345 int calendar_spec_to_string(const CalendarSpec
*c
, char **p
) {
354 f
= open_memstream(&buf
, &sz
);
358 (void) __fsetlocking(f
, FSETLOCKING_BYCALLER
);
360 if (c
->weekdays_bits
> 0 && c
->weekdays_bits
<= BITS_WEEKDAYS
) {
361 format_weekdays(f
, c
);
365 format_chain(f
, 4, c
->year
, false);
367 format_chain(f
, 2, c
->month
, false);
368 fputc(c
->end_of_month
? '~' : '-', f
);
369 format_chain(f
, 2, c
->day
, false);
371 format_chain(f
, 2, c
->hour
, false);
373 format_chain(f
, 2, c
->minute
, false);
375 format_chain(f
, 2, c
->microsecond
, true);
379 else if (c
->timezone
!= NULL
) {
381 fputs(c
->timezone
, f
);
382 } else if (IN_SET(c
->dst
, 0, 1)) {
384 /* If daylight saving is explicitly on or off, let's show the used timezone. */
388 if (!isempty(tzname
[c
->dst
])) {
390 fputs(tzname
[c
->dst
], f
);
394 r
= fflush_and_check(f
);
407 static int parse_weekdays(const char **p
, CalendarSpec
*c
) {
408 static const struct {
438 for (i
= 0; i
< ELEMENTSOF(day_nr
); i
++) {
441 if (!startswith_no_case(*p
, day_nr
[i
].name
))
444 skip
= strlen(day_nr
[i
].name
);
446 if (!IN_SET((*p
)[skip
], 0, '-', '.', ',', ' '))
449 c
->weekdays_bits
|= 1 << day_nr
[i
].nr
;
454 if (l
> day_nr
[i
].nr
)
457 for (j
= l
+ 1; j
< day_nr
[i
].nr
; j
++)
458 c
->weekdays_bits
|= 1 << j
;
465 /* Couldn't find this prefix, so let's assume the
466 weekday was not specified and let's continue with
468 if (i
>= ELEMENTSOF(day_nr
))
469 return first
? 0 : -EINVAL
;
471 /* We reached the end of the string */
475 /* We reached the end of the weekday spec part */
477 *p
+= strspn(*p
, " ");
491 /* Support ranges with "-" for backwards compatibility */
492 } else if (**p
== '-') {
499 } else if (**p
== ',') {
504 /* Allow a trailing comma but not an open range */
505 if (IN_SET(**p
, 0, ' ')) {
506 *p
+= strspn(*p
, " ");
507 return l
< 0 ? 0 : -EINVAL
;
514 static int parse_one_number(const char *p
, const char **e
, unsigned long *ret
) {
519 value
= strtoul(p
, &ee
, 10);
530 static int parse_component_decimal(const char **p
, bool usec
, int *res
) {
532 const char *e
= NULL
;
538 r
= parse_one_number(*p
, &e
, &value
);
543 if (value
* USEC_PER_SEC
/ USEC_PER_SEC
!= value
)
546 value
*= USEC_PER_SEC
;
548 /* One "." is a decimal point, but ".." is a range separator */
549 if (e
[0] == '.' && e
[1] != '.') {
553 r
= parse_fractional_part_u(&e
, 6, &add
);
557 if (add
+ value
< value
)
572 static int const_chain(int value
, CalendarComponent
**c
) {
573 CalendarComponent
*cc
= NULL
;
577 cc
= new0(CalendarComponent
, 1);
591 static int calendarspec_from_time_t(CalendarSpec
*c
, time_t time
) {
593 CalendarComponent
*year
= NULL
, *month
= NULL
, *day
= NULL
, *hour
= NULL
, *minute
= NULL
, *us
= NULL
;
596 if (!gmtime_r(&time
, &tm
))
599 r
= const_chain(tm
.tm_year
+ 1900, &year
);
603 r
= const_chain(tm
.tm_mon
+ 1, &month
);
607 r
= const_chain(tm
.tm_mday
, &day
);
611 r
= const_chain(tm
.tm_hour
, &hour
);
615 r
= const_chain(tm
.tm_min
, &minute
);
619 r
= const_chain(tm
.tm_sec
* USEC_PER_SEC
, &us
);
633 static int prepend_component(const char **p
, bool usec
, unsigned nesting
, CalendarComponent
**c
) {
634 int r
, start
, stop
= -1, repeat
= 0;
635 CalendarComponent
*cc
;
641 if (nesting
> CALENDARSPEC_COMPONENTS_MAX
)
644 r
= parse_component_decimal(&e
, usec
, &start
);
648 if (e
[0] == '.' && e
[1] == '.') {
650 r
= parse_component_decimal(&e
, usec
, &stop
);
654 repeat
= usec
? USEC_PER_SEC
: 1;
659 r
= parse_component_decimal(&e
, usec
, &repeat
);
667 if (!IN_SET(*e
, 0, ' ', ',', '-', '~', ':'))
670 cc
= new0(CalendarComponent
, 1);
684 return prepend_component(p
, usec
, nesting
+ 1, c
);
690 static int parse_chain(const char **p
, bool usec
, CalendarComponent
**c
) {
692 CalendarComponent
*cc
= NULL
;
702 r
= const_chain(0, c
);
705 (*c
)->repeat
= USEC_PER_SEC
;
713 r
= prepend_component(&t
, usec
, 0, &cc
);
724 static int parse_date(const char **p
, CalendarSpec
*c
) {
727 CalendarComponent
*first
, *second
, *third
;
738 /* @TIMESTAMP — UNIX time in seconds since the epoch */
743 r
= parse_one_number(t
+ 1, &t
, &value
);
748 if ((unsigned long) time
!= value
)
751 r
= calendarspec_from_time_t(c
, time
);
756 return 1; /* finito, don't parse H:M:S after that */
759 r
= parse_chain(&t
, false, &first
);
763 /* Already the end? A ':' as separator? In that case this was a time, not a date */
764 if (IN_SET(*t
, 0, ':')) {
770 c
->end_of_month
= true;
771 else if (*t
!= '-') {
777 r
= parse_chain(&t
, false, &second
);
783 /* Got two parts, hence it's month and day */
784 if (IN_SET(*t
, 0, ' ')) {
785 *p
= t
+ strspn(t
, " ");
789 } else if (c
->end_of_month
) {
796 c
->end_of_month
= true;
797 else if (*t
!= '-') {
804 r
= parse_chain(&t
, false, &third
);
811 /* Got three parts, hence it is year, month and day */
812 if (IN_SET(*t
, 0, ' ')) {
813 *p
= t
+ strspn(t
, " ");
826 static int parse_calendar_time(const char **p
, CalendarSpec
*c
) {
827 CalendarComponent
*h
= NULL
, *m
= NULL
, *s
= NULL
;
837 /* If no time is specified at all, then this means 00:00:00 */
841 r
= parse_chain(&t
, false, &h
);
851 r
= parse_chain(&t
, false, &m
);
855 /* Already at the end? Then it's hours and minutes, and seconds are 0 */
865 r
= parse_chain(&t
, true, &s
);
869 /* At the end? Then it's hours, minutes and seconds */
877 r
= const_chain(0, &h
);
881 r
= const_chain(0, &m
);
886 r
= const_chain(0, &s
);
905 int calendar_spec_from_string(const char *p
, CalendarSpec
**spec
) {
913 c
= new0(CalendarSpec
, 1);
919 utc
= endswith_no_case(p
, " UTC");
922 p
= strndupa(p
, utc
- p
);
924 const char *e
= NULL
;
929 /* Check if the local timezone was specified? */
930 for (j
= 0; j
<= 1; j
++) {
931 if (isempty(tzname
[j
]))
934 e
= endswith_no_case(p
, tzname
[j
]);
945 /* Found one of the two timezones specified? */
946 if (IN_SET(j
, 0, 1)) {
947 p
= strndupa(p
, e
- p
- 1);
950 const char *last_space
;
952 last_space
= strrchr(p
, ' ');
953 if (last_space
!= NULL
&& timezone_is_valid(last_space
+ 1)) {
954 c
->timezone
= strdup(last_space
+ 1);
960 p
= strndupa(p
, last_space
- p
);
970 if (strcaseeq(p
, "minutely")) {
971 r
= const_chain(0, &c
->microsecond
);
975 } else if (strcaseeq(p
, "hourly")) {
976 r
= const_chain(0, &c
->minute
);
979 r
= const_chain(0, &c
->microsecond
);
983 } else if (strcaseeq(p
, "daily")) {
984 r
= const_chain(0, &c
->hour
);
987 r
= const_chain(0, &c
->minute
);
990 r
= const_chain(0, &c
->microsecond
);
994 } else if (strcaseeq(p
, "monthly")) {
995 r
= const_chain(1, &c
->day
);
998 r
= const_chain(0, &c
->hour
);
1001 r
= const_chain(0, &c
->minute
);
1004 r
= const_chain(0, &c
->microsecond
);
1008 } else if (strcaseeq(p
, "annually") ||
1009 strcaseeq(p
, "yearly") ||
1010 strcaseeq(p
, "anually") /* backwards compatibility */ ) {
1012 r
= const_chain(1, &c
->month
);
1015 r
= const_chain(1, &c
->day
);
1018 r
= const_chain(0, &c
->hour
);
1021 r
= const_chain(0, &c
->minute
);
1024 r
= const_chain(0, &c
->microsecond
);
1028 } else if (strcaseeq(p
, "weekly")) {
1030 c
->weekdays_bits
= 1;
1032 r
= const_chain(0, &c
->hour
);
1035 r
= const_chain(0, &c
->minute
);
1038 r
= const_chain(0, &c
->microsecond
);
1042 } else if (strcaseeq(p
, "quarterly")) {
1044 r
= const_chain(1, &c
->month
);
1047 r
= const_chain(4, &c
->month
);
1050 r
= const_chain(7, &c
->month
);
1053 r
= const_chain(10, &c
->month
);
1056 r
= const_chain(1, &c
->day
);
1059 r
= const_chain(0, &c
->hour
);
1062 r
= const_chain(0, &c
->minute
);
1065 r
= const_chain(0, &c
->microsecond
);
1069 } else if (strcaseeq(p
, "biannually") ||
1070 strcaseeq(p
, "bi-annually") ||
1071 strcaseeq(p
, "semiannually") ||
1072 strcaseeq(p
, "semi-annually")) {
1074 r
= const_chain(1, &c
->month
);
1077 r
= const_chain(7, &c
->month
);
1080 r
= const_chain(1, &c
->day
);
1083 r
= const_chain(0, &c
->hour
);
1086 r
= const_chain(0, &c
->minute
);
1089 r
= const_chain(0, &c
->microsecond
);
1094 r
= parse_weekdays(&p
, c
);
1098 r
= parse_date(&p
, c
);
1103 r
= parse_calendar_time(&p
, c
);
1114 r
= calendar_spec_normalize(c
);
1118 if (!calendar_spec_valid(c
)) {
1127 calendar_spec_free(c
);
1131 static int find_end_of_month(struct tm
*tm
, bool utc
, int day
) {
1135 t
.tm_mday
= 1 - day
;
1137 if (mktime_or_timegm(&t
, utc
) < 0 ||
1138 t
.tm_mon
!= tm
->tm_mon
)
1144 static int find_matching_component(const CalendarSpec
*spec
, const CalendarComponent
*c
,
1145 struct tm
*tm
, int *val
) {
1146 const CalendarComponent
*p
= c
;
1147 int start
, stop
, d
= -1;
1160 if (spec
->end_of_month
&& p
== spec
->day
) {
1161 start
= find_end_of_month(tm
, spec
->utc
, start
);
1162 stop
= find_end_of_month(tm
, spec
->utc
, stop
);
1165 SWAP_TWO(start
, stop
);
1168 if (start
>= *val
) {
1170 if (!d_set
|| start
< d
) {
1175 } else if (c
->repeat
> 0) {
1178 k
= start
+ c
->repeat
* DIV_ROUND_UP(*val
- start
, c
->repeat
);
1180 if ((!d_set
|| k
< d
) && (stop
< 0 || k
<= stop
)) {
1197 static bool tm_out_of_bounds(const struct tm
*tm
, bool utc
) {
1203 if (mktime_or_timegm(&t
, utc
) < 0)
1207 * Set an upper bound on the year so impossible dates like "*-02-31"
1208 * don't cause find_next() to loop forever. tm_year contains years
1209 * since 1900, so adjust it accordingly.
1211 if (tm
->tm_year
+ 1900 > MAX_YEAR
)
1214 /* Did any normalization take place? If so, it was out of bounds before */
1216 t
.tm_year
!= tm
->tm_year
||
1217 t
.tm_mon
!= tm
->tm_mon
||
1218 t
.tm_mday
!= tm
->tm_mday
||
1219 t
.tm_hour
!= tm
->tm_hour
||
1220 t
.tm_min
!= tm
->tm_min
||
1221 t
.tm_sec
!= tm
->tm_sec
;
1224 static bool matches_weekday(int weekdays_bits
, const struct tm
*tm
, bool utc
) {
1228 if (weekdays_bits
< 0 || weekdays_bits
>= BITS_WEEKDAYS
)
1232 if (mktime_or_timegm(&t
, utc
) < 0)
1235 k
= t
.tm_wday
== 0 ? 6 : t
.tm_wday
- 1;
1236 return (weekdays_bits
& (1 << k
));
1239 static int find_next(const CalendarSpec
*spec
, struct tm
*tm
, usec_t
*usec
) {
1251 /* Normalize the current date */
1252 (void) mktime_or_timegm(&c
, spec
->utc
);
1253 c
.tm_isdst
= spec
->dst
;
1256 r
= find_matching_component(spec
, spec
->year
, &c
, &c
.tm_year
);
1262 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1266 if (tm_out_of_bounds(&c
, spec
->utc
))
1270 r
= find_matching_component(spec
, spec
->month
, &c
, &c
.tm_mon
);
1275 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1277 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
1281 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1285 r
= find_matching_component(spec
, spec
->day
, &c
, &c
.tm_mday
);
1287 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1288 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
1291 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1295 if (!matches_weekday(spec
->weekdays_bits
, &c
, spec
->utc
)) {
1297 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1301 r
= find_matching_component(spec
, spec
->hour
, &c
, &c
.tm_hour
);
1303 c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1304 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
1306 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1310 r
= find_matching_component(spec
, spec
->minute
, &c
, &c
.tm_min
);
1312 c
.tm_sec
= tm_usec
= 0;
1313 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
1315 c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1319 c
.tm_sec
= c
.tm_sec
* USEC_PER_SEC
+ tm_usec
;
1320 r
= find_matching_component(spec
, spec
->microsecond
, &c
, &c
.tm_sec
);
1321 tm_usec
= c
.tm_sec
% USEC_PER_SEC
;
1322 c
.tm_sec
/= USEC_PER_SEC
;
1324 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
1326 c
.tm_sec
= tm_usec
= 0;
1336 static int calendar_spec_next_usec_impl(const CalendarSpec
*spec
, usec_t usec
, usec_t
*next
) {
1345 if (usec
> USEC_TIMESTAMP_FORMATTABLE_MAX
)
1349 t
= (time_t) (usec
/ USEC_PER_SEC
);
1350 assert_se(localtime_or_gmtime_r(&t
, &tm
, spec
->utc
));
1351 tm_usec
= usec
% USEC_PER_SEC
;
1353 r
= find_next(spec
, &tm
, &tm_usec
);
1357 t
= mktime_or_timegm(&tm
, spec
->utc
);
1361 *next
= (usec_t
) t
* USEC_PER_SEC
+ tm_usec
;
1365 typedef struct SpecNextResult
{
1370 int calendar_spec_next_usec(const CalendarSpec
*spec
, usec_t usec
, usec_t
*next
) {
1371 SpecNextResult
*shared
, tmp
;
1374 if (isempty(spec
->timezone
))
1375 return calendar_spec_next_usec_impl(spec
, usec
, next
);
1377 shared
= mmap(NULL
, sizeof *shared
, PROT_READ
|PROT_WRITE
, MAP_SHARED
|MAP_ANONYMOUS
, -1, 0);
1378 if (shared
== MAP_FAILED
)
1379 return negative_errno();
1381 r
= safe_fork("(sd-calendar)", FORK_RESET_SIGNALS
|FORK_CLOSE_ALL_FDS
|FORK_DEATHSIG
|FORK_WAIT
, NULL
);
1383 (void) munmap(shared
, sizeof *shared
);
1387 if (setenv("TZ", spec
->timezone
, 1) != 0) {
1388 shared
->return_value
= negative_errno();
1389 _exit(EXIT_FAILURE
);
1394 shared
->return_value
= calendar_spec_next_usec_impl(spec
, usec
, &shared
->next
);
1396 _exit(EXIT_SUCCESS
);
1400 if (munmap(shared
, sizeof *shared
) < 0)
1401 return negative_errno();
1403 if (tmp
.return_value
== 0)
1406 return tmp
.return_value
;