Skip to content

Commit 91c3ed2

Browse files
address review feedback
Signed-off-by: darkexplosiveqwx <101737077+darkexplosiveqwx@users.noreply.github.com>
1 parent 266bd72 commit 91c3ed2

5 files changed

Lines changed: 68 additions & 29 deletions

File tree

src/config/config.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1924,6 +1924,13 @@ bool readFTLconf(struct config *conf, const bool rewrite)
19241924
// First, read the environment
19251925
getEnvVars();
19261926

1927+
// Open pihole.log and webserver.log now (with default or ENV paths)
1928+
// so that any log output during write_dnsmasq_config() below is not
1929+
// silently lost. open_log_fds(false) will be called again after the
1930+
// config parse in main() to pick up any path overrides from the TOML
1931+
// file or legacy config.
1932+
open_log_fds(false);
1933+
19271934
// Try to read TOML config file
19281935
// If we cannot parse /etc/pihole.toml (due to missing or invalid syntax),
19291936
// we try to read the rotated files in /etc/pihole/config_backup starting at

src/dnsmasq_interface.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4282,8 +4282,11 @@ void FTL_dnsmasq_log(const char *payload, const int priority, const char *func,
42824282
// Unlock SHM
42834283
unlock_shm();
42844284

4285-
// Write to pihole.log via shared writer (FTL owns this file now)
4286-
FTL_write_dnsmasq_log(payload, func);
4285+
// Write to pihole.log via shared writer (FTL owns this file now).
4286+
// If pihole.log is unavailable, fall back to syslog for warnings and
4287+
// errors so they are not silently lost for the lifetime of the process.
4288+
if(!FTL_write_dnsmasq_log(payload, func) && priority <= LOG_WARNING)
4289+
syslog(priority, "%s", payload);
42874290

42884291
/* Pi-hole diagnosis system */
42894292
if(priority == LOG_WARNING)

src/log.c

Lines changed: 53 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,10 @@
3434
static bool print_log = true, print_stdout = true;
3535
bool debug_flags[DEBUG_MAX] = { false };
3636

37-
// Per-file log state: fd, path, writer-preferenced lock, reopen flag
37+
// Per-file log state: fd, path (owned copy), writer-preferenced lock, reopen flag
3838
struct log_fd {
3939
int fd;
40-
const char *path;
40+
char *path;
4141
pthread_mutex_t lock;
4242
volatile sig_atomic_t reopen_needed;
4343
};
@@ -70,8 +70,9 @@ static void log_atfork_child(void)
7070
}
7171

7272
// Return 1 if this fd is associated with any logfile to avoid
73-
// dnsmasq closing it during initialization
74-
int __attribute__((pure)) is_log_fd(const int fd)
73+
// dnsmasq closing it during initialization.
74+
// Not marked pure: the descriptors are reassigned on reopen from another thread.
75+
int is_log_fd(const int fd)
7576
{
7677
return fd == ftl_log.fd || fd == webserver_log.fd || fd == dnsmasq_log.fd;
7778
}
@@ -137,6 +138,17 @@ void log_ctrl(bool plog, bool pstdout)
137138
print_stdout = pstdout;
138139
}
139140

