]>
Commit | Line | Data |
---|---|---|
c9f916ab CW |
1 | /* |
2 | * utmpdump Simple program to dump UTMP and WTMP files in | |
3 | * raw format, so they can be examined. | |
4 | * | |
5 | * Author: Miquel van Smoorenburg, <miquels@cistron.nl> | |
6 | * Danek Duvall <duvall@alumni.princeton.edu> | |
7 | * | |
8 | * Version: @(#)utmpdump 2.79 12-Sep-2000 | |
9 | * | |
10 | * This file is part of the sysvinit suite, | |
11 | * Copyright 1991-2000 Miquel van Smoorenburg. | |
12 | * | |
13 | * Additional Copyright on this file 1998 Danek Duvall. | |
14 | * | |
bd695173 CW |
15 | * This program is free software; you can redistribute it and/or modify |
16 | * it under the terms of the GNU General Public License as published by | |
17 | * the Free Software Foundation in version 2 of the License. | |
18 | * | |
19 | * This program is distributed in the hope that it will be useful, | |
20 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
22 | * GNU General Public License for more details. | |
23 | * | |
24 | * You should have received a copy of the GNU General Public License | |
25 | * along with this program; if not, write to the Free Software | |
26 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
c9f916ab CW |
27 | */ |
28 | ||
bd695173 CW |
29 | #if HAVE_CONFIG_H |
30 | # include "config.h" | |
31 | #endif | |
32 | ||
d2b03e6a | 33 | #include "common.h" |
bd695173 CW |
34 | |
35 | /* special requirements moved to common.h */ | |
36 | /* | |
c9f916ab | 37 | #include <utmp.h> |
c9f916ab CW |
38 | #include <netinet/in.h> |
39 | #include <arpa/inet.h> | |
bd695173 CW |
40 | */ |
41 | /* end special requirements */ | |
42 | ||
c9f916ab CW |
43 | #include "oldutmp.h" |
44 | ||
45 | char *strptime (const char *, const char *, struct tm *); | |
46 | ||
47 | struct utmp | |
48 | oldtonew(struct oldutmp src) | |
49 | { | |
50 | struct utmp dest; | |
51 | ||
52 | memset(&dest, 0, sizeof dest); | |
53 | dest.ut_type = src.ut_type; | |
54 | dest.ut_pid = src.ut_pid; | |
55 | dest.ut_time = src.ut_oldtime; | |
56 | dest.ut_addr = src.ut_oldaddr; | |
57 | strncpy(dest.ut_id, src.ut_id, 4); | |
58 | strncpy(dest.ut_line, src.ut_line, OLD_LINESIZE); | |
59 | strncpy(dest.ut_user, src.ut_user, OLD_NAMESIZE); | |
60 | strncpy(dest.ut_host, src.ut_host, OLD_HOSTSIZE); | |
61 | ||
62 | return dest; | |
63 | } | |
64 | ||
65 | struct oldutmp | |
66 | newtoold(struct utmp src) | |
67 | { | |
68 | struct oldutmp dest; | |
69 | ||
70 | memset(&dest, 0, sizeof dest); | |
71 | dest.ut_type = src.ut_type; | |
72 | dest.ut_pid = src.ut_pid; | |
73 | dest.ut_oldtime = src.ut_time; | |
74 | dest.ut_oldaddr = src.ut_addr; | |
75 | strncpy(dest.ut_id, src.ut_id, 4); | |
76 | strncpy(dest.ut_line, src.ut_line, OLD_LINESIZE); | |
77 | strncpy(dest.ut_user, src.ut_user, OLD_NAMESIZE); | |
78 | strncpy(dest.ut_host, src.ut_host, OLD_HOSTSIZE); | |
79 | ||
80 | return dest; | |
81 | } | |
82 | ||
83 | char * | |
84 | timetostr(const time_t time) | |
85 | { | |
86 | static char s[29]; /* [Sun Sep 01 00:00:00 1998 PST] */ | |
87 | ||
88 | if (time != 0) | |
89 | strftime(s, 29, "%a %b %d %T %Y %Z", localtime(&time)); | |
90 | else | |
91 | s[0] = '\0'; | |
92 | ||
93 | return s; | |
94 | } | |
95 | ||
96 | time_t | |
97 | strtotime(const char *s_time) | |
98 | { | |
99 | struct tm *tm = malloc(sizeof(*tm)); | |
100 | ||
101 | if (s_time[0] == ' ' || s_time[0] == '\0') | |
102 | return (time_t)0; | |
103 | ||
104 | strptime(s_time, "%a %b %d %T %Y", tm); | |
105 | ||
106 | /* Cheesy way of checking for DST */ | |
107 | if (s_time[26] == 'D') | |
108 | tm->tm_isdst = 1; | |
109 | ||
110 | return mktime(tm); | |
111 | } | |
112 | ||
113 | #define cleanse(x) xcleanse(x, sizeof(x)) | |
114 | void | |
115 | xcleanse(char *s, int len) | |
116 | { | |
117 | for ( ; *s && len-- > 0; s++) | |
118 | if (!isprint(*s) || *s == '[' || *s == ']') | |
119 | *s = '?'; | |
120 | } | |
121 | ||
122 | void | |
123 | unspace(char *s, int len) | |
124 | { | |
125 | while (*s && *s != ' ' && len--) | |
126 | ++s; | |
127 | ||
128 | if (len > 0) | |
129 | *s = '\0'; | |
130 | } | |
131 | ||
132 | void | |
133 | print_utline(struct utmp ut) | |
134 | { | |
135 | char *addr_string, *time_string; | |
136 | struct in_addr in; | |
137 | ||
138 | in.s_addr = ut.ut_addr; | |
139 | addr_string = inet_ntoa(in); | |
140 | time_string = timetostr(ut.ut_time); | |
141 | cleanse(ut.ut_id); | |
142 | cleanse(ut.ut_user); | |
143 | cleanse(ut.ut_line); | |
144 | cleanse(ut.ut_host); | |
145 | ||
146 | /* pid id user line host addr time */ | |
147 | printf("[%d] [%05d] [%-4.4s] [%-*.*s] [%-*.*s] [%-*.*s] [%-15.15s] [%-28.28s]\n", | |
148 | ut.ut_type, ut.ut_pid, ut.ut_id, 8, UT_NAMESIZE, ut.ut_user, | |
149 | 12, UT_LINESIZE, ut.ut_line, 20, UT_HOSTSIZE, ut.ut_host, | |
150 | addr_string, time_string); | |
151 | } | |
152 | ||
153 | void | |
154 | dump(FILE *fp, int forever, int oldfmt) | |
155 | { | |
156 | struct utmp ut; | |
157 | struct oldutmp uto; | |
158 | ||
159 | if (forever) | |
160 | fseek(fp, -10 * (oldfmt ? sizeof uto : sizeof ut), SEEK_END); | |
161 | ||
162 | do { | |
163 | if (oldfmt) | |
164 | while (fread(&uto, sizeof uto, 1, fp) == 1) | |
165 | print_utline(oldtonew(uto)); | |
166 | else | |
167 | while (fread(&ut, sizeof ut, 1, fp) == 1) | |
168 | print_utline(ut); | |
169 | if (forever) sleep(1); | |
170 | } while (forever); | |
171 | } | |
172 | ||
173 | /* This function won't work properly if there's a ']' or a ' ' in the real | |
174 | * token. Thankfully, this should never happen. */ | |
175 | int | |
176 | gettok(char *line, char *dest, int size, int eatspace) | |
177 | { | |
178 | int bpos, epos, eaten; | |
179 | char *t; | |
180 | ||
181 | bpos = strchr(line, '[') - line; | |
182 | if (bpos < 0) { | |
183 | fprintf(stderr, "Extraneous newline in file. Exiting."); | |
184 | exit(1); | |
185 | } | |
186 | line += 1 + bpos; | |
187 | ||
188 | epos = strchr(line, ']') - line; | |
189 | if (epos < 0) { | |
190 | fprintf(stderr, "Extraneous newline in file. Exiting."); | |
191 | exit(1); | |
192 | } | |
193 | line[epos] = '\0'; | |
194 | ||
195 | eaten = bpos + epos + 1; | |
196 | ||
197 | if (eatspace) | |
198 | if ((t = strchr(line, ' '))) | |
199 | *t = 0; | |
200 | ||
201 | strncpy(dest, line, size); | |
202 | ||
203 | return eaten + 1; | |
204 | } | |
205 | ||
206 | void | |
207 | undump(FILE *fp, int forever, int oldfmt) | |
208 | { | |
209 | struct utmp ut; | |
210 | struct oldutmp uto; | |
211 | char s_addr[16], s_time[29], *linestart, *line; | |
212 | int count = 0; | |
213 | ||
214 | line = linestart = malloc(1024 * sizeof *linestart); | |
215 | s_addr[15] = 0; | |
216 | s_time[28] = 0; | |
217 | ||
218 | while(fgets(linestart, 1023, fp)) | |
219 | { | |
220 | line = linestart; | |
221 | memset(&ut, '\0', sizeof(ut)); | |
222 | sscanf(line, "[%hd] [%d] [%4c] ", &ut.ut_type, &ut.ut_pid, ut.ut_id); | |
223 | ||
224 | line += 19; | |
225 | line += gettok(line, ut.ut_user, sizeof(ut.ut_user), 1); | |
226 | line += gettok(line, ut.ut_line, sizeof(ut.ut_line), 1); | |
227 | line += gettok(line, ut.ut_host, sizeof(ut.ut_host), 1); | |
228 | line += gettok(line, s_addr, sizeof(s_addr)-1, 1); | |
229 | line += gettok(line, s_time, sizeof(s_time)-1, 0); | |
230 | ||
231 | ut.ut_addr = inet_addr(s_addr); | |
232 | ut.ut_time = strtotime(s_time); | |
233 | ||
234 | if (oldfmt) { | |
235 | uto = newtoold(ut); | |
236 | fwrite(&uto, sizeof(uto), 1, stdout); | |
237 | } else | |
238 | fwrite(&ut, sizeof(ut), 1, stdout); | |
239 | ||
240 | ++count; | |
241 | } | |
242 | ||
243 | free(linestart); | |
244 | } | |
245 | ||
246 | void | |
247 | usage(int result) | |
248 | { | |
249 | printf("Usage: utmpdump [ -froh ] [ filename ]\n"); | |
250 | exit(result); | |
251 | } | |
252 | ||
253 | int main(int argc, char **argv) | |
254 | { | |
255 | int c; | |
256 | FILE *fp; | |
257 | int reverse = 0, forever = 0, oldfmt = 0; | |
258 | ||
259 | while ((c = getopt(argc, argv, "froh")) != EOF) { | |
260 | switch (c) { | |
261 | case 'r': | |
262 | reverse = 1; | |
263 | break; | |
264 | ||
265 | case 'f': | |
266 | forever = 1; | |
267 | break; | |
268 | ||
269 | case 'o': | |
270 | oldfmt = 1; | |
271 | break; | |
272 | ||
273 | case 'h': | |
274 | usage(0); | |
275 | break; | |
276 | ||
277 | default: | |
278 | usage(1); | |
279 | } | |
280 | } | |
281 | ||
282 | if (optind < argc) { | |
283 | fprintf(stderr, "Utmp %sdump of %s\n", reverse ? "un" : "", argv[optind]); | |
284 | if ((fp = fopen(argv[optind], "r")) == NULL) { | |
285 | perror("Unable to open file"); | |
286 | exit(1); | |
287 | } | |
288 | } | |
289 | else { | |
290 | fprintf(stderr, "Utmp %sdump of stdin\n", reverse ? "un" : ""); | |
291 | fp = stdin; | |
292 | } | |
293 | ||
294 | if (reverse) | |
295 | undump(fp, forever, oldfmt); | |
296 | else | |
297 | dump(fp, forever, oldfmt); | |
298 | ||
299 | fclose(fp); | |
300 | ||
301 | return 0; | |
302 | } | |
303 | ||
304 | /* | |
305 | Everything after this point should be removed when the new cygwin dll is released, as this functionality has been added to it. The current dll is 1.3.6. | |
306 | */ | |
307 | ||
308 | static const char *abb_weekdays[] = { | |
309 | "Sun", | |
310 | "Mon", | |
311 | "Tue", | |
312 | "Wed", | |
313 | "Thu", | |
314 | "Fri", | |
315 | "Sat", | |
316 | NULL | |
317 | }; | |
318 | ||
319 | static const char *full_weekdays[] = { | |
320 | "Sunday", | |
321 | "Monday", | |
322 | "Tuesday", | |
323 | "Wednesday", | |
324 | "Thursday", | |
325 | "Friday", | |
326 | "Saturday", | |
327 | NULL | |
328 | }; | |
329 | ||
330 | static const char *abb_month[] = { | |
331 | "Jan", | |
332 | "Feb", | |
333 | "Mar", | |
334 | "Apr", | |
335 | "May", | |
336 | "Jun", | |
337 | "Jul", | |
338 | "Aug", | |
339 | "Sep", | |
340 | "Oct", | |
341 | "Nov", | |
342 | "Dec", | |
343 | NULL | |
344 | }; | |
345 | ||
346 | static const char *full_month[] = { | |
347 | "January", | |
348 | "February", | |
349 | "Mars", | |
350 | "April", | |
351 | "May", | |
352 | "June", | |
353 | "July", | |
354 | "August", | |
355 | "September", | |
356 | "October", | |
357 | "November", | |
358 | "December", | |
359 | NULL, | |
360 | }; | |
361 | ||
362 | static const char *ampm[] = { | |
363 | "am", | |
364 | "pm", | |
365 | NULL | |
366 | }; | |
367 | ||
368 | /* | |
369 | * tm_year is relative this year */ | |
370 | const int tm_year_base = 1900; | |
371 | ||
372 | /* | |
373 | * Return TRUE iff `year' was a leap year. | |
374 | */ | |
375 | ||
376 | static int | |
377 | is_leap_year (int year) | |
378 | { | |
379 | return (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0); | |
380 | } | |
381 | ||
382 | static int | |
383 | match_string (const char **buf, const char **strs) | |
384 | { | |
385 | int i = 0; | |
386 | ||
387 | for (i = 0; strs[i] != NULL; ++i) { | |
388 | int len = strlen (strs[i]); | |
389 | ||
390 | if (strncasecmp (*buf, strs[i], len) == 0) { | |
391 | *buf += len; | |
392 | return i; | |
393 | } | |
394 | } | |
395 | return -1; | |
396 | } | |
397 | ||
398 | static int | |
399 | first_day (int year) | |
400 | { | |
401 | int ret = 4; | |
402 | ||
403 | for (; year > 1970; --year) | |
404 | ret = (ret + 365 + is_leap_year (year) ? 1 : 0) % 7; | |
405 | return ret; | |
406 | } | |
407 | ||
408 | /* | |
409 | * Set `timeptr' given `wnum' (week number [0, 53]) | |
410 | */ | |
411 | ||
412 | static void | |
413 | set_week_number_sun (struct tm *timeptr, int wnum) | |
414 | { | |
415 | int fday = first_day (timeptr->tm_year + tm_year_base); | |
416 | ||
417 | timeptr->tm_yday = wnum * 7 + timeptr->tm_wday - fday; | |
418 | if (timeptr->tm_yday < 0) { | |
419 | timeptr->tm_wday = fday; | |
420 | timeptr->tm_yday = 0; | |
421 | } | |
422 | } | |
423 | ||
424 | /* | |
425 | * Set `timeptr' given `wnum' (week number [0, 53]) | |
426 | */ | |
427 | ||
428 | static void | |
429 | set_week_number_mon (struct tm *timeptr, int wnum) | |
430 | { | |
431 | int fday = (first_day (timeptr->tm_year + tm_year_base) + 6) % 7; | |
432 | ||
433 | timeptr->tm_yday = wnum * 7 + (timeptr->tm_wday + 6) % 7 - fday; | |
434 | if (timeptr->tm_yday < 0) { | |
435 | timeptr->tm_wday = (fday + 1) % 7; | |
436 | timeptr->tm_yday = 0; | |
437 | } | |
438 | } | |
439 | ||
440 | /* | |
441 | * Set `timeptr' given `wnum' (week number [0, 53]) | |
442 | */ | |
443 | ||
444 | static void | |
445 | set_week_number_mon4 (struct tm *timeptr, int wnum) | |
446 | { | |
447 | int fday = (first_day (timeptr->tm_year + tm_year_base) + 6) % 7; | |
448 | int offset = 0; | |
449 | ||
450 | if (fday < 4) | |
451 | offset += 7; | |
452 | ||
453 | timeptr->tm_yday = offset + (wnum - 1) * 7 + timeptr->tm_wday - fday; | |
454 | if (timeptr->tm_yday < 0) { | |
455 | timeptr->tm_wday = fday; | |
456 | timeptr->tm_yday = 0; | |
457 | } | |
458 | } | |
459 | ||
460 | /* strptime: roken */ | |
461 | char * | |
462 | strptime (const char *buf, const char *format, struct tm *timeptr) | |
463 | { | |
464 | char c; | |
465 | ||
466 | for (; (c = *format) != '\0'; ++format) { | |
467 | char *s; | |
468 | int ret; | |
469 | ||
470 | if (isspace (c)) { | |
471 | while (isspace (*buf)) | |
472 | ++buf; | |
473 | } else if (c == '%' && format[1] != '\0') { | |
474 | c = *++format; | |
475 | if (c == 'E' || c == 'O') | |
476 | c = *++format; | |
477 | switch (c) { | |
478 | case 'A' : | |
479 | ret = match_string (&buf, full_weekdays); | |
480 | if (ret < 0) | |
481 | return NULL; | |
482 | timeptr->tm_wday = ret; | |
483 | break; | |
484 | case 'a' : | |
485 | ret = match_string (&buf, abb_weekdays); | |
486 | if (ret < 0) | |
487 | return NULL; | |
488 | timeptr->tm_wday = ret; | |
489 | break; | |
490 | case 'B' : | |
491 | ret = match_string (&buf, full_month); | |
492 | if (ret < 0) | |
493 | return NULL; | |
494 | timeptr->tm_mon = ret; | |
495 | break; | |
496 | case 'b' : | |
497 | case 'h' : | |
498 | ret = match_string (&buf, abb_month); | |
499 | if (ret < 0) | |
500 | return NULL; | |
501 | timeptr->tm_mon = ret; | |
502 | break; | |
503 | case 'C' : | |
504 | ret = strtol (buf, &s, 10); | |
505 | if (s == buf) | |
506 | return NULL; | |
507 | timeptr->tm_year = (ret * 100) - tm_year_base; | |
508 | buf = s; | |
509 | break; | |
510 | case 'c' : | |
511 | abort (); | |
512 | case 'D' : /* %m/%d/%y */ | |
513 | s = strptime (buf, "%m/%d/%y", timeptr); | |
514 | if (s == NULL) | |
515 | return NULL; | |
516 | buf = s; | |
517 | break; | |
518 | case 'd' : | |
519 | case 'e' : | |
520 | ret = strtol (buf, &s, 10); | |
521 | if (s == buf) | |
522 | return NULL; | |
523 | timeptr->tm_mday = ret; | |
524 | buf = s; | |
525 | break; | |
526 | case 'H' : | |
527 | case 'k' : | |
528 | ret = strtol (buf, &s, 10); | |
529 | if (s == buf) | |
530 | return NULL; | |
531 | timeptr->tm_hour = ret; | |
532 | buf = s; | |
533 | break; | |
534 | case 'I' : | |
535 | case 'l' : | |
536 | ret = strtol (buf, &s, 10); | |
537 | if (s == buf) | |
538 | return NULL; | |
539 | if (ret == 12) | |
540 | timeptr->tm_hour = 0; | |
541 | else | |
542 | timeptr->tm_hour = ret; | |
543 | buf = s; | |
544 | break; | |
545 | case 'j' : | |
546 | ret = strtol (buf, &s, 10); | |
547 | if (s == buf) | |
548 | return NULL; | |
549 | timeptr->tm_yday = ret - 1; | |
550 | buf = s; | |
551 | break; | |
552 | case 'm' : | |
553 | ret = strtol (buf, &s, 10); | |
554 | if (s == buf) | |
555 | return NULL; | |
556 | timeptr->tm_mon = ret - 1; | |
557 | buf = s; | |
558 | break; | |
559 | case 'M' : | |
560 | ret = strtol (buf, &s, 10); | |
561 | if (s == buf) | |
562 | return NULL; | |
563 | timeptr->tm_min = ret; | |
564 | buf = s; | |
565 | break; | |
566 | case 'n' : | |
567 | if (*buf == '\n') | |
568 | ++buf; | |
569 | else | |
570 | return NULL; | |
571 | break; | |
572 | case 'p' : | |
573 | ret = match_string (&buf, ampm); | |
574 | if (ret < 0) | |
575 | return NULL; | |
576 | if (timeptr->tm_hour == 0) { | |
577 | if (ret == 1) | |
578 | timeptr->tm_hour = 12; | |
579 | } else | |
580 | timeptr->tm_hour += 12; | |
581 | break; | |
582 | case 'r' : /* %I:%M:%S %p */ | |
583 | s = strptime (buf, "%I:%M:%S %p", timeptr); | |
584 | if (s == NULL) | |
585 | return NULL; | |
586 | buf = s; | |
587 | break; | |
588 | case 'R' : /* %H:%M */ | |
589 | s = strptime (buf, "%H:%M", timeptr); | |
590 | if (s == NULL) | |
591 | return NULL; | |
592 | buf = s; | |
593 | break; | |
594 | case 'S' : | |
595 | ret = strtol (buf, &s, 10); | |
596 | if (s == buf) | |
597 | return NULL; | |
598 | timeptr->tm_sec = ret; | |
599 | buf = s; | |
600 | break; | |
601 | case 't' : | |
602 | if (*buf == '\t') | |
603 | ++buf; | |
604 | else | |
605 | return NULL; | |
606 | break; | |
607 | case 'T' : /* %H:%M:%S */ | |
608 | case 'X' : | |
609 | s = strptime (buf, "%H:%M:%S", timeptr); | |
610 | if (s == NULL) | |
611 | return NULL; | |
612 | buf = s; | |
613 | break; | |
614 | case 'u' : | |
615 | ret = strtol (buf, &s, 10); | |
616 | if (s == buf) | |
617 | return NULL; | |
618 | timeptr->tm_wday = ret - 1; | |
619 | buf = s; | |
620 | break; | |
621 | case 'w' : | |
622 | ret = strtol (buf, &s, 10); | |
623 | if (s == buf) | |
624 | return NULL; | |
625 | timeptr->tm_wday = ret; | |
626 | buf = s; | |
627 | break; | |
628 | case 'U' : | |
629 | ret = strtol (buf, &s, 10); | |
630 | if (s == buf) | |
631 | return NULL; | |
632 | set_week_number_sun (timeptr, ret); | |
633 | buf = s; | |
634 | break; | |
635 | case 'V' : | |
636 | ret = strtol (buf, &s, 10); | |
637 | if (s == buf) | |
638 | return NULL; | |
639 | set_week_number_mon4 (timeptr, ret); | |
640 | buf = s; | |
641 | break; | |
642 | case 'W' : | |
643 | ret = strtol (buf, &s, 10); | |
644 | if (s == buf) | |
645 | return NULL; | |
646 | set_week_number_mon (timeptr, ret); | |
647 | buf = s; | |
648 | break; | |
649 | case 'x' : | |
650 | s = strptime (buf, "%Y:%m:%d", timeptr); | |
651 | if (s == NULL) | |
652 | return NULL; | |
653 | buf = s; | |
654 | break; | |
655 | case 'y' : | |
656 | ret = strtol (buf, &s, 10); | |
657 | if (s == buf) | |
658 | return NULL; | |
659 | if (ret < 70) | |
660 | timeptr->tm_year = 100 + ret; | |
661 | else | |
662 | timeptr->tm_year = ret; | |
663 | buf = s; | |
664 | break; | |
665 | case 'Y' : | |
666 | ret = strtol (buf, &s, 10); | |
667 | if (s == buf) | |
668 | return NULL; | |
669 | timeptr->tm_year = ret - tm_year_base; | |
670 | buf = s; | |
671 | break; | |
672 | case 'Z' : | |
673 | abort (); | |
674 | case '\0' : | |
675 | --format; | |
676 | /* FALLTHROUGH */ | |
677 | case '%' : | |
678 | if (*buf == '%') | |
679 | ++buf; | |
680 | else | |
681 | return NULL; | |
682 | break; | |
683 | default : | |
684 | if (*buf == '%' || *++buf == c) | |
685 | ++buf; | |
686 | else | |
687 | return NULL; | |
688 | break; | |
689 | } | |
690 | } else { | |
691 | if (*buf == c) | |
692 | ++buf; | |
693 | else | |
694 | return NULL; | |
695 | } | |
696 | } | |
697 | return (char *)buf; | |
d2b03e6a | 698 | } |