1 /** @file
2 
3   Base class for log files implementation
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 #include "tscore/BaseLogFile.h"
25 
26 /*
27  * This constructor creates a BaseLogFile based on a given name.
28  * This is the most common way BaseLogFiles are created.
29  */
BaseLogFile(const char * name)30 BaseLogFile::BaseLogFile(const char *name) : m_name(ats_strdup(name))
31 {
32   log_log_trace("exiting BaseLogFile constructor, m_name=%s, this=%p\n", m_name.get(), this);
33 }
34 
35 /*
36  * This constructor creates a BaseLogFile based on a given name.
37  * Similar to above constructor, but is overloaded with the object signature
38  */
BaseLogFile(const char * name,uint64_t sig)39 BaseLogFile::BaseLogFile(const char *name, uint64_t sig) : m_name(ats_strdup(name)), m_signature(sig), m_has_signature(true)
40 {
41   log_log_trace("exiting BaseLogFile signature constructor, m_name=%s, m_signature=%ld, this=%p\n", m_name.get(), m_signature,
42                 this);
43 }
44 
45 /*
46  * This copy constructor creates a BaseLogFile based on a given copy.
47  */
BaseLogFile(const BaseLogFile & copy)48 BaseLogFile::BaseLogFile(const BaseLogFile &copy)
49   : m_start_time(copy.m_start_time),
50 
51     m_name(ats_strdup(copy.m_name)),
52     m_hostname(ats_strdup(copy.m_hostname)),
53 
54     m_is_init(copy.m_is_init),
55 
56     m_signature(copy.m_signature),
57     m_has_signature(copy.m_has_signature)
58 {
59   log_log_trace("exiting BaseLogFile copy constructor, m_name=%s, this=%p\n", m_name.get(), this);
60 }
61 
62 /*
63  * Destructor.
64  */
~BaseLogFile()65 BaseLogFile::~BaseLogFile()
66 {
67   log_log_trace("entering BaseLogFile destructor, m_name=%s, this=%p\n", m_name.get(), this);
68 
69   if (m_is_regfile) {
70     close_file();
71   } else {
72     log_log_trace("not a regular file, not closing, m_name=%s, this=%p\n", m_name.get(), this);
73   }
74 
75   log_log_trace("exiting BaseLogFile destructor, this=%p\n", this);
76 }
77 
78 /*
79  * This function is called by a client of BaseLogFile to roll the underlying
80  * file  The tricky part to this routine is in coming up with the new file name,
81  * which contains the bounding timestamp interval for the entries
82  * within the file.
83 
84  * Under normal operating conditions, this BaseLogFile object was in existence
85  * for all writes to the file.  In this case, the LogFile members m_start_time
86  * and m_end_time will have the starting and ending times for the actual
87  * entries written to the file.
88 
89  * On restart situations, it is possible to re-open an existing BaseLogFile,
90  * which means that the m_start_time variable will be later than the actual
91  * entries recorded in the file.  In this case, we'll use the creation time
92  * of the file, which should be recorded in the meta-information located on
93  * disk.
94 
95  * If we can't use the meta-file, either because it's not there or because
96  * it's not valid, then we'll use timestamp 0 (Jan 1, 1970) as the starting
97  * bound.
98 
99  * Return 1 if file rolled, 0 otherwise
100  */
101 int
roll(long interval_start,long interval_end)102 BaseLogFile::roll(long interval_start, long interval_end)
103 {
104   // First, let's see if a roll is even needed.
105   if (m_name == nullptr || !BaseLogFile::exists(m_name.get())) {
106     log_log_trace("Roll not needed for %s; file doesn't exist\n", (m_name.get()) ? m_name.get() : "no_name\n");
107     return 0;
108   }
109 
110   // Then, check if this object is backing a regular file
111   if (!m_is_regfile) {
112     log_log_trace("Roll not needed for %s; not regular file\n", (m_name.get()) ? m_name.get() : "no_name\n");
113     return 0;
114   }
115 
116   // Read meta info if needed (if file was not opened)
117   if (!m_meta_info) {
118     m_meta_info = new BaseMetaInfo(m_name.get());
119   }
120 
121   // Create the new file name, which consists of a timestamp and rolled
122   // extension added to the previous file name.  The timestamp format is
123   // ".%Y%m%d.%Hh%Mm%Ss-%Y%m%d.%Hh%Mm%Ss", where the two date/time values
124   // represent the starting and ending times for entries in the rolled
125   // log file.  In addition, we add the hostname.  So, the entire rolled
126   // format is something like:
127   //
128   //    "squid.log.mymachine.19980712.12h00m00s-19980713.12h00m00s.old"
129   char roll_name[LOGFILE_ROLL_MAXPATHLEN];
130   char start_time_ext[64];
131   char end_time_ext[64];
132   time_t start, end;
133 
134   // Start with conservative values for the start and end bounds, then
135   // try to refine.
136   start = 0L;
137   end   = (interval_end >= m_end_time) ? interval_end : m_end_time;
138 
139   if (m_meta_info->data_from_metafile()) {
140     // If the metadata came from the metafile, this means that
141     // the file was preexisting, so we can't use m_start_time for
142     // our starting bounds.  Instead, we'll try to use the file
143     // creation time stored in the metafile (if it's valid and we can
144     // read it).  If all else fails, we'll use 0 for the start time.
145     log_log_trace("in BaseLogFile::roll(..) used metadata starttime\n");
146     m_meta_info->get_creation_time(&start);
147   } else {
148     // The logfile was not preexisting (normal case), so we'll use
149     // earlier of the interval start time and the m_start_time.
150     //
151     // note that m_start_time is not the time of the first
152     // transaction, but the time of the creation of the first log
153     // buffer used by the file. These times may be different,
154     // especially under light load, and using the m_start_time may
155     // produce overlapping filenames (the problem is that we have
156     // no easy way of keeping track of the timestamp of the first
157     // transaction
158     log_log_trace("in BaseLogFile::roll(..), didn't use metadata starttime, used earliest available starttime\n");
159     if (interval_start == 0) {
160       start = m_start_time;
161     } else {
162       start = (m_start_time < interval_start) ? m_start_time : interval_start;
163     }
164   }
165   log_log_trace("in BaseLogFile::roll(..), start = %ld, m_start_time = %ld, interval_start = %ld\n", start, m_start_time,
166                 interval_start);
167 
168   // Now that we have our timestamp values, convert them to the proper
169   // timestamp formats and create the rolled file name.
170   timestamp_to_str(static_cast<long>(start), start_time_ext, sizeof(start_time_ext));
171   timestamp_to_str(static_cast<long>(end), end_time_ext, sizeof(start_time_ext));
172   snprintf(roll_name, LOGFILE_ROLL_MAXPATHLEN, "%s%s%s.%s-%s%s", m_name.get(), (m_hostname.get() ? LOGFILE_SEPARATOR_STRING : ""),
173            (m_hostname.get() ? m_hostname.get() : ""), start_time_ext, end_time_ext, LOGFILE_ROLLED_EXTENSION);
174 
175   // It may be possible that the file we want to roll into already
176   // exists.  If so, then we need to add a version tag to the rolled
177   // filename as well so that we don't clobber existing files.
178   int version = 1;
179   while (BaseLogFile::exists(roll_name)) {
180     log_log_trace("The rolled file %s already exists; adding version "
181                   "tag %d to avoid clobbering the existing file.\n",
182                   roll_name, version);
183     snprintf(roll_name, LOGFILE_ROLL_MAXPATHLEN, "%s%s%s.%s-%s.%d%s", m_name.get(),
184              (m_hostname.get() ? LOGFILE_SEPARATOR_STRING : ""), (m_hostname.get() ? m_hostname.get() : ""), start_time_ext,
185              end_time_ext, version, LOGFILE_ROLLED_EXTENSION);
186     ++version;
187   }
188 
189   // It's now safe to rename the file.
190   if (::rename(m_name.get(), roll_name) < 0) {
191     log_log_error("Traffic Server could not rename logfile %s to %s, error %d: "
192                   "%s.\n",
193                   m_name.get(), roll_name, errno, strerror(errno));
194     return 0;
195   }
196 
197   // reset m_start_time
198   m_start_time    = 0;
199   m_bytes_written = 0;
200 
201   log_log_trace("The logfile %s was rolled to %s.\n", m_name.get(), roll_name);
202 
203   return 1;
204 }
205 
206 /*
207  * The more convenient rolling function. Intended use is for less
208  * critical logs such as diags.log or traffic.out, since _exact_
209  * timestamps may be less important
210  *
211  * The function calls roll(long,long) with these parameters:
212  * Start time is either 0 or creation time stored in the metafile,
213  * whichever is greater
214  *
215  * End time is the current time
216  *
217  * Returns 1 on success, 0 otherwise
218  */
219 int
roll()220 BaseLogFile::roll()
221 {
222   time_t start;
223   time_t now = time(nullptr);
224 
225   if (!m_meta_info || !m_meta_info->get_creation_time(&start)) {
226     start = 0L;
227   }
228 
229   return roll(start, now);
230 }
231 
232 /*
233  * This function will return true if the given filename corresponds to a
234  * rolled logfile.  We make this determination based on the file extension.
235  */
236 bool
rolled_logfile(char * path)237 BaseLogFile::rolled_logfile(char *path)
238 {
239   const int target_len = static_cast<int>(strlen(LOGFILE_ROLLED_EXTENSION));
240   int len              = static_cast<int>(strlen(path));
241   if (len > target_len) {
242     char *str = &path[len - target_len];
243     if (!strcmp(str, LOGFILE_ROLLED_EXTENSION)) {
244       return true;
245     }
246   }
247   return false;
248 }
249 
250 /*
251  * Returns if the provided file in 'pathname' exists or not
252  */
253 bool
exists(const char * pathname)254 BaseLogFile::exists(const char *pathname)
255 {
256   ink_assert(pathname != nullptr);
257   return (pathname && ::access(pathname, F_OK) == 0);
258 }
259 
260 /*
261  * Opens the BaseLogFile and associated BaseMetaInfo on disk if it exists
262  * Returns relevant exit status
263  */
264 int
open_file(int perm)265 BaseLogFile::open_file(int perm)
266 {
267   log_log_trace("BaseLogFile: entered open_file()\n");
268   if (is_open()) {
269     return LOG_FILE_NO_ERROR;
270   }
271 
272   if (!m_name.get()) {
273     log_log_error("BaseLogFile: m_name is nullptr, aborting open_file()\n");
274     return LOG_FILE_COULD_NOT_OPEN_FILE;
275   } else if (!strcmp(m_name.get(), "stdout")) {
276     log_log_trace("BaseLogFile: stdout opened\n");
277     m_fp      = stdout;
278     m_is_init = true;
279     return LOG_FILE_NO_ERROR;
280   } else if (!strcmp(m_name.get(), "stderr")) {
281     log_log_trace("BaseLogFile: stderr opened\n");
282     m_fp      = stderr;
283     m_is_init = true;
284     return LOG_FILE_NO_ERROR;
285   }
286 
287   // means this object is representing a real file on disk
288   m_is_regfile = true;
289 
290   // Check to see if the file exists BEFORE we try to open it, since
291   // opening it will also create it.
292   bool file_exists = BaseLogFile::exists(m_name.get());
293 
294   if (file_exists) {
295     if (!m_meta_info) {
296       // This object must be fresh since it has not built its MetaInfo
297       // so we create a new MetaInfo object that will read right away
298       // (in the constructor) the corresponding metafile
299       m_meta_info = new BaseMetaInfo(m_name.get());
300     }
301   } else {
302     // The log file does not exist, so we create a new MetaInfo object
303     //  which will save itself to disk right away (in the constructor)
304     if (m_has_signature) {
305       m_meta_info = new BaseMetaInfo(m_name.get(), static_cast<long>(time(nullptr)), m_signature);
306     } else {
307       m_meta_info = new BaseMetaInfo(m_name.get(), static_cast<long>(time(nullptr)));
308     }
309   }
310 
311   // open actual log file (not metainfo)
312   log_log_trace("BaseLogFile: attempting to open %s\n", m_name.get());
313 
314   m_fp = elevating_fopen(m_name.get(), "a+");
315 
316   // error check
317   if (m_fp) {
318     setlinebuf(m_fp);
319   } else {
320     log_log_error("Error opening log file %s: %s\n", m_name.get(), strerror(errno));
321     log_log_error("Actual error: %s\n", (errno == EINVAL ? "einval" : "something else"));
322     return LOG_FILE_COULD_NOT_OPEN_FILE;
323   }
324 
325   // set permissions if necessary
326   if (perm != -1) {
327     // means LogFile passed in some permissions we need to set
328     log_log_trace("BaseLogFile attempting to change %s's permissions to %o\n", m_name.get(), perm);
329     if (elevating_chmod(m_name.get(), perm) != 0) {
330       log_log_error("Error changing logfile=%s permissions: %s\n", m_name.get(), strerror(errno));
331     }
332   }
333 
334   // set m_bytes_written to force the rolling based on file size.
335   m_bytes_written = fseek(m_fp, 0, SEEK_CUR);
336 
337   log_log_trace("BaseLogFile %s is now open (fd=%d)\n", m_name.get(), fileno(m_fp));
338   m_is_init = true;
339   return LOG_FILE_NO_ERROR;
340 }
341 
342 /**
343  * @brief Close the managed log file.
344  *
345  * @note This closes the actual log file, not its metainfo.
346  *
347  * @return The result of calling fclose on the file descriptor or 0 if the file
348  * was not open. If the result is non-zero, fclose failed and errno is set
349  * appropriately.
350  */
351 int
close_file()352 BaseLogFile::close_file()
353 {
354   int ret = 0;
355   if (is_open()) {
356     log_log_trace("BaseLogFile %s is closed\n", m_name.get());
357 
358     // Both log_log_trace and fclose may set errno. Thus, keep fclose after
359     // log_log_trace so that by the time this function completes if errno was
360     // set by fclose it will remain upon function return.
361     ret       = fclose(m_fp);
362     m_fp      = nullptr;
363     m_is_init = false;
364     delete m_meta_info;
365     m_meta_info = nullptr;
366   }
367   return ret;
368 }
369 
370 /*
371  * Changes names of the actual log file (not metainfo)
372  */
373 void
change_name(const char * new_name)374 BaseLogFile::change_name(const char *new_name)
375 {
376   m_name = ats_strdup(new_name);
377 }
378 
379 void
display(FILE * fd)380 BaseLogFile::display(FILE *fd)
381 {
382   fprintf(fd, "Logfile: %s, %s\n", get_name(), (is_open()) ? "file is open" : "file is not open");
383 }
384 
385 /*
386  * Lowest level internal logging facility for BaseLogFile
387  *
388  * Since BaseLogFiles can potentially be created before the bootstrap
389  * instance of Diags is ready, we cannot simply call something like Debug().
390  * However, we still need to log the creation of BaseLogFile, since the
391  * information is still useful. This function will print out log messages
392  * into traffic.out if we happen to be bootstrapping Diags. Since
393  * traffic_manager redirects stdout/stderr into traffic.out, that
394  * redirection is inherited by way of exec()/fork() all the way here.
395  *
396  * TODO use Debug() for non bootstrap instances
397  */
398 void
log_log(LogLogPriorityLevel priority,const char * format,...)399 BaseLogFile::log_log(LogLogPriorityLevel priority, const char *format, ...)
400 {
401   va_list args;
402 
403   const char *priority_name = nullptr;
404   FILE *output              = stdout;
405   switch (priority) {
406   case LL_Debug:
407     priority_name = "DEBUG";
408     break;
409   case LL_Note:
410     priority_name = "NOTE";
411     break;
412   case LL_Warning:
413     priority_name = "WARNING";
414     output        = stderr;
415     break;
416   case LL_Error:
417     priority_name = "ERROR";
418     output        = stderr;
419     break;
420   case LL_Fatal:
421     priority_name = "FATAL";
422     output        = stderr;
423     break;
424   default:
425     priority_name = "unknown priority";
426   }
427 
428   va_start(args, format);
429   struct timeval now;
430   double now_f;
431 
432   gettimeofday(&now, nullptr);
433   now_f = now.tv_sec + now.tv_usec / 1000000.0f;
434 
435   fprintf(output, "<%.4f> [%s]: ", now_f, priority_name);
436   vfprintf(output, format, args);
437   fflush(output);
438 
439   va_end(args);
440 }
441 
442 /****************************************************************************
443 
444   BaseMetaInfo methods
445 
446 *****************************************************************************/
447 
448 /*
449  * Given a file name (with or without the full path, but without the extension name),
450  * this function creates appends the ".meta" extension and stores it in the local
451  * variable
452  */
453 void
_build_name(const char * filename)454 BaseMetaInfo::_build_name(const char *filename)
455 {
456   int i = -1, l = 0;
457   char c;
458   while (c = filename[l], c != 0) {
459     if (c == '/') {
460       i = l;
461     }
462     ++l;
463   }
464 
465   // 7 = 1 (dot at beginning) + 5 (".meta") + 1 (null terminating)
466   //
467   _filename = static_cast<char *>(ats_malloc(l + 7));
468 
469   if (i < 0) {
470     ink_string_concatenate_strings(_filename, ".", filename, ".meta", nullptr);
471   } else {
472     memcpy(_filename, filename, i + 1);
473     ink_string_concatenate_strings(&_filename[i + 1], ".", &filename[i + 1], ".meta", nullptr);
474   }
475 }
476 
477 /*
478  * Reads in meta info into the local variables
479  */
480 void
_read_from_file()481 BaseMetaInfo::_read_from_file()
482 {
483   _flags |= DATA_FROM_METAFILE; // mark attempt
484   int fd = elevating_open(_filename, O_RDONLY);
485   if (fd < 0) {
486     log_log_error("Could not open metafile %s for reading: %s\n", _filename, strerror(errno));
487   } else {
488     _flags |= FILE_OPEN_SUCCESSFUL;
489     SimpleTokenizer tok('=', SimpleTokenizer::OVERWRITE_INPUT_STRING);
490     int line_number = 1;
491     while (ink_file_fd_readline(fd, BUF_SIZE, _buffer) > 0) {
492       tok.setString(_buffer);
493       char *t = tok.getNext();
494       if (t) {
495         if (strcmp(t, "creation_time") == 0) {
496           t = tok.getNext();
497           if (t) {
498             _creation_time = static_cast<time_t>(ink_atoi64(t));
499             _flags |= VALID_CREATION_TIME;
500           }
501         } else if (strcmp(t, "object_signature") == 0) {
502           t = tok.getNext();
503           if (t) {
504             _log_object_signature = ink_atoi64(t);
505             _flags |= VALID_SIGNATURE;
506             log_log_trace("BaseMetaInfo::_read_from_file\n"
507                           "\tfilename = %s\n"
508                           "\tsignature string = %s\n"
509                           "\tsignature value = %" PRIu64 "\n",
510                           _filename, t, _log_object_signature);
511           }
512         } else if (line_number == 1) {
513           ink_release_assert(!"no panda support");
514         }
515       }
516       ++line_number;
517     }
518     close(fd);
519   }
520 }
521 
522 /*
523  * Writes out metadata info onto disk
524  */
525 void
_write_to_file()526 BaseMetaInfo::_write_to_file()
527 {
528   int fd = elevating_open(_filename, O_WRONLY | O_CREAT | O_TRUNC, LOGFILE_DEFAULT_PERMS);
529   if (fd < 0) {
530     log_log_error("Could not open metafile %s for writing: %s\n", _filename, strerror(errno));
531     return;
532   }
533   log_log_trace("Successfully opened metafile=%s\n", _filename);
534 
535   int n;
536   if (_flags & VALID_CREATION_TIME) {
537     log_log_trace("Writing creation time to %s\n", _filename);
538     n = snprintf(_buffer, BUF_SIZE, "creation_time = %lu\n", static_cast<unsigned long>(_creation_time));
539     // TODO modify this runtime check so that it is not an assertion
540     ink_release_assert(n <= BUF_SIZE);
541     if (write(fd, _buffer, n) == -1) {
542       log_log_trace("Could not write creation_time");
543     }
544   }
545 
546   if (_flags & VALID_SIGNATURE) {
547     log_log_trace("Writing signature to %s\n", _filename);
548     n = snprintf(_buffer, BUF_SIZE, "object_signature = %" PRIu64 "\n", _log_object_signature);
549     // TODO modify this runtime check so that it is not an assertion
550     ink_release_assert(n <= BUF_SIZE);
551     if (write(fd, _buffer, n) == -1) {
552       log_log_error("Could not write object_signature\n");
553     }
554     log_log_trace("BaseMetaInfo::_write_to_file\n"
555                   "\tfilename = %s\n"
556                   "\tsignature value = %" PRIu64 "\n"
557                   "\tsignature string = %s\n",
558                   _filename, _log_object_signature, _buffer);
559   }
560 
561   close(fd);
562 }
563 
564 /*
565  * This routine will convert a timestamp (seconds) into a short string,
566  * of the format "%Y%m%d.%Hh%Mm%Ss".
567  *
568  * Since the resulting buffer is passed in, this routine is thread-safe.
569  * Return value is the number of characters placed into the array, not
570  * including the nullptr.
571  */
572 
573 int
timestamp_to_str(long timestamp,char * buf,int size)574 BaseLogFile::timestamp_to_str(long timestamp, char *buf, int size)
575 {
576   static const char *format_str = "%Y%m%d.%Hh%Mm%Ss";
577   struct tm res;
578   struct tm *tms;
579   tms = ink_localtime_r((const time_t *)&timestamp, &res);
580   return strftime(buf, size, format_str, tms);
581 }
582