141+
// Set a log_fd path from a config string. The path is duplicated so
142+
// that a config replacement (free_config + memcpy) cannot leave a
143+
// dangling pointer in the reopen path.
144+
static void set_log_path(struct log_fd *log, const char *path)
145+
{
146+
if(log->path != NULL && path != NULL && strcmp(log->path, path) == 0)
147+
return; // unchanged
148+
free(log->path);
149+
log->path = path != NULL ? strdup(path) : NULL;
150+
}
151+
140152
// Open cached log fds from config paths.
141153
// open_log_fds(true): open FTL.log only (called early, before full config)
142154
// open_log_fds(false): open webserver.log + pihole.log (called after config)
@@ -147,7 +159,7 @@ void open_log_fds(bool ftl)
147159
// FTL.log - path is known from getLogFilePath()
148160
if(config.files.log.ftl.v.s != NULL)
149161
{
150-
ftl_log.path = config.files.log.ftl.v.s;
162+
set_log_path(&ftl_log, config.files.log.ftl.v.s);
151163
ftl_log.fd = open(ftl_log.path, O_WRONLY|O_CREAT|O_APPEND|O_CLOEXEC, S_IRUSR|S_IWUSR|S_IRGRP);
152164
if(ftl_log.fd == -1)
153165
{
@@ -162,7 +174,7 @@ void open_log_fds(bool ftl)
162174
// webserver.log + pihole.log - paths are known after readFTLconf()
163175
if(config.files.log.webserver.v.s != NULL)
164176
{
165-
webserver_log.path = config.files.log.webserver.v.s;
177+
set_log_path(&webserver_log, config.files.log.webserver.v.s);
166178
webserver_log.fd = open(webserver_log.path, O_WRONLY|O_CREAT|O_APPEND|O_CLOEXEC, S_IRUSR|S_IWUSR|S_IRGRP);
167179
if(webserver_log.fd == -1)
168180
{
@@ -174,7 +186,7 @@ void open_log_fds(bool ftl)
174186
// pihole.log (dnsmasq) - FTL owns this file from now on
175187
if(config.files.log.dnsmasq.v.s != NULL)
176188
{
177-
dnsmasq_log.path = config.files.log.dnsmasq.v.s;
189+
set_log_path(&dnsmasq_log, config.files.log.dnsmasq.v.s);
178190
dnsmasq_log.fd = open(dnsmasq_log.path, O_WRONLY|O_CREAT|O_APPEND|O_CLOEXEC, S_IRUSR|S_IWUSR|S_IRGRP);
179191
if(dnsmasq_log.fd == -1)
180192
{
@@ -190,7 +202,10 @@ void open_log_fds(bool ftl)
190202
}
191203

192204
// Register atfork handlers once, before any threads or dnsmasq forks
193-
// exist, so a TCP-query fork can never inherit a locked log mutex
205+
// exist, so a TCP-query fork can never inherit a locked log mutex.
206+
// Invariant: fork() is never called from inside a log write, so the
207+
// atfork prepare/parent/child handlers only need to cover the case
208+
// where a thread holds a log mutex at the moment of the fork.
194209
static bool atfork_registered = false;
195210
if(!atfork_registered)
196211
{
@@ -400,36 +415,45 @@ const char *debugstr(const enum debug_flag flag)
400415
// "Jan 1 12:00:00 dnsmasq-dhcp[12345]: <message>\n"
401416
// where the func suffix (e.g. "-dhcp", "-tftp") comes from the priority
402417
// bits extracted in my_syslog().
403-
void FTL_write_dnsmasq_log(const char *message, const char *func)
418+
bool FTL_write_dnsmasq_log(const char *message, const char *func)
404419
{
405-
// Locale-independent timestamp: ctime() renders the month/day in the
420+
// Locale-independent timestamp: ctime_r() renders the month/day in the
406421
// C locale regardless of setlocale(LC_ALL, ""), so the buffer cannot
407422
// overflow with non-English month names (strftime("%b") would emit
408-
// e.g. six bytes for ru_RU). This is dnsmasq's own idiom and keeps
409-
// the on-disk format byte-identical to what we wrote before.
423+
// e.g. six bytes for ru_RU). ctime_r() is reentrant, unlike ctime()
424+
// which returns a pointer to a static buffer shared with localtime()
425+
// and asctime() - critical since FTL_write_dnsmasq_log() runs on the
426+
// DNS thread while the webserver, database and NTP threads format their
427+
// own timestamps. This is dnsmasq's own idiom and keeps the on-disk
428+
// format byte-identical to what we wrote before.
410429
time_t now = time(NULL);
430+
char ctime_buf[26];
431+
char *ctime_str = ctime_r(&now, ctime_buf);
432+
if(ctime_str == NULL)
433+
ctime_str = "Jan 1 00:00:00 ";
411434
char ts_buf[16];
412-
snprintf(ts_buf, sizeof(ts_buf), "%.15s", ctime(&now) + 4);
435+
snprintf(ts_buf, sizeof(ts_buf), "%.15s", ctime_str + 4);
413436

414437
char line[2048];
415438
int off = snprintf(line, sizeof(line), "%s dnsmasq%s[%d]: ", ts_buf, func ? func : "", getpid());
416439

417440
// Clamp before using off as an offset - snprintf returns the would-be
418-
// length on truncation and sizeof(line) - off would underflow otherwise
419-
if(off >= (int)sizeof(line))
441+
// length on truncation and sizeof(line) - off would underflow otherwise;
442+
// it may also return negative on an encoding error
443+
if(off < 0 || off >= (int)sizeof(line))
420444
off = sizeof(line) - 1;
421445

422446
const char *msg = message ? message : "";
423447
off += snprintf(line + off, sizeof(line) - off, "%s", msg);
424448

425449
// Clamp to buffer end - snprintf returns would-be length on truncation
426-
if(off >= (int)sizeof(line))
450+
if(off < 0 || off >= (int)sizeof(line))
427451
off = sizeof(line) - 1;
428452

429453
if(off > 0 && line[off - 1] != '\n')
430454
line[off++] = '\n';
431455

432-
write_log_line(&dnsmasq_log, line, off);
456+
return write_log_line(&dnsmasq_log, line, off);
433457
}
434458

435459
void __attribute__ ((format (printf, 3, 4))) _FTL_log(const int priority, const enum debug_flag flag, const char *format, ...)
@@ -477,16 +501,17 @@ void __attribute__ ((format (printf, 3, 4))) _FTL_log(const int priority, const
477501
int off = snprintf(line, sizeof(line), "%s [%s] %s: ", timestring, idstr, prio);
478502

479503
// Clamp before using off as an offset - snprintf returns the would-be
480-
// length on truncation and sizeof(line) - off would underflow otherwise
481-
if(off >= (int)sizeof(line))
504+
// length on truncation and sizeof(line) - off would underflow otherwise;
505+
// it may also return negative on an encoding error
506+
if(off < 0 || off >= (int)sizeof(line))
482507
off = sizeof(line) - 1;
483508

484509
va_start(args, format);
485510
off += vsnprintf(line + off, sizeof(line) - off, format, args);
486511
va_end(args);
487512

488513
// Clamp to buffer end - snprintf returns would-be length on truncation
489-
if(off >= (int)sizeof(line))
514+
if(off < 0 || off >= (int)sizeof(line))
490515
off = sizeof(line) - 1;
491516

492517
line[off++] = '\n';
@@ -547,16 +572,17 @@ void __attribute__ ((format (printf, 3, 4))) _log_web(const int priority, const
547572
int off = snprintf(line, sizeof(line), "%s [%s] %s: ", timestring, idstr, prio);
548573

549574
// Clamp before using off as an offset - snprintf returns the would-be
550-
// length on truncation and sizeof(line) - off would underflow otherwise
551-
if(off >= (int)sizeof(line))
575+
// length on truncation and sizeof(line) - off would underflow otherwise;
576+
// it may also return negative on an encoding error
577+
if(off < 0 || off >= (int)sizeof(line))
552578
off = sizeof(line) - 1;
553579

554580
va_start(args, format);
555581
off += vsnprintf(line + off, sizeof(line) - off, format, args);
556582
va_end(args);
557583

558584
// Clamp to buffer end - snprintf returns would-be length on truncation
559-
if(off >= (int)sizeof(line))
585+
if(off < 0 || off >= (int)sizeof(line))
560586
off = sizeof(line) - 1;
561587

562588
line[off++] = '\n';
@@ -975,10 +1001,10 @@ bool flush_dnsmasq_log(void)
9751001
lock_shm();
9761002

9771003
// Open file in write mode to truncate it
978-
FILE *logfile = fopen(config.files.log.dnsmasq.v.s, "w");
1004+
FILE *logfile = fopen(dnsmasq_log.path, "w");
9791005
if(!logfile)
9801006
{
981-
log_err("Could not open log file %s for truncation: %s\n", config.files.log.dnsmasq.v.s, strerror(errno));
1007+
log_err("Could not open log file %s for truncation: %s\n", dnsmasq_log.path, strerror(errno));
9821008
unlock_shm();
9831009
return false;
9841010
}
@@ -989,6 +1015,8 @@ bool flush_dnsmasq_log(void)
9891015
if(dnsmasq_log.fd != -1)
9901016
close(dnsmasq_log.fd);
9911017
dnsmasq_log.fd = open(dnsmasq_log.path, O_WRONLY|O_CREAT|O_APPEND|O_CLOEXEC, S_IRUSR|S_IWUSR|S_IRGRP);
1018+
if(dnsmasq_log.fd == -1)
1019+
log_warn("pihole.log reopen failed after flush (%s)", strerror(errno));
9921020
pthread_mutex_unlock(&dnsmasq_log.lock);
9931021

9941022
// Flush dnsmasq FIFO logs

src/log.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ extern bool only_testing;
4545
void clear_debug_flags(void);
4646
void open_log_fds(bool ftl);
4747
void mark_log_reopen(void);
48-
void FTL_write_dnsmasq_log(const char *message, const char *func);
48+
bool FTL_write_dnsmasq_log(const char *message, const char *func);
4949
void log_counter_info(void);
5050
void format_memory_size(char prefix[2], const off_t bytes, double * const formatted);
5151
void format_time(char buffer[42], unsigned long seconds, double milliseconds);

src/main.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ int main (int argc, char *argv[])
8383
if(readFTLconf(&config, true))
8484
log_info("Parsed config file "GLOBALTOMLPATH" successfully");
8585

86-
// Open webserver.log and pihole.log (paths now known after config parse)
86+
// Re-open webserver.log and pihole.log to pick up any path overrides
87+
// from the TOML file or legacy config
8788
open_log_fds(false);
8889

8990
// Check if another FTL process is already running

0 commit comments

Comments
 (0)