1 /** @file
2 
3   A brief file description
4 
5   @section license License
6 
7   Licensed to the Apache Software Foundation (ASF) under one
8   or more contributor license agreements.  See the NOTICE file
9   distributed with this work for additional information
10   regarding copyright ownership.  The ASF licenses this file
11   to you under the Apache License, Version 2.0 (the
12   "License"); you may not use this file except in compliance
13   with the License.  You may obtain a copy of the License at
14 
15       http://www.apache.org/licenses/LICENSE-2.0
16 
17   Unless required by applicable law or agreed to in writing, software
18   distributed under the License is distributed on an "AS IS" BASIS,
19   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20   See the License for the specific language governing permissions and
21   limitations under the License.
22  */
23 
24 /****************************************************************************
25 
26   Diags.cc
27 
28   This file contains code to manipulate run-time diagnostics, and print
29   warnings and errors at runtime.  Action tags and debugging tags are
30   supported, allowing run-time conditionals affecting diagnostics.
31 
32   Joe User should only need to use the macros at the bottom of Diags.h
33 
34 
35  ****************************************************************************/
36 
37 #include "tscore/BufferWriter.h"
38 #include "tscore/bwf_std_format.h"
39 #include "tscore/ink_platform.h"
40 #include "tscore/ink_memory.h"
41 #include "tscore/ink_defs.h"
42 #include "tscore/ink_error.h"
43 #include "tscore/ink_assert.h"
44 #include "tscore/ink_time.h"
45 #include "tscore/ink_hrtime.h"
46 #include "tscore/ink_thread.h"
47 #include "tscore/BufferWriter.h"
48 #include "tscore/Diags.h"
49 
50 int diags_on_for_plugins         = 0;
51 int DiagsConfigState::enabled[2] = {0, 0};
52 
53 // Global, used for all diagnostics
54 inkcoreapi Diags *diags = nullptr;
55 
56 static bool
location(const SourceLocation * loc,DiagsShowLocation show,DiagsLevel level)57 location(const SourceLocation *loc, DiagsShowLocation show, DiagsLevel level)
58 {
59   if (loc && loc->valid()) {
60     switch (show) {
61     case SHOW_LOCATION_ALL:
62       return true;
63     case SHOW_LOCATION_DEBUG:
64       return level <= DL_Debug;
65     default:
66       return false;
67     }
68   }
69 
70   return false;
71 }
72 
73 //////////////////////////////////////////////////////////////////////////////
74 //
75 //      Diags::Diags(char *bdt, char *bat)
76 //
77 //      This is the constructor for the Diags class.  The constructor takes
78 //      two strings called the "base debug tags" (bdt) and the
79 //      "base action tags" (bat).  These represent debug/action overrides,
80 //      to override the records.config values.  They current come from
81 //      command-line options.
82 //
83 //      If bdt is not nullptr, and not "", it overrides records.config settings.
84 //      If bat is not nullptr, and not "", it overrides records.config settings.
85 //
86 //      When the constructor is done, records.config callbacks will be set,
87 //      the initial values read, and the Diags instance will be ready to use.
88 //
89 //////////////////////////////////////////////////////////////////////////////
90 
Diags(std::string_view prefix_string,const char * bdt,const char * bat,BaseLogFile * _diags_log,int dl_perm,int ol_perm)91 Diags::Diags(std::string_view prefix_string, const char *bdt, const char *bat, BaseLogFile *_diags_log, int dl_perm, int ol_perm)
92   : diags_log(nullptr),
93     stdout_log(nullptr),
94     stderr_log(nullptr),
95     magic(DIAGS_MAGIC),
96     show_location(SHOW_LOCATION_NONE),
97     base_debug_tags(nullptr),
98     base_action_tags(nullptr),
99     prefix_str(prefix_string)
100 {
101   ink_release_assert(!prefix_str.empty());
102   int i;
103 
104   cleanup_func = nullptr;
105   ink_mutex_init(&tag_table_lock);
106 
107   ////////////////////////////////////////////////////////
108   // initialize the default, base debugging/action tags //
109   ////////////////////////////////////////////////////////
110 
111   if (bdt && *bdt) {
112     base_debug_tags = ats_strdup(bdt);
113   }
114   if (bat && *bat) {
115     base_action_tags = ats_strdup(bat);
116   }
117 
118   config.enabled[DiagsTagType_Debug]  = (base_debug_tags != nullptr);
119   config.enabled[DiagsTagType_Action] = (base_action_tags != nullptr);
120   diags_on_for_plugins                = config.enabled[DiagsTagType_Debug];
121 
122   // The caller must always provide a non-empty prefix.
123 
124   for (i = 0; i < DiagsLevel_Count; i++) {
125     config.outputs[i].to_stdout   = false;
126     config.outputs[i].to_stderr   = false;
127     config.outputs[i].to_syslog   = false;
128     config.outputs[i].to_diagslog = true;
129   }
130 
131   // create default stdout and stderr BaseLogFile objects
132   // (in case the user of this class doesn't specify in the future)
133   stdout_log = new BaseLogFile("stdout");
134   stderr_log = new BaseLogFile("stderr");
135   stdout_log->open_file(); // should never fail
136   stderr_log->open_file(); // should never fail
137 
138   //////////////////////////////////////////////////////////////////
139   // start off with empty tag tables, will build in reconfigure() //
140   //////////////////////////////////////////////////////////////////
141 
142   activated_tags[DiagsTagType_Debug]  = nullptr;
143   activated_tags[DiagsTagType_Action] = nullptr;
144 
145   outputlog_rolling_enabled  = RollingEnabledValues::NO_ROLLING;
146   outputlog_rolling_interval = -1;
147   outputlog_rolling_size     = -1;
148   diagslog_rolling_enabled   = RollingEnabledValues::NO_ROLLING;
149   diagslog_rolling_interval  = -1;
150   diagslog_rolling_size      = -1;
151 
152   outputlog_time_last_roll = time(nullptr);
153   diagslog_time_last_roll  = time(nullptr);
154 
155   diags_logfile_perm  = dl_perm;
156   output_logfile_perm = ol_perm;
157 
158   if (setup_diagslog(_diags_log)) {
159     diags_log = _diags_log;
160   }
161 }
162 
~Diags()163 Diags::~Diags()
164 {
165   if (diags_log) {
166     delete diags_log;
167     diags_log = nullptr;
168   }
169 
170   if (stdout_log) {
171     delete stdout_log;
172     stdout_log = nullptr;
173   }
174 
175   if (stderr_log) {
176     delete stderr_log;
177     stderr_log = nullptr;
178   }
179 
180   ats_free((void *)base_debug_tags);
181   ats_free((void *)base_action_tags);
182 
183   deactivate_all(DiagsTagType_Debug);
184   deactivate_all(DiagsTagType_Action);
185 }
186 
187 //////////////////////////////////////////////////////////////////////////////
188 //
189 //      void Diags::print_va(...)
190 //
191 //      This is the lowest-level diagnostic printing routine, that does the
192 //      work of formatting and outputting diagnostic and error messages,
193 //      in the standard format.
194 //
195 //      This routine takes an optional <debug_tag>, which is printed in
196 //      parentheses if its value is not nullptr.  It takes a <diags_level>,
197 //      which is converted to a prefix string.
198 //      print_va takes an optional source location structure pointer <loc>,
199 //      which can be nullptr.  If <loc> is not NULL, the source code location
200 //      is converted to a string, and printed between angle brackets.
201 //      Finally, it takes a printf format string <format_string>, and a
202 //      va_list list of varargs.
203 //
204 //      This routine outputs to all of the output targets enabled for this
205 //      debugging level in config.outputs[diags_level].  Many higher level
206 //      diagnostics printing routines are built upon print_va, including:
207 //
208 //              void print(...)
209 //              void log_va(...)
210 //              void log(...)
211 //
212 //////////////////////////////////////////////////////////////////////////////
213 
214 void
print_va(const char * debug_tag,DiagsLevel diags_level,const SourceLocation * loc,const char * format_string,va_list ap) const215 Diags::print_va(const char *debug_tag, DiagsLevel diags_level, const SourceLocation *loc, const char *format_string,
216                 va_list ap) const
217 {
218   ink_release_assert(diags_level < DiagsLevel_Count);
219   ts::LocalBufferWriter<1024> format_writer;
220 
221   // Save room for optional newline and terminating NUL bytes.
222   format_writer.clip(2);
223 
224   format_writer.print("[{timestamp}] ");
225   auto timestamp_offset = format_writer.size();
226 
227   format_writer.print("{thread-name}");
228   format_writer.print(" {}: ", level_name(diags_level));
229 
230   if (location(loc, show_location, diags_level)) {
231     format_writer.print("<{}> ", *loc);
232   }
233 
234   if (debug_tag) {
235     format_writer.print("({}) ", debug_tag);
236   }
237 
238   format_writer.print("{}", format_string);
239 
240   format_writer.extend(2);                   // restore the space for required termination.
241   if (format_writer.view().back() != '\n') { // safe because always some chars in the buffer.
242     format_writer.write('\n');
243   }
244   format_writer.write('\0');
245 
246   //////////////////////////////////////
247   // now, finally, output the message //
248   //////////////////////////////////////
249 
250   lock();
251   if (config.outputs[diags_level].to_diagslog) {
252     if (diags_log && diags_log->m_fp) {
253       va_list tmp;
254       va_copy(tmp, ap);
255       vfprintf(diags_log->m_fp, format_writer.data(), tmp);
256       va_end(tmp);
257     }
258   }
259 
260   if (config.outputs[diags_level].to_stdout) {
261     if (stdout_log && stdout_log->m_fp) {
262       va_list tmp;
263       va_copy(tmp, ap);
264       vfprintf(stdout_log->m_fp, format_writer.data(), tmp);
265       va_end(tmp);
266     }
267   }
268 
269   if (config.outputs[diags_level].to_stderr) {
270     if (stderr_log && stderr_log->m_fp) {
271       va_list tmp;
272       va_copy(tmp, ap);
273       vfprintf(stderr_log->m_fp, format_writer.data(), tmp);
274       va_end(tmp);
275     }
276   }
277 
278 #if !defined(freebsd)
279   unlock();
280 #endif
281 
282   if (config.outputs[diags_level].to_syslog) {
283     int priority;
284     char syslog_buffer[2048];
285 
286     switch (diags_level) {
287     case DL_Diag:
288     case DL_Debug:
289       priority = LOG_DEBUG;
290 
291       break;
292     case DL_Status:
293       priority = LOG_INFO;
294       break;
295     case DL_Note:
296       priority = LOG_NOTICE;
297       break;
298     case DL_Warning:
299       priority = LOG_WARNING;
300       break;
301     case DL_Error:
302       priority = LOG_ERR;
303       break;
304     case DL_Fatal:
305       priority = LOG_CRIT;
306       break;
307     case DL_Alert:
308       priority = LOG_ALERT;
309       break;
310     case DL_Emergency:
311       priority = LOG_EMERG;
312       break;
313     default:
314       priority = LOG_NOTICE;
315       break;
316     }
317     vsnprintf(syslog_buffer, sizeof(syslog_buffer), format_writer.data() + timestamp_offset, ap);
318     syslog(priority, "%s", syslog_buffer);
319   }
320 
321 #if defined(freebsd)
322   unlock();
323 #endif
324 }
325 
326 //////////////////////////////////////////////////////////////////////////////
327 //
328 //      bool Diags::tag_activated(char * tag, DiagsTagType mode)
329 //
330 //      This routine inquires if a particular <tag> in the tag table of
331 //      type <mode> is activated, returning true if it is, false if it
332 //      isn't.  If <tag> is nullptr, true is returned.  The call uses a lock
333 //      to get atomic access to the tag tables.
334 //
335 //////////////////////////////////////////////////////////////////////////////
336 
337 bool
tag_activated(const char * tag,DiagsTagType mode) const338 Diags::tag_activated(const char *tag, DiagsTagType mode) const
339 {
340   bool activated = false;
341 
342   if (tag == nullptr) {
343     return (true);
344   }
345 
346   lock();
347   if (activated_tags[mode]) {
348     activated = (activated_tags[mode]->match(tag) != -1);
349   }
350   unlock();
351 
352   return (activated);
353 }
354 
355 //////////////////////////////////////////////////////////////////////////////
356 //
357 //      void Diags::activate_taglist(char * taglist, DiagsTagType mode)
358 //
359 //      This routine adds all tags in the vertical-bar-separated taglist
360 //      to the tag table of type <mode>.  Each addition is done under a lock.
361 //      If an individual tag is already set, that tag is ignored.  If
362 //      <taglist> is nullptr, this routine exits immediately.
363 //
364 //////////////////////////////////////////////////////////////////////////////
365 
366 void
activate_taglist(const char * taglist,DiagsTagType mode)367 Diags::activate_taglist(const char *taglist, DiagsTagType mode)
368 {
369   if (taglist) {
370     lock();
371     if (activated_tags[mode]) {
372       delete activated_tags[mode];
373     }
374     activated_tags[mode] = new DFA;
375     activated_tags[mode]->compile(taglist);
376     unlock();
377   }
378 }
379 
380 //////////////////////////////////////////////////////////////////////////////
381 //
382 //      void Diags::deactivate_all(DiagsTagType mode)
383 //
384 //      This routine deactivates all tags in the tag table of type <mode>.
385 //      The deactivation is done under a lock.  When done, the taglist will
386 //      be empty.
387 //
388 //////////////////////////////////////////////////////////////////////////////
389 
390 void
deactivate_all(DiagsTagType mode)391 Diags::deactivate_all(DiagsTagType mode)
392 {
393   lock();
394   if (activated_tags[mode]) {
395     delete activated_tags[mode];
396     activated_tags[mode] = nullptr;
397   }
398   unlock();
399 }
400 
401 //////////////////////////////////////////////////////////////////////////////
402 //
403 //      const char *Diags::level_name(DiagsLevel dl)
404 //
405 //      This routine returns a string name corresponding to the error
406 //      level <dl>, suitable for us as an output log entry prefix.
407 //
408 //////////////////////////////////////////////////////////////////////////////
409 
410 const char *
level_name(DiagsLevel dl) const411 Diags::level_name(DiagsLevel dl) const
412 {
413   switch (dl) {
414   case DL_Diag:
415     return ("DIAG");
416   case DL_Debug:
417     return ("DEBUG");
418   case DL_Status:
419     return ("STATUS");
420   case DL_Note:
421     return ("NOTE");
422   case DL_Warning:
423     return ("WARNING");
424   case DL_Error:
425     return ("ERROR");
426   case DL_Fatal:
427     return ("FATAL");
428   case DL_Alert:
429     return ("ALERT");
430   case DL_Emergency:
431     return ("EMERGENCY");
432   default:
433     return ("DIAG");
434   }
435 }
436 
437 //////////////////////////////////////////////////////////////////////////////
438 //
439 //      void Diags::dump(FILE *fp)
440 //
441 //////////////////////////////////////////////////////////////////////////////
442 
443 void
dump(FILE * fp) const444 Diags::dump(FILE *fp) const
445 {
446   int i;
447 
448   fprintf(fp, "Diags:\n");
449   fprintf(fp, "  debug.enabled: %d\n", config.enabled[DiagsTagType_Debug]);
450   fprintf(fp, "  debug default tags: '%s'\n", (base_debug_tags ? base_debug_tags : "NULL"));
451   fprintf(fp, "  action.enabled: %d\n", config.enabled[DiagsTagType_Action]);
452   fprintf(fp, "  action default tags: '%s'\n", (base_action_tags ? base_action_tags : "NULL"));
453   fprintf(fp, "  outputs:\n");
454   for (i = 0; i < DiagsLevel_Count; i++) {
455     fprintf(fp, "    %10s [stdout=%d, stderr=%d, syslog=%d, diagslog=%d]\n", level_name(static_cast<DiagsLevel>(i)),
456             config.outputs[i].to_stdout, config.outputs[i].to_stderr, config.outputs[i].to_syslog, config.outputs[i].to_diagslog);
457   }
458 }
459 
460 void
error_va(DiagsLevel level,const SourceLocation * loc,const char * format_string,va_list ap) const461 Diags::error_va(DiagsLevel level, const SourceLocation *loc, const char *format_string, va_list ap) const
462 {
463   print_va(nullptr, level, loc, format_string, ap);
464 
465   if (DiagsLevel_IsTerminal(level)) {
466     va_list ap2;
467 
468     va_copy(ap2, ap);
469     if (cleanup_func) {
470       cleanup_func();
471     }
472 
473     // DL_Emergency means the process cannot recover from a reboot
474     if (level == DL_Emergency) {
475       ink_emergency_va(format_string, ap2);
476     } else {
477       ink_fatal_va(format_string, ap2);
478     }
479     va_end(ap2);
480   }
481 }
482 
483 /*
484  * Sets up and error handles the given BaseLogFile object to work
485  * with this instance of Diags.
486  *
487  * Returns true on success, false otherwise
488  */
489 bool
setup_diagslog(BaseLogFile * blf)490 Diags::setup_diagslog(BaseLogFile *blf)
491 {
492   if (blf != nullptr) {
493     if (blf->open_file(diags_logfile_perm) != BaseLogFile::LOG_FILE_NO_ERROR) {
494       log_log_error("Could not open diags log file: %s\n", strerror(errno));
495       delete blf;
496       return false;
497     }
498   }
499 
500   return true;
501 }
502 
503 void
config_roll_diagslog(RollingEnabledValues re,int ri,int rs)504 Diags::config_roll_diagslog(RollingEnabledValues re, int ri, int rs)
505 {
506   diagslog_rolling_enabled  = re;
507   diagslog_rolling_interval = ri;
508   diagslog_rolling_size     = rs;
509 }
510 
511 void
config_roll_outputlog(RollingEnabledValues re,int ri,int rs)512 Diags::config_roll_outputlog(RollingEnabledValues re, int ri, int rs)
513 {
514   outputlog_rolling_enabled  = re;
515   outputlog_rolling_interval = ri;
516   outputlog_rolling_size     = rs;
517 }
518 
519 /*
520  * Update diags_log to use the underlying file on disk.
521  *
522  * This function will replace the current BaseLogFile object with a new one, as
523  * each BaseLogFile object logically represents one file on disk. It can be
524  * used when we want to re-open the log file if the initial one was moved.
525  *
526  * Note that, however, cross process race conditions may still exist,
527  * especially with the metafile, and further work with flock() for fcntl() may
528  * still need to be done.
529  *
530  * Returns true if the log was reseated, false otherwise.
531  */
532 bool
reseat_diagslog()533 Diags::reseat_diagslog()
534 {
535   if (diags_log == nullptr || !diags_log->is_init()) {
536     return false;
537   }
538   fflush(diags_log->m_fp);
539   char *oldname = ats_strdup(diags_log->get_name());
540   log_log_trace("in %s for diags.log, oldname=%s\n", __func__, oldname);
541   BaseLogFile *n = new BaseLogFile(oldname);
542   if (setup_diagslog(n)) {
543     BaseLogFile *old_diags = diags_log;
544     lock();
545     diags_log = n;
546     unlock();
547     delete old_diags;
548   }
549   ats_free(oldname);
550   return true;
551 }
552 
553 /*
554  * Checks diags_log 's underlying file on disk and see if it needs to be rolled,
555  * and does so if necessary.
556  *
557  * This function will replace the current BaseLogFile object with a new one
558  * (if we choose to roll), as each BaseLogFile object logically represents one
559  * file on disk.
560  *
561  * Note that, however, cross process race conditions may still exist, especially with
562  * the metafile, and further work with flock() for fcntl() may still need to be done.
563  *
564  * Returns true if any logs rolled, false otherwise
565  */
566 bool
should_roll_diagslog()567 Diags::should_roll_diagslog()
568 {
569   bool ret_val = false;
570 
571   log_log_trace("%s was called\n", __func__);
572   log_log_trace("%s: rolling_enabled = %d, output_rolling_size = %d, output_rolling_interval = %d\n", __func__,
573                 diagslog_rolling_enabled, diagslog_rolling_size, diagslog_rolling_interval);
574   log_log_trace("%s: RollingEnabledValues::ROLL_ON_TIME = %d\n", __func__, RollingEnabledValues::ROLL_ON_TIME);
575   log_log_trace("%s: time(0) - last_roll_time = %d\n", __func__, time(nullptr) - diagslog_time_last_roll);
576 
577   // Roll diags_log if necessary
578   if (diags_log && diags_log->is_init()) {
579     if (diagslog_rolling_enabled == RollingEnabledValues::ROLL_ON_SIZE ||
580         diagslog_rolling_enabled == RollingEnabledValues::ROLL_ON_TIME_OR_SIZE) {
581       // if we can't even check the file, we can forget about rotating
582       struct stat buf;
583       if (fstat(fileno(diags_log->m_fp), &buf) != 0) {
584         return false;
585       }
586 
587       off_t size = buf.st_size;
588       if (diagslog_rolling_size != -1 && size >= (static_cast<off_t>(diagslog_rolling_size) * BYTES_IN_MB)) {
589         fflush(diags_log->m_fp);
590         if (diags_log->roll()) {
591           char *oldname = ats_strdup(diags_log->get_name());
592           log_log_trace("in %s for diags.log, oldname=%s\n", __func__, oldname);
593           BaseLogFile *n = new BaseLogFile(oldname);
594           if (setup_diagslog(n)) {
595             BaseLogFile *old_diags = diags_log;
596             lock();
597             diags_log = n;
598             unlock();
599             delete old_diags;
600           }
601           ats_free(oldname);
602           ret_val = true;
603         }
604       }
605     }
606 
607     if (diagslog_rolling_enabled == RollingEnabledValues::ROLL_ON_TIME ||
608         diagslog_rolling_enabled == RollingEnabledValues::ROLL_ON_TIME_OR_SIZE) {
609       time_t now = time(nullptr);
610       if (diagslog_rolling_interval != -1 && (now - diagslog_time_last_roll) >= diagslog_rolling_interval) {
611         fflush(diags_log->m_fp);
612         if (diags_log->roll()) {
613           diagslog_time_last_roll = now;
614           char *oldname           = ats_strdup(diags_log->get_name());
615           log_log_trace("in %s for diags.log, oldname=%s\n", __func__, oldname);
616           BaseLogFile *n = new BaseLogFile(oldname);
617           if (setup_diagslog(n)) {
618             BaseLogFile *old_diags = diags_log;
619             lock();
620             diags_log = n;
621             unlock();
622             delete old_diags;
623           }
624           ats_free(oldname);
625           ret_val = true;
626         }
627       }
628     }
629   }
630 
631   return ret_val;
632 }
633 
634 /*
635  * Checks stdout_log and stderr_log if their underlying files on disk need to be
636  * rolled, and does so if necessary.
637  *
638  * This function will replace the current BaseLogFile objects with a
639  * new one (if we choose to roll), as each BaseLogFile object logically
640  * represents one file on disk
641  *
642  * Note that, however, cross process race conditions may still exist, especially with
643  * the metafile, and further work with flock() for fcntl() may still need to be done.
644  *
645  * Returns true if any logs rolled, false otherwise
646  */
647 bool
should_roll_outputlog()648 Diags::should_roll_outputlog()
649 {
650   // stdout_log and stderr_log should never be nullptr as this point in time
651   ink_assert(stdout_log != nullptr);
652   ink_assert(stderr_log != nullptr);
653 
654   bool ret_val              = false;
655   bool need_consider_stderr = true;
656 
657   log_log_trace("%s was called\n", __func__);
658   log_log_trace("%s: rolling_enabled = %d, output_rolling_size = %d, output_rolling_interval = %d\n", __func__,
659                 outputlog_rolling_enabled, outputlog_rolling_size, outputlog_rolling_interval);
660   log_log_trace("%s: RollingEnabledValues::ROLL_ON_TIME = %d\n", __func__, RollingEnabledValues::ROLL_ON_TIME);
661   log_log_trace("%s: time(0) - last_roll_time = %d\n", __func__, time(nullptr) - outputlog_time_last_roll);
662   log_log_trace("%s: stdout_log = %p\n", __func__, stdout_log);
663 
664   // Roll stdout_log if necessary
665   if (stdout_log->is_init()) {
666     if (outputlog_rolling_enabled == RollingEnabledValues::ROLL_ON_SIZE ||
667         outputlog_rolling_enabled == RollingEnabledValues::ROLL_ON_TIME_OR_SIZE) {
668       // if we can't even check the file, we can forget about rotating
669       struct stat buf;
670       if (fstat(fileno(stdout_log->m_fp), &buf) != 0) {
671         return false;
672       }
673 
674       off_t size = buf.st_size;
675       if (outputlog_rolling_size != -1 && size >= static_cast<off_t>(outputlog_rolling_size) * BYTES_IN_MB) {
676         // since usually stdout and stderr are the same file on disk, we should just
677         // play it safe and just flush both BaseLogFiles
678         if (stderr_log->is_init()) {
679           fflush(stderr_log->m_fp);
680         }
681         fflush(stdout_log->m_fp);
682 
683         if (stdout_log->roll()) {
684           char *oldname = ats_strdup(stdout_log->get_name());
685           log_log_trace("in %s(), oldname=%s\n", __func__, oldname);
686           set_std_output(StdStream::STDOUT, oldname);
687 
688           // if stderr and stdout are redirected to the same place, we should
689           // update the stderr_log object as well
690           if (!strcmp(oldname, stderr_log->get_name())) {
691             log_log_trace("oldname == stderr_log->get_name()\n");
692             set_std_output(StdStream::STDERR, oldname);
693             need_consider_stderr = false;
694           }
695           ats_free(oldname);
696           ret_val = true;
697         }
698       }
699     }
700 
701     if (outputlog_rolling_enabled == RollingEnabledValues::ROLL_ON_TIME ||
702         outputlog_rolling_enabled == RollingEnabledValues::ROLL_ON_TIME_OR_SIZE) {
703       time_t now = time(nullptr);
704       if (outputlog_rolling_interval != -1 && (now - outputlog_time_last_roll) >= outputlog_rolling_interval) {
705         // since usually stdout and stderr are the same file on disk, we should just
706         // play it safe and just flush both BaseLogFiles
707         if (stderr_log->is_init()) {
708           fflush(stderr_log->m_fp);
709         }
710         fflush(stdout_log->m_fp);
711 
712         if (stdout_log->roll()) {
713           outputlog_time_last_roll = now;
714           char *oldname            = ats_strdup(stdout_log->get_name());
715           log_log_trace("in %s, oldname=%s\n", __func__, oldname);
716           set_std_output(StdStream::STDOUT, oldname);
717 
718           // if stderr and stdout are redirected to the same place, we should
719           // update the stderr_log object as well
720           if (!strcmp(oldname, stderr_log->get_name())) {
721             log_log_trace("oldname == stderr_log->get_name()\n");
722             set_std_output(StdStream::STDERR, oldname);
723             need_consider_stderr = false;
724           }
725           ats_free(oldname);
726           ret_val = true;
727         }
728       }
729     }
730   }
731 
732   // This assertion has to be true since log rolling for traffic.out is only ever enabled
733   // (and useful) when traffic_server is NOT running in stand alone mode. If traffic_server
734   // is NOT running in stand alone mode, then stderr and stdout SHOULD ALWAYS be pointing
735   // to the same file (traffic.out).
736   //
737   // If for some reason, someone wants the feature to have stdout pointing to some file on
738   // disk, and stderr pointing to a different file on disk, and then also wants both files to
739   // rotate according to the (same || different) scheme, it would not be difficult to add
740   // some more config options in records.config and said feature into this function.
741   if (ret_val) {
742     ink_assert(!need_consider_stderr);
743   }
744 
745   return ret_val;
746 }
747 
748 /*
749  * Sets up a BaseLogFile for the specified file. Then it binds the specified standard steam
750  * to the aforementioned BaseLogFile.
751  *
752  * Returns true on successful binding and setup, false otherwise
753  */
754 bool
set_std_output(StdStream stream,const char * file)755 Diags::set_std_output(StdStream stream, const char *file)
756 {
757   const char *target_stream;
758   BaseLogFile **current;
759   BaseLogFile *old_log, *new_log;
760 
761   // If the caller is stupid, we give up
762   if (strcmp(file, "") == 0) {
763     return false;
764   }
765 
766   // Figure out which standard stream we want to redirect
767   if (stream == StdStream::STDOUT) {
768     target_stream = "stdout";
769     current       = &stdout_log;
770   } else {
771     target_stream = "stderr";
772     current       = &stderr_log;
773   }
774   (void)target_stream; // silence clang-analyzer for now
775   old_log = *current;
776   new_log = new BaseLogFile(file);
777 
778   // On any errors we quit
779   if (!new_log || new_log->open_file(output_logfile_perm) != BaseLogFile::LOG_FILE_NO_ERROR) {
780     log_log_error("[Warning]: unable to open file=%s to bind %s to\n", file, target_stream);
781     log_log_error("[Warning]: %s is currently not bound to anything\n", target_stream);
782     delete new_log;
783     lock();
784     *current = nullptr;
785     unlock();
786     return false;
787   }
788   if (!new_log->is_open()) {
789     log_log_error("[Warning]: file pointer for %s %s = nullptr\n", target_stream, file);
790     log_log_error("[Warning]: %s is currently not bound to anything\n", target_stream);
791     delete new_log;
792     lock();
793     *current = nullptr;
794     unlock();
795     return false;
796   }
797 
798   // Now exchange the pointer to the standard stream in question
799   lock();
800   *current = new_log;
801   bool ret = rebind_std_stream(stream, fileno(new_log->m_fp));
802   unlock();
803 
804   // Free the BaseLogFile we rotated out
805   if (old_log) {
806     delete old_log;
807   }
808 
809   // "this should never happen"^{TM}
810   ink_release_assert(ret);
811 
812   return ret;
813 }
814 
815 /*
816  * Helper function that rebinds a specified stream to specified file descriptor
817  *
818  * Returns true on success, false otherwise
819  */
820 bool
rebind_std_stream(StdStream stream,int new_fd)821 Diags::rebind_std_stream(StdStream stream, int new_fd)
822 {
823   const char *target_stream;
824   int stream_fd;
825 
826   // Figure out which stream to dup2
827   if (stream == StdStream::STDOUT) {
828     target_stream = "stdout";
829     stream_fd     = STDOUT_FILENO;
830   } else {
831     target_stream = "stderr";
832     stream_fd     = STDERR_FILENO;
833   }
834   (void)target_stream; // silence clang-analyzer for now
835 
836   if (new_fd < 0) {
837     log_log_error("[Warning]: TS unable to bind %s to new file descriptor=%d", target_stream, new_fd);
838   } else {
839     dup2(new_fd, stream_fd);
840     return true;
841   }
842   return false;
843 }
844