]> git.ipfire.org Git - thirdparty/squid.git/blame - src/tools.cc
ANSIFY
[thirdparty/squid.git] / src / tools.cc
CommitLineData
94e891ea 1
30a4f2a8 2/*
fedac7e5 3 * $Id: tools.cc,v 1.56 1996/09/12 22:18:01 wessels Exp $
30a4f2a8 4 *
5 * DEBUG: section 21 Misc Functions
6 * AUTHOR: Harvest Derived
7 *
8 * SQUID Internet Object Cache http://www.nlanr.net/Squid/
9 * --------------------------------------------------------
10 *
11 * Squid is the result of efforts by numerous individuals from the
12 * Internet community. Development is led by Duane Wessels of the
13 * National Laboratory for Applied Network Research and funded by
14 * the National Science Foundation.
15 *
16 * This program is free software; you can redistribute it and/or modify
17 * it under the terms of the GNU General Public License as published by
18 * the Free Software Foundation; either version 2 of the License, or
19 * (at your option) any later version.
20 *
21 * This program is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 * GNU General Public License for more details.
25 *
26 * You should have received a copy of the GNU General Public License
27 * along with this program; if not, write to the Free Software
28 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
29 *
30 */
019dd986 31
32/*
30a4f2a8 33 * Copyright (c) 1994, 1995. All rights reserved.
34 *
35 * The Harvest software was developed by the Internet Research Task
36 * Force Research Group on Resource Discovery (IRTF-RD):
37 *
38 * Mic Bowman of Transarc Corporation.
39 * Peter Danzig of the University of Southern California.
40 * Darren R. Hardy of the University of Colorado at Boulder.
41 * Udi Manber of the University of Arizona.
42 * Michael F. Schwartz of the University of Colorado at Boulder.
43 * Duane Wessels of the University of Colorado at Boulder.
44 *
45 * This copyright notice applies to software in the Harvest
46 * ``src/'' directory only. Users should consult the individual
47 * copyright notices in the ``components/'' subdirectories for
48 * copyright information about other software bundled with the
49 * Harvest source code distribution.
50 *
51 * TERMS OF USE
52 *
53 * The Harvest software may be used and re-distributed without
54 * charge, provided that the software origin and research team are
55 * cited in any use of the system. Most commonly this is
56 * accomplished by including a link to the Harvest Home Page
57 * (http://harvest.cs.colorado.edu/) from the query page of any
58 * Broker you deploy, as well as in the query result pages. These
59 * links are generated automatically by the standard Broker
60 * software distribution.
61 *
62 * The Harvest software is provided ``as is'', without express or
63 * implied warranty, and with no support nor obligation to assist
64 * in its use, correction, modification or enhancement. We assume
65 * no liability with respect to the infringement of copyrights,
66 * trade secrets, or any patents, and are not responsible for
67 * consequential damages. Proper use of the Harvest software is
68 * entirely the responsibility of the user.
69 *
70 * DERIVATIVE WORKS
71 *
72 * Users may make derivative works from the Harvest software, subject
73 * to the following constraints:
74 *
75 * - You must include the above copyright notice and these
76 * accompanying paragraphs in all forms of derivative works,
77 * and any documentation and other materials related to such
78 * distribution and use acknowledge that the software was
79 * developed at the above institutions.
80 *
81 * - You must notify IRTF-RD regarding your distribution of
82 * the derivative work.
83 *
84 * - You must clearly notify users that your are distributing
85 * a modified version and not the original Harvest software.
86 *
87 * - Any derivative product is also subject to these copyright
88 * and use restrictions.
89 *
90 * Note that the Harvest software is NOT in the public domain. We
91 * retain copyright, as specified above.
92 *
93 * HISTORY OF FREE SOFTWARE STATUS
94 *
95 * Originally we required sites to license the software in cases
96 * where they were going to build commercial products/services
97 * around Harvest. In June 1995 we changed this policy. We now
98 * allow people to use the core Harvest software (the code found in
99 * the Harvest ``src/'' directory) for free. We made this change
100 * in the interest of encouraging the widest possible deployment of
101 * the technology. The Harvest software is really a reference
102 * implementation of a set of protocols and formats, some of which
103 * we intend to standardize. We encourage commercial
104 * re-implementations of code complying to this set of standards.
019dd986 105 */
44a47c6e 106
107#include "squid.h"
108
090089c4 109int do_mallinfo = 0; /* don't do mallinfo() unless this gets set */
b8de7ebe 110time_t squid_curtime;
3a57089e 111struct timeval current_time;
090089c4 112
090089c4 113#define DEAD_MSG "\
c5c666ab 114The Squid Cache (version %s) died.\n\
090089c4 115\n\
c5c666ab 116You've encountered a fatal error in the Squid Cache version %s.\n\
090089c4 117If a core file was created (possibly in the swap directory),\n\
b8de7ebe 118please execute 'gdb squid core' or 'dbx squid core', then type 'where',\n\
234967c9 119and report the trace back to squid-bugs@nlanr.net.\n\
090089c4 120\n\
121Thanks!\n"
122
123static char *dead_msg()
124{
95d659f0 125 LOCAL_ARRAY(char, msg, 1024);
8213067d 126 sprintf(msg, DEAD_MSG, version_string, version_string);
090089c4 127 return msg;
128}
129
130void mail_warranty()
131{
019dd986 132 FILE *fp = NULL;
6e40f263 133 char *filename;
134 static char command[256];
135 if ((filename = tempnam(NULL, appname)) == NULL)
136 return;
137 if ((fp = fopen(filename, "w")) == NULL)
138 return;
139 fprintf(fp, "From: %s\n", appname);
edae7bc0 140 fprintf(fp, "To: %s\n", Config.adminEmail);
6e40f263 141 fprintf(fp, "Subject: %s\n", dead_msg());
142 fclose(fp);
edae7bc0 143 sprintf(command, "mail %s < %s", Config.adminEmail, filename);
6e40f263 144 system(command); /* XXX should avoid system(3) */
145 unlink(filename);
090089c4 146}
147
3f43b19b 148static void dumpMallocStats(f)
149 FILE *f;
150{
151#if HAVE_MALLINFO
152 struct mallinfo mp;
30a4f2a8 153 int t;
3f43b19b 154 if (!do_mallinfo)
155 return;
3f43b19b 156 mp = mallinfo();
30a4f2a8 157 fprintf(f, "Memory usage for %s via mallinfo():\n", appname);
158 fprintf(f, "\ttotal space in arena: %6d KB\n",
159 mp.arena >> 10);
160 fprintf(f, "\tOrdinary blocks: %6d KB %6d blks\n",
161 mp.uordblks >> 10, mp.ordblks);
162 fprintf(f, "\tSmall blocks: %6d KB %6d blks\n",
163 mp.usmblks >> 10, mp.smblks);
164 fprintf(f, "\tHolding blocks: %6d KB %6d blks\n",
165 mp.hblkhd >> 10, mp.hblks);
166 fprintf(f, "\tFree Small blocks: %6d KB\n",
167 mp.fsmblks >> 10);
168 fprintf(f, "\tFree Ordinary blocks: %6d KB\n",
169 mp.fordblks >> 10);
170 t = mp.uordblks + mp.usmblks + mp.hblkhd;
171 fprintf(f, "\tTotal in use: %6d KB %d%%\n",
172 t >> 10, percent(t, mp.arena));
173 t = mp.fsmblks + mp.fordblks;
174 fprintf(f, "\tTotal free: %6d KB %d%%\n",
175 t >> 10, percent(t, mp.arena));
176#ifdef WE_DONT_USE_KEEP
177 fprintf(f, "\tKeep option: %6d KB\n",
178 mp.keepcost >> 10);
179#endif
46c883ed 180#if HAVE_EXT_MALLINFO
30a4f2a8 181 fprintf(f, "\tmax size of small blocks:\t%d\n",
182 mp.mxfast);
183 fprintf(f, "\tnumber of small blocks in a holding block:\t%d\n",
3f43b19b 184 mp.nlblks);
30a4f2a8 185 fprintf(f, "\tsmall block rounding factor:\t%d\n",
186 mp.grain);
187 fprintf(f, "\tspace (including overhead) allocated in ord. blks:\t%d\n",
3f43b19b 188 mp.uordbytes);
30a4f2a8 189 fprintf(f, "\tnumber of ordinary blocks allocated:\t%d\n",
3f43b19b 190 mp.allocated);
30a4f2a8 191 fprintf(f, "\tbytes used in maintaining the free tree:\t%d\n",
3f43b19b 192 mp.treeoverhead);
46c883ed 193#endif /* HAVE_EXT_MALLINFO */
3f43b19b 194#if PRINT_MMAP
195 mallocmap();
196#endif /* PRINT_MMAP */
197#endif /* HAVE_MALLINFO */
198}
30a4f2a8 199
3f43b19b 200static int PrintRusage(f, lf)
f0f81709 201 void (*f) ();
202 FILE *lf;
203{
30a4f2a8 204#if HAVE_GETRUSAGE && defined(RUSAGE_SELF)
f0f81709 205 struct rusage rusage;
206 getrusage(RUSAGE_SELF, &rusage);
30a4f2a8 207 fprintf(lf, "CPU Usage: user %d sys %d\n",
208 (int) rusage.ru_utime.tv_sec, (int) rusage.ru_stime.tv_sec);
209 fprintf(lf, "Memory Usage: rss %ld KB\n",
210 rusage.ru_maxrss * getpagesize() >> 10);
211 fprintf(lf, "Page faults with physical i/o: %ld\n",
f0f81709 212 rusage.ru_majflt);
213#endif
214 dumpMallocStats(lf);
215 if (f)
216 f(0);
217 return 0;
218}
219
0e08ce4b 220void death(sig)
12b9e9b1 221 int sig;
44a47c6e 222{
0e08ce4b 223 if (sig == SIGSEGV)
2c47cf74 224 fprintf(debug_log, "FATAL: Received Segment Violation...dying.\n");
0e08ce4b 225 else if (sig == SIGBUS)
6e40f263 226 fprintf(debug_log, "FATAL: Received Bus Error...dying.\n");
0e08ce4b 227 else
2c47cf74 228 fprintf(debug_log, "FATAL: Received signal %d...dying.\n", sig);
6e40f263 229#if SA_RESETHAND == 0
44a47c6e 230 signal(SIGSEGV, SIG_DFL);
231 signal(SIGBUS, SIG_DFL);
0e08ce4b 232 signal(sig, SIG_DFL);
30a4f2a8 233#endif
44a47c6e 234 storeWriteCleanLog();
2c47cf74 235 PrintRusage(NULL, debug_log);
6e40f263 236 if (squid_curtime - SQUID_RELEASE_TIME < 864000) {
237 /* skip if more than 10 days old */
edae7bc0 238 if (Config.adminEmail)
6e40f263 239 mail_warranty();
240 else
241 puts(dead_msg());
242 }
44a47c6e 243 abort();
244}
245
246
30a4f2a8 247void sigusr2_handle(sig)
090089c4 248 int sig;
249{
30a4f2a8 250 static int state = 0;
251 debug(21, 1, "sigusr2_handle: SIGUSR2 received.\n");
252 if (state == 0) {
b6f794d6 253 _db_init(Config.Log.log, "ALL,10");
30a4f2a8 254 state = 1;
255 } else {
b6f794d6 256 _db_init(Config.Log.log, Config.debugOptions);
30a4f2a8 257 state = 0;
258 }
259#if !HAVE_SIGACTION
260 signal(sig, sigusr2_handle); /* reinstall */
090089c4 261#endif
262}
263
30a4f2a8 264void setSocketShutdownLifetimes()
265{
266 FD_ENTRY *f = NULL;
b6f794d6 267 int lft = Config.lifetimeShutdown;
30a4f2a8 268 int cur;
269 int i;
270 for (i = fdstat_biggest_fd(); i >= 0; i--) {
271 f = &fd_table[i];
272 if (!f->read_handler && !f->write_handler && !f->except_handler)
273 continue;
274 if (fdstatGetType(i) != FD_SOCKET)
275 continue;
276 cur = comm_get_fd_lifetime(i);
277 if (cur > 0 && (cur - squid_curtime) <= lft)
278 continue;
279 comm_set_fd_lifetime(i, lft);
280 }
281}
282
4d64d74a 283void normal_shutdown()
284{
285 debug(21, 1, "Shutting down...\n");
b6f794d6 286 if (Config.pidFilename) {
30a4f2a8 287 enter_suid();
b6f794d6 288 safeunlink(Config.pidFilename, 0);
30a4f2a8 289 leave_suid();
234967c9 290 }
4d64d74a 291 storeWriteCleanLog();
292 PrintRusage(NULL, debug_log);
c5c666ab 293 debug(21, 0, "Squid Cache (Version %s): Exiting normally.\n",
8213067d 294 version_string);
457bedbd 295 storeCloseLog();
296 statCloseLog();
297 fclose(debug_log);
4d64d74a 298 exit(0);
299}
147d3115 300
090089c4 301void fatal_common(message)
302 char *message;
303{
db40ae20 304#if HAVE_SYSLOG
6e40f263 305 if (opt_syslog_enable)
090089c4 306 syslog(LOG_ALERT, message);
db40ae20 307#endif
2c47cf74 308 fprintf(debug_log, "FATAL: %s\n", message);
c5c666ab 309 fprintf(debug_log, "Squid Cache (Version %s): Terminated abnormally.\n",
8213067d 310 version_string);
2c47cf74 311 fflush(debug_log);
312 PrintRusage(NULL, debug_log);
090089c4 313}
314
315/* fatal */
316void fatal(message)
317 char *message;
318{
319 fatal_common(message);
320 exit(1);
321}
322
323/* fatal with dumping core */
324void fatal_dump(message)
325 char *message;
326{
327 if (message)
328 fatal_common(message);
1758c627 329 if (opt_catch_signals)
090089c4 330 storeWriteCleanLog();
331 abort();
332}
333
85b701ed 334/* fatal with dumping core */
335void _debug_trap(message)
336 char *message;
337{
338 if (opt_catch_signals)
339 fatal_dump(message);
fedac7e5 340 _db_print(0, 0, "WARNING: %s\n", message);
85b701ed 341}
342
090089c4 343void sig_child(sig)
344 int sig;
345{
30a4f2a8 346#ifdef _SQUID_NEXT_
347 union wait status;
348#else
090089c4 349 int status;
090089c4 350#endif
30a4f2a8 351 int pid;
090089c4 352
30a4f2a8 353 do {
354#ifdef _SQUID_NEXT_
355 pid = wait3(&status, WNOHANG, NULL);
090089c4 356#else
30a4f2a8 357 pid = waitpid(-1, &status, WNOHANG);
090089c4 358#endif
30a4f2a8 359 debug(21, 3, "sig_child: Ate pid %d\n", pid);
360#if HAVE_SIGACTION
361 } while (pid > 0);
234967c9 362#else
30a4f2a8 363 } while (pid > 0 || (pid < 0 && errno == EINTR));
364 signal(sig, sig_child);
234967c9 365#endif
30a4f2a8 366}
44a47c6e 367
368char *getMyHostname()
369{
95d659f0 370 LOCAL_ARRAY(char, host, SQUIDHOSTNAMELEN + 1);
44a47c6e 371 static int present = 0;
372 struct hostent *h = NULL;
019dd986 373 char *t = NULL;
374
b6f794d6 375 if ((t = Config.visibleHostname))
019dd986 376 return t;
44a47c6e 377
378 /* Get the host name and store it in host to return */
379 if (!present) {
380 host[0] = '\0';
381 if (gethostname(host, SQUIDHOSTNAMELEN) == -1) {
019dd986 382 debug(21, 1, "getMyHostname: gethostname failed: %s\n",
44a47c6e 383 xstrerror());
384 return NULL;
385 } else {
30a4f2a8 386 if ((h = ipcache_gethostbyname(host, IP_BLOCKING_LOOKUP)) != NULL) {
44a47c6e 387 /* DNS lookup successful */
388 /* use the official name from DNS lookup */
389 strcpy(host, h->h_name);
390 }
391 present = 1;
392 }
393 }
394 return host;
395}
396
397int safeunlink(s, quiet)
398 char *s;
399 int quiet;
400{
401 int err;
402 if ((err = unlink(s)) < 0)
403 if (!quiet)
019dd986 404 debug(21, 1, "safeunlink: Couldn't delete %s. %s\n", s, xstrerror());
44a47c6e 405 return (err);
406}
12b9e9b1 407
30a4f2a8 408/* leave a privilegied section. (Give up any privilegies)
409 * Routines that need privilegies can rap themselves in enter_suid()
410 * and leave_suid()
411 * To give upp all posibilites to gain privilegies use no_suid()
12b9e9b1 412 */
30a4f2a8 413void leave_suid()
12b9e9b1 414{
415 struct passwd *pwd = NULL;
416 struct group *grp = NULL;
30a4f2a8 417 debug(21, 3, "leave_suid: PID %d called\n", getpid());
12b9e9b1 418 if (geteuid() != 0)
419 return;
420 /* Started as a root, check suid option */
b6f794d6 421 if (Config.effectiveUser == NULL)
12b9e9b1 422 return;
b6f794d6 423 if ((pwd = getpwnam(Config.effectiveUser)) == NULL)
12b9e9b1 424 return;
b6f794d6 425 if (Config.effectiveGroup && (grp = getgrnam(Config.effectiveGroup))) {
12b9e9b1 426 setgid(grp->gr_gid);
427 } else {
428 setgid(pwd->pw_gid);
429 }
30a4f2a8 430 debug(21, 3, "leave_suid: PID %d giving up root, becoming '%s'\n",
431 getpid(), pwd->pw_name);
234967c9 432#if HAVE_SETRESUID
433 setresuid(pwd->pw_uid, pwd->pw_uid, 0);
434#elif HAVE_SETEUID
435 seteuid(pwd->pw_uid);
436#else
12b9e9b1 437 setuid(pwd->pw_uid);
234967c9 438#endif
439}
440
30a4f2a8 441/* Enter a privilegied section */
442void enter_suid()
234967c9 443{
30a4f2a8 444 debug(21, 3, "enter_suid: PID %d taking root priveleges\n", getpid());
234967c9 445#if HAVE_SETRESUID
446 setresuid(-1, 0, -1);
447#else
448 setuid(0);
449#endif
450}
451
30a4f2a8 452/* Give up the posibility to gain privilegies.
453 * this should be used before starting a sub process
454 */
234967c9 455void no_suid()
456{
457 uid_t uid;
30a4f2a8 458 leave_suid();
234967c9 459 uid = geteuid();
30a4f2a8 460 debug(21, 3, "leave_suid: PID %d giving up root priveleges forever\n", getpid());
234967c9 461#if HAVE_SETRESUID
462 setresuid(uid, uid, uid);
463#else
464 setuid(0);
465 setuid(uid);
466#endif
12b9e9b1 467}
ccff9601 468
469void writePidFile()
470{
471 FILE *pid_fp = NULL;
472 char *f = NULL;
473
b6f794d6 474 if ((f = Config.pidFilename) == NULL)
ccff9601 475 return;
30a4f2a8 476 enter_suid();
477 pid_fp = fopen(f, "w");
478 leave_suid();
479 if (pid_fp != NULL) {
480 fprintf(pid_fp, "%d\n", (int) getpid());
481 fclose(pid_fp);
482 } else {
019dd986 483 debug(21, 0, "WARNING: Could not write pid file\n");
484 debug(21, 0, " %s: %s\n", f, xstrerror());
ccff9601 485 }
ccff9601 486}
c4fb974e 487
488
7690e8eb 489int readPidFile()
490{
491 FILE *pid_fp = NULL;
492 char *f = NULL;
493 int pid = -1;
494
495 if ((f = Config.pidFilename) == NULL) {
496 fprintf(stderr, "%s: ERROR: No pid file name defined\n", appname);
497 exit(1);
498 }
499 pid_fp = fopen(f, "r");
500 if (pid_fp != NULL) {
501 if (fscanf(pid_fp, "%d", &pid) != 1)
502 pid = 0;
503 fclose(pid_fp);
504 } else {
505 if (errno != ENOENT) {
506 fprintf(stderr, "%s: ERROR: Could not read pid file\n", appname);
507 fprintf(stderr, "\t%s: %s\n", f, xstrerror());
508 exit(1);
509 }
510 }
511 return pid;
512}
513
514
c4fb974e 515void setMaxFD()
516{
234967c9 517#if HAVE_SETRLIMIT
c4fb974e 518 /* try to use as many file descriptors as possible */
519 /* System V uses RLIMIT_NOFILE and BSD uses RLIMIT_OFILE */
520 struct rlimit rl;
521#if defined(RLIMIT_NOFILE)
522 if (getrlimit(RLIMIT_NOFILE, &rl) < 0) {
a8f7d3ee 523 debug(21, 0, "setrlimit: RLIMIT_NOFILE: %s\n", xstrerror());
c4fb974e 524 } else {
234967c9 525 rl.rlim_cur = FD_SETSIZE;
30a4f2a8 526 if (rl.rlim_cur > rl.rlim_max)
527 rl.rlim_cur = rl.rlim_max;
c4fb974e 528 if (setrlimit(RLIMIT_NOFILE, &rl) < 0) {
234967c9 529 sprintf(tmp_error_buf, "setrlimit: RLIMIT_NOFILE: %s", xstrerror());
530 fatal_dump(tmp_error_buf);
c4fb974e 531 }
532 }
533#elif defined(RLIMIT_OFILE)
534 if (getrlimit(RLIMIT_OFILE, &rl) < 0) {
a8f7d3ee 535 debug(21, 0, "setrlimit: RLIMIT_NOFILE: %s\n", xstrerror());
c4fb974e 536 } else {
234967c9 537 rl.rlim_cur = FD_SETSIZE;
30a4f2a8 538 if (rl.rlim_cur > rl.rlim_max)
539 rl.rlim_cur = rl.rlim_max;
c4fb974e 540 if (setrlimit(RLIMIT_OFILE, &rl) < 0) {
234967c9 541 sprintf(tmp_error_buf, "setrlimit: RLIMIT_OFILE: %s", xstrerror());
542 fatal_dump(tmp_error_buf);
c4fb974e 543 }
544 }
545#endif
c4fb974e 546#else /* HAVE_SETRLIMIT */
a8f7d3ee 547 debug(21, 1, "setMaxFD: Cannot increase: setrlimit() not supported on this system\n");
30a4f2a8 548#endif /* HAVE_SETRLIMIT */
549
550#if HAVE_SETRLIMIT && defined(RLIMIT_DATA)
551 if (getrlimit(RLIMIT_DATA, &rl) < 0) {
a8f7d3ee 552 debug(21, 0, "getrlimit: RLIMIT_DATA: %s\n", xstrerror());
30a4f2a8 553 } else {
554 rl.rlim_cur = rl.rlim_max; /* set it to the max */
555 if (setrlimit(RLIMIT_DATA, &rl) < 0) {
556 sprintf(tmp_error_buf, "setrlimit: RLIMIT_DATA: %s", xstrerror());
557 fatal_dump(tmp_error_buf);
558 }
559 }
560#endif /* RLIMIT_DATA */
c4fb974e 561}
3a57089e 562
563time_t getCurrentTime()
564{
94e891ea 565#if GETTIMEOFDAY_NO_TZP
30a4f2a8 566 gettimeofday(&current_time);
567#else
d598947a 568 gettimeofday(&current_time, NULL);
5f3f8d0e 569#endif
30a4f2a8 570 return squid_curtime = current_time.tv_sec;
5f3f8d0e 571}
fe113054 572
a0bbd6c8 573int tvSubMsec(t1, t2)
574 struct timeval t1;
575 struct timeval t2;
fe113054 576{
577 return (t2.tv_sec - t1.tv_sec) * 1000 +
a0bbd6c8 578 (t2.tv_usec - t1.tv_usec) / 1000;
fe113054 579}
30a4f2a8 580
581int percent(a, b)
582 int a;
583 int b;
584{
585 return b ? ((int) (100.0 * a / b + 0.5)) : 0;
586}
587
588void squid_signal(sig, func, flags)
589 int sig;
590 void (*func) ();
591 int flags;
592{
593#if HAVE_SIGACTION
594 struct sigaction sa;
595 sa.sa_handler = func;
596 sa.sa_flags = flags;
597 sigemptyset(&sa.sa_mask);
598 if (sigaction(sig, &sa, NULL) < 0)
599 debug(1, 0, "sigaction: sig=%d func=%p: %s\n", sig, func, xstrerror());
600#else
601 (void) signal(sig, func);
602#endif
603}
396c5745 604
605char *accessLogTime(t)
5e79098a 606 time_t t;
396c5745 607{
5e79098a 608 struct tm *tm;
609 static char buf[128];
610 static char last_t = 0;
611 if (t != last_t) {
612 tm = localtime(&t);
613 strftime(buf, 127, "%y/%m/%d %T", tm);
614 last_t = t;
615 }
616 return buf;
396c5745 617}