1#!/usr/bin/tclsh
2#*
3#* SRT - Secure, Reliable, Transport
4#* Copyright (c) 2020 Haivision Systems Inc.
5#*
6#* This Source Code Form is subject to the terms of the Mozilla Public
7#* License, v. 2.0. If a copy of the MPL was not distributed with this
8#* file, You can obtain one at http://mozilla.org/MPL/2.0/.
9#*
10#*/
11#
12#*****************************************************************************
13#written by
14#  Haivision Systems Inc.
15#*****************************************************************************
16
17# What fields are there in every entry
18set model {
19	longname
20	shortname
21	id
22	description
23}
24
25# Logger definitions.
26# Comments here allowed, just only for the whole line.
27
28# Use values greater than 0. Value 0 is reserved for LOGFA_GENERAL,
29# which is considered always enabled.
30set loggers {
31	GENERAL    gg  0  "General uncategorized log, for serious issues only"
32	SOCKMGMT   sm  1  "Socket create/open/close/configure activities"
33	CONN       cn  2  "Connection establishment and handshake"
34	XTIMER     xt  3  "The checkTimer and around activities"
35	TSBPD      ts  4  "The TsBPD thread"
36	RSRC       rs  5  "System resource allocation and management"
37	CONGEST    cc  7  "Congestion control module"
38	PFILTER    pf  8  "Packet filter module"
39	API_CTRL   ac  11 "API part for socket and library managmenet"
40	QUE_CTRL   qc  13 "Queue control activities"
41	EPOLL_UPD  ei  16 "EPoll, internal update activities"
42
43	API_RECV   ar  21 "API part for receiving"
44	BUF_RECV   br  22 "Buffer, receiving side"
45	QUE_RECV   qr  23 "Queue, receiving side"
46	CHN_RECV   kr  24 "CChannel, receiving side"
47	GRP_RECV   gr  25 "Group, receiving side"
48
49	API_SEND   as  31 "API part for sending"
50	BUF_SEND   bs  32 "Buffer, sending side"
51	QUE_SEND   qs  33 "Queue, sending side"
52	CHN_SEND   ks  34 "CChannel, sending side"
53	GRP_SEND   gs  35 "Group, sending side"
54
55	INTERNAL   in  41 "Internal activities not connected directly to a socket"
56	QUE_MGMT   qm  43 "Queue, management part"
57	CHN_MGMT   km  44 "CChannel, management part"
58	GRP_MGMT   gm  45 "Group, management part"
59	EPOLL_API  ea  46 "EPoll, API part"
60}
61
62set hidden_loggers {
63	# Haicrypt logging - usually off.
64	HAICRYPT hc 6  "Haicrypt module area"
65
66    # defined in apps, this is only a stub to lock the value
67	APPLOG   ap 10 "Applications"
68}
69
70set globalheader {
71 /*
72  WARNING: Generated from ../scripts/generate-logging-defs.tcl
73
74  DO NOT MODIFY.
75
76  Copyright applies as per the generator script.
77 */
78
79}
80
81
82# This defines, what kind of definition will be generated
83# for a given file out of the log FA entry list.
84
85# Fields:
86#  - prefix/postfix model
87#  - logger_format
88#  - hidden_logger_format
89
90# COMMENTS NOT ALLOWED HERE! Only as C++ comments inside C++ model code.
91set special {
92	srtcore/logger_default.cpp {
93		if {"$longname" == "HAICRYPT"} {
94			puts $od "
95#if ENABLE_HAICRYPT_LOGGING
96		allfa.set(SRT_LOGFA_HAICRYPT, true);
97#endif"
98		}
99	}
100}
101
102proc GenerateModelForSrtH {} {
103
104	# `path` will be set to the git top path
105	global path
106
107	set fd [open [file join $path srtcore/srt.h] r]
108
109	set contents ""
110
111	set state read
112	set pass looking
113
114	while { [gets $fd line] != -1 } {
115		if { $state == "read" } {
116
117			if { $pass != "passed" } {
118
119				set re [regexp {SRT_LOGFA BEGIN GENERATED SECTION} $line]
120				if {$re} {
121					set state skip
122					set pass found
123				}
124
125			}
126
127			append contents "$line\n"
128			continue
129		}
130
131		if {$state == "skip"} {
132			if { [string trim $line] == "" } {
133				# Empty line, continue skipping
134				continue
135			}
136
137			set re [regexp {SRT_LOGFA END GENERATED SECTION} $line]
138			if {!$re} {
139				# Still SRT_LOGFA definitions
140				continue
141			}
142
143			# End of generated section. Switch back to pass-thru.
144
145			# First fill the gap
146			append contents "\n\$entries\n\n"
147
148			append contents "$line\n"
149			set state read
150			set pass passed
151		}
152	}
153
154	close $fd
155
156	# Sanity check
157	if {$pass != "passed"} {
158		error "Invalid contents of `srt.h` file, can't find '#define SRT_LOGFA_' phrase"
159	}
160
161	return $contents
162}
163
164# COMMENTS NOT ALLOWED HERE! Only as C++ comments inside C++ model code.
165# (NOTE: Tcl syntax highlighter will likely falsely highlight # as comment here)
166#
167# Model:  TARGET-NAME { format-model logger-pattern hidden-logger-pattern }
168#
169# Special syntax:
170#
171# %<command> : a high-level command execution. This declares a command that
172# must be executed to GENERATE the model. Then, [subst] is executed
173# on the results.
174#
175# = : when placed as the hidden-logger-pattern, it's equal to logger-pattern.
176#
177set generation {
178	srtcore/srt.h {
179
180		{%GenerateModelForSrtH}
181
182		{#define [format "%-20s %-3d" SRT_LOGFA_${longname} $id] // ${shortname}log: $description}
183
184		=
185	}
186
187    srtcore/logger_default.cpp {
188
189        {
190            $globalheader
191            #include "srt.h"
192            #include "logging.h"
193            #include "logger_defs.h"
194
195            namespace srt_logging
196            {
197                AllFaOn::AllFaOn()
198                {
199                    $entries
200                }
201            } // namespace srt_logging
202
203        }
204
205        {
206            allfa.set(SRT_LOGFA_${longname}, true);
207        }
208    }
209
210    srtcore/logger_defs.cpp {
211
212        {
213            $globalheader
214            #include "srt.h"
215            #include "logging.h"
216            #include "logger_defs.h"
217
218            namespace srt_logging { AllFaOn logger_fa_all; }
219            // We need it outside the namespace to preserve the global name.
220            // It's a part of "hidden API" (used by applications)
221            SRT_API srt_logging::LogConfig srt_logger_config(srt_logging::logger_fa_all.allfa);
222
223            namespace srt_logging
224            {
225                $entries
226            } // namespace srt_logging
227        }
228
229        {
230            Logger ${shortname}log(SRT_LOGFA_${longname}, srt_logger_config, "SRT.${shortname}");
231        }
232    }
233
234    srtcore/logger_defs.h {
235        {
236            $globalheader
237            #ifndef INC_SRT_LOGGER_DEFS_H
238            #define INC_SRT_LOGGER_DEFS_H
239
240            #include "srt.h"
241            #include "logging.h"
242
243            namespace srt_logging
244            {
245                struct AllFaOn
246                {
247                    LogConfig::fa_bitset_t allfa;
248                    AllFaOn();
249                };
250
251                $entries
252
253            } // namespace srt_logging
254
255            #endif
256        }
257
258        {
259            extern Logger ${shortname}log;
260        }
261    }
262
263    apps/logsupport_appdefs.cpp {
264        {
265            $globalheader
266            #include "logsupport.hpp"
267
268            LogFANames::LogFANames()
269            {
270                $entries
271            }
272        }
273
274        {
275            Install("$longname", SRT_LOGFA_${longname});
276        }
277
278        {
279            Install("$longname", SRT_LOGFA_${longname});
280        }
281    }
282}
283
284# EXECUTION
285
286set here [file dirname [file normalize $argv0]]
287
288if {[lindex [file split $here] end] != "scripts"} {
289	puts stderr "The script is in weird location."
290	exit 1
291}
292
293set path [file join {*}[lrange [file split $here] 0 end-1]]
294
295# Utility. Allows to put line-oriented comments and have empty lines
296proc no_comments {input} {
297	set output ""
298	foreach line [split $input \n] {
299		set nn [string trim $line]
300		if { $nn == "" || [string index $nn 0] == "#" } {
301			continue
302		}
303		append output $line\n
304	}
305
306	return $output
307}
308
309proc generate_file {od target} {
310
311	global globalheader
312	lassign [dict get $::generation $target] format_model pattern hpattern
313
314    set ptabprefix ""
315
316	if {[string index $format_model 0] == "%"} {
317		set command [string range $format_model 1 end]
318		set format_model [eval $command]
319	}
320
321	if {$format_model != ""} {
322		set beginindex 0
323		while { [string index $format_model $beginindex] == "\n" } {
324			incr beginindex
325		}
326
327		set endindex $beginindex
328		while { [string is space [string index $format_model $endindex]] } {
329			incr endindex
330		}
331
332		set tabprefix [string range $pattern $beginindex $endindex-1]
333
334		set newformat ""
335		foreach line [split $format_model \n] {
336			if {[string trim $line] == ""} {
337				append newformat "\n"
338				continue
339			}
340
341			if {[string first $tabprefix $line] == 0} {
342                set line [string range $line [string length $tabprefix] end]
343			}
344            append newformat $line\n
345
346            set ie [string first {$} $line]
347            if {$ie != -1} {
348                if {[string range $line $ie end] == {$entries}} {
349                    set ptabprefix "[string range $line 0 $ie-1]"
350                }
351            }
352		}
353
354		set format_model $newformat
355		unset newformat
356	}
357
358	set entries ""
359
360	if {[string trim $pattern] != "" } {
361
362        set prevval 0
363 		set pattern [string trim $pattern]
364
365		# The first "$::model" will expand into variable names
366		# as defined there.
367		foreach [list {*}$::model] [no_comments $::loggers] {
368			if {$prevval + 1 != $id} {
369				append entries "\n"
370			}
371
372			append entries "${ptabprefix}[subst -nobackslashes $pattern]\n"
373			set prevval $id
374		}
375	}
376
377	if {$hpattern != ""} {
378		if {$hpattern == "="} {
379			set hpattern $pattern
380		} else {
381 			set hpattern [string trim $hpattern]
382		}
383
384		# Extra line to separate from the normal entries
385		append entries "\n"
386		foreach [list {*}$::model] [no_comments $::hidden_loggers] {
387			append entries "${ptabprefix}[subst -nobackslashes $hpattern]\n"
388		}
389	}
390
391	if { [dict exists $::special $target] } {
392		set code [subst [dict get $::special $target]]
393
394		# The code should contain "append entries" !
395		eval $code
396	}
397
398	set entries [string trim $entries]
399
400    if {$format_model == ""} {
401        set format_model $entries
402    }
403
404	# For any case, cut external spaces
405	puts $od [string trim [subst -nocommands -nobackslashes $format_model]]
406}
407
408proc debug_vars {list} {
409	set output ""
410	foreach name $list {
411		upvar $name _${name}
412		lappend output "${name}=[set _${name}]"
413	}
414
415	return $output
416}
417
418# MAIN
419
420set entryfiles $argv
421
422if {$entryfiles == ""} {
423	set entryfiles [dict keys $generation]
424} else {
425	foreach ef $entryfiles {
426		if { $ef ni [dict keys $generation] } {
427			error "Unknown generation target: $entryfiles"
428		}
429	}
430}
431
432foreach f $entryfiles {
433
434	# Set simple relative path, if the file isn't defined as path.
435	if { [llength [file split $f]] == 1 } {
436		set filepath $f
437	} else {
438		set filepath [file join $path $f]
439	}
440
441    puts stderr "Generating '$filepath'"
442	set od [open $filepath.tmp w]
443	generate_file $od $f
444	close $od
445	if { [file exists $filepath] } {
446		puts "WARNING: will overwrite exiting '$f'. Hit ENTER to confirm, or Control-C to stop"
447		gets stdin
448	}
449
450	file rename -force $filepath.tmp $filepath
451}
452
453puts stderr Done.
454
455