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 #include "tscore/ink_platform.h"
25 #include "tscore/ink_memory.h"
26 #include "tscore/ink_file.h"
27 #include "tscore/I_Layout.h"
28 #include "tscore/Filenames.h"
29 #include "DiagsConfig.h"
30 #include "records/P_RecCore.h"
31 
32 //////////////////////////////////////////////////////////////////////////////
33 //
34 //      void reconfigure_diags()
35 //
36 //      This function extracts the current diags configuration settings from
37 //      records.config, and rebuilds the Diags data structures.
38 //
39 //////////////////////////////////////////////////////////////////////////////
40 
41 void
reconfigure_diags()42 DiagsConfig::reconfigure_diags()
43 {
44   int i, e;
45   char *p, *dt, *at;
46   DiagsConfigState c;
47   bool found, all_found;
48 
49   static struct {
50     const char *config_name;
51     DiagsLevel level;
52   } output_records[] = {
53     {"proxy.config.diags.output.diag", DL_Diag},           {"proxy.config.diags.output.debug", DL_Debug},
54     {"proxy.config.diags.output.status", DL_Status},       {"proxy.config.diags.output.note", DL_Note},
55     {"proxy.config.diags.output.warning", DL_Warning},     {"proxy.config.diags.output.error", DL_Error},
56     {"proxy.config.diags.output.fatal", DL_Fatal},         {"proxy.config.diags.output.alert", DL_Alert},
57     {"proxy.config.diags.output.emergency", DL_Emergency}, {nullptr, DL_Undefined},
58   };
59 
60   if (!callbacks_established) {
61     register_diags_callbacks();
62   }
63   ////////////////////////////////////////////
64   // extract relevant records.config values //
65   ////////////////////////////////////////////
66 
67   all_found = true;
68 
69   // initial value set to 0 or 1 based on command line tags
70   c.enabled[DiagsTagType_Debug]  = (diags->base_debug_tags != nullptr);
71   c.enabled[DiagsTagType_Action] = (diags->base_action_tags != nullptr);
72 
73   // enabled if records.config set
74 
75   e = static_cast<int>(REC_readInteger("proxy.config.diags.debug.enabled", &found));
76   if (e && found) {
77     c.enabled[DiagsTagType_Debug] = e; // implement OR logic
78   }
79   all_found = all_found && found;
80 
81   e = static_cast<int>(REC_readInteger("proxy.config.diags.action.enabled", &found));
82   if (e && found) {
83     c.enabled[DiagsTagType_Action] = true; // implement OR logic
84   }
85   all_found = all_found && found;
86 
87   e                    = static_cast<int>(REC_readInteger("proxy.config.diags.show_location", &found));
88   diags->show_location = ((e == 1 && found) ? SHOW_LOCATION_DEBUG : ((e == 2 && found) ? SHOW_LOCATION_ALL : SHOW_LOCATION_NONE));
89   all_found            = all_found && found;
90 
91   // read output routing values
92   for (i = 0;; i++) {
93     const char *record_name = output_records[i].config_name;
94     DiagsLevel l            = output_records[i].level;
95 
96     if (!record_name) {
97       break;
98     }
99 
100     p         = REC_readString(record_name, &found);
101     all_found = all_found && found;
102 
103     if (found) {
104       parse_output_string(p, &(c.outputs[l]));
105       ats_free(p);
106     } else {
107       Error("can't find config variable '%s'", record_name);
108     }
109   }
110 
111   p         = REC_readString("proxy.config.diags.debug.tags", &found);
112   dt        = (found ? p : nullptr); // NOTE: needs to be freed
113   all_found = all_found && found;
114 
115   p         = REC_readString("proxy.config.diags.action.tags", &found);
116   at        = (found ? p : nullptr); // NOTE: needs to be freed
117   all_found = all_found && found;
118 
119   ///////////////////////////////////////////////////////////////////
120   // if couldn't read all values, return without changing config,  //
121   // otherwise rebuild taglists and change the diags config values //
122   ///////////////////////////////////////////////////////////////////
123 
124   if (!all_found) {
125     Error("couldn't fetch all proxy.config.diags values");
126   } else {
127     //////////////////////////////
128     // clear out old tag tables //
129     //////////////////////////////
130 
131     diags->deactivate_all(DiagsTagType_Debug);
132     diags->deactivate_all(DiagsTagType_Action);
133 
134     //////////////////////////////////////////////////////////////////////
135     // add new tag tables from records.config or command line overrides //
136     //////////////////////////////////////////////////////////////////////
137 
138     diags->activate_taglist((diags->base_debug_tags ? diags->base_debug_tags : dt), DiagsTagType_Debug);
139     diags->activate_taglist((diags->base_action_tags ? diags->base_action_tags : at), DiagsTagType_Action);
140 
141 ////////////////////////////////////
142 // change the diags config values //
143 ////////////////////////////////////
144 #if !defined(__GNUC__)
145     diags->config = c;
146 #else
147     memcpy(((void *)&diags->config), ((void *)&c), sizeof(DiagsConfigState));
148 #endif
149     Note("updated diags config");
150   }
151 
152   ////////////////////////////////////
153   // free the record.config strings //
154   ////////////////////////////////////
155   ats_free(dt);
156   ats_free(at);
157 }
158 
159 //////////////////////////////////////////////////////////////////////////////
160 //
161 //      static void *diags_config_callback(void *opaque_token, void *data)
162 //
163 //      This is the records.config registration callback that is called
164 //      when any diags value is changed.  Each time a diags value changes
165 //      the entire diags state is reconfigured.
166 //
167 //////////////////////////////////////////////////////////////////////////////
168 static int
diags_config_callback(const char *,RecDataT,RecData,void * opaque_token)169 diags_config_callback(const char * /* name ATS_UNUSED */, RecDataT /* data_type ATS_UNUSED */, RecData /* data ATS_UNUSED */,
170                       void *opaque_token)
171 {
172   DiagsConfig *diagsConfig;
173 
174   diagsConfig = static_cast<DiagsConfig *>(opaque_token);
175   ink_assert(diags->magic == DIAGS_MAGIC);
176   diagsConfig->reconfigure_diags();
177   return (0);
178 }
179 
180 //////////////////////////////////////////////////////////////////////////////
181 //
182 //      void Diags::parse_output_string(char *s, DiagsModeOutput *o)
183 //
184 //      This routine converts a diags outpur routing string <s> to the
185 //      internal DiagsModeOutput structure.  Currently there are 4 possible
186 //      routing destinations:
187 //              O  stdout
188 //              E  stderr
189 //              S  syslog
190 //              L  diags.log
191 //
192 //////////////////////////////////////////////////////////////////////////////
193 
194 void
parse_output_string(char * s,DiagsModeOutput * o)195 DiagsConfig::parse_output_string(char *s, DiagsModeOutput *o)
196 {
197   o->to_stdout   = (s && strchr(s, 'O'));
198   o->to_stderr   = (s && strchr(s, 'E'));
199   o->to_syslog   = (s && strchr(s, 'S'));
200   o->to_diagslog = (s && strchr(s, 'L'));
201 }
202 
203 //////////////////////////////////////////////////////////////////////////////
204 //
205 //      void Diags::config_norecords()
206 //
207 //      Builds the Diags data structures based on the command line values
208 //        it does not use any of the records based config variables
209 //
210 //////////////////////////////////////////////////////////////////////////////
211 void
config_diags_norecords()212 DiagsConfig::config_diags_norecords()
213 {
214   DiagsConfigState c;
215   ink_zero(c);
216 
217   //////////////////////////////
218   // clear out old tag tables //
219   //////////////////////////////
220   diags->deactivate_all(DiagsTagType_Debug);
221   diags->deactivate_all(DiagsTagType_Action);
222 
223   //////////////////////////////////////////////////////////////////////
224   // add new tag tables from command line overrides only              //
225   //////////////////////////////////////////////////////////////////////
226 
227   if (diags->base_debug_tags) {
228     diags->activate_taglist(diags->base_debug_tags, DiagsTagType_Debug);
229     c.enabled[DiagsTagType_Debug] = true;
230   } else {
231     c.enabled[DiagsTagType_Debug] = false;
232   }
233 
234   if (diags->base_action_tags) {
235     diags->activate_taglist(diags->base_action_tags, DiagsTagType_Action);
236     c.enabled[DiagsTagType_Action] = true;
237   } else {
238     c.enabled[DiagsTagType_Action] = false;
239   }
240 
241 #if !defined(__GNUC__)
242   diags->config = c;
243 #else
244   memcpy(((void *)&diags->config), ((void *)&c), sizeof(DiagsConfigState));
245 #endif
246 }
247 
DiagsConfig(std::string_view prefix_string,const char * filename,const char * tags,const char * actions,bool use_records)248 DiagsConfig::DiagsConfig(std::string_view prefix_string, const char *filename, const char *tags, const char *actions,
249                          bool use_records)
250   : callbacks_established(false), diags_log(nullptr), diags(nullptr)
251 {
252   char diags_logpath[PATH_NAME_MAX];
253   ats_scoped_str logpath;
254 
255   ////////////////////////////////////////////////////////////////////
256   //  If we aren't using the manager records for configuration      //
257   //   just build the tables based on command line parameters and   //
258   //   exit                                                         //
259   ////////////////////////////////////////////////////////////////////
260 
261   if (!use_records) {
262     diags = new Diags(prefix_string, tags, actions, nullptr);
263     config_diags_norecords();
264     return;
265   }
266 
267   // Open the diagnostics log. If proxy.config.log.logfile_dir is set use that, otherwise fall
268   // back to the configured log directory.
269 
270   logpath = RecConfigReadLogDir();
271   if (access(logpath, W_OK | R_OK) == -1) {
272     fprintf(stderr, "unable to access log directory '%s': %d, %s\n", (const char *)logpath, errno, strerror(errno));
273     fprintf(stderr, "please set 'proxy.config.log.logfile_dir'\n");
274     ::exit(1);
275   }
276 
277   ink_filepath_make(diags_logpath, sizeof(diags_logpath), logpath, filename);
278 
279   // Grab rolling intervals from configuration
280   // TODO error check these values
281   int output_log_roll_int    = static_cast<int>(REC_ConfigReadInteger("proxy.config.output.logfile.rolling_interval_sec"));
282   int output_log_roll_size   = static_cast<int>(REC_ConfigReadInteger("proxy.config.output.logfile.rolling_size_mb"));
283   int output_log_roll_enable = static_cast<int>(REC_ConfigReadInteger("proxy.config.output.logfile.rolling_enabled"));
284   int diags_log_roll_int     = static_cast<int>(REC_ConfigReadInteger("proxy.config.diags.logfile.rolling_interval_sec"));
285   int diags_log_roll_size    = static_cast<int>(REC_ConfigReadInteger("proxy.config.diags.logfile.rolling_size_mb"));
286   int diags_log_roll_enable  = static_cast<int>(REC_ConfigReadInteger("proxy.config.diags.logfile.rolling_enabled"));
287 
288   // Grab some perms for the actual files on disk
289   char *diags_perm       = REC_ConfigReadString("proxy.config.diags.logfile_perm");
290   char *output_perm      = REC_ConfigReadString("proxy.config.output.logfile_perm");
291   int diags_perm_parsed  = diags_perm ? ink_fileperm_parse(diags_perm) : -1;
292   int output_perm_parsed = diags_perm ? ink_fileperm_parse(output_perm) : -1;
293 
294   ats_free(diags_perm);
295   ats_free(output_perm);
296 
297   // Set up diags, FILE streams are opened in Diags constructor
298   diags_log = new BaseLogFile(diags_logpath);
299   diags     = new Diags(prefix_string, tags, actions, diags_log, diags_perm_parsed, output_perm_parsed);
300   diags->config_roll_diagslog(static_cast<RollingEnabledValues>(diags_log_roll_enable), diags_log_roll_int, diags_log_roll_size);
301   diags->config_roll_outputlog(static_cast<RollingEnabledValues>(output_log_roll_enable), output_log_roll_int,
302                                output_log_roll_size);
303 
304   Status("opened %s", diags_logpath);
305 
306   register_diags_callbacks();
307 
308   reconfigure_diags();
309 }
310 
311 //////////////////////////////////////////////////////////////////////////////
312 //
313 //      void DiagsConfig::register_diags_callbacks()
314 //
315 //      set up management callbacks to update diags on every change ---   //
316 //      right now, this system kind of sucks, we rebuild the tag tables //
317 //      from scratch for *every* proxy.config.diags value that changed; //
318 //      dgourley is looking into changing the management API to provide //
319 //      a callback each time records.config changed, possibly better.   //
320 //
321 //////////////////////////////////////////////////////////////////////////////
322 void
register_diags_callbacks()323 DiagsConfig::register_diags_callbacks()
324 {
325   static const char *config_record_names[] = {
326     "proxy.config.diags.debug.enabled",  "proxy.config.diags.debug.tags",       "proxy.config.diags.action.enabled",
327     "proxy.config.diags.action.tags",    "proxy.config.diags.show_location",    "proxy.config.diags.output.diag",
328     "proxy.config.diags.output.debug",   "proxy.config.diags.output.status",    "proxy.config.diags.output.note",
329     "proxy.config.diags.output.warning", "proxy.config.diags.output.error",     "proxy.config.diags.output.fatal",
330     "proxy.config.diags.output.alert",   "proxy.config.diags.output.emergency", nullptr,
331   };
332 
333   bool total_status = true;
334   bool status;
335   int i;
336   void *o = (void *)this;
337 
338   // set triggers to call same callback for any diag config change
339   for (i = 0; config_record_names[i] != nullptr; i++) {
340     status = (REC_RegisterConfigUpdateFunc(config_record_names[i], diags_config_callback, o) == REC_ERR_OKAY);
341     if (!status) {
342       Warning("couldn't register variable '%s', is %s up to date?", config_record_names[i], ts::filename::RECORDS);
343     }
344     total_status = total_status && status;
345   }
346 
347   if (total_status == false) {
348     Error("couldn't setup all diags callbacks, diagnostics may misbehave");
349     callbacks_established = false;
350   } else {
351     callbacks_established = true;
352   }
353 }
354 
~DiagsConfig()355 DiagsConfig::~DiagsConfig()
356 {
357   delete diags;
358 }
359