1 /*
2  * Copyright (C) 1996-2021 The Squid Software Foundation and contributors
3  *
4  * Squid software is distributed under GPLv2+ license and includes
5  * contributions from numerous individuals and organizations.
6  * Please see the COPYING and CONTRIBUTORS files for details.
7  */
8 
9 #include "squid.h"
10 #include "base/File.h"
11 #include "fs_io.h"
12 #include "Instance.h"
13 #include "parser/Tokenizer.h"
14 #include "sbuf/Stream.h"
15 #include "SquidConfig.h"
16 #include "tools.h"
17 
18 #include <cerrno>
19 
20 /* To support concurrent PID files, convert local statics into PidFile class */
21 
22 /// Describes the (last) instance PID file being processed.
23 /// This hack shortens reporting code while keeping its messages consistent.
24 static SBuf TheFile;
25 
26 /// PidFilename() helper
27 /// \returns PID file name or, if PID signaling was disabled, an empty SBuf
28 static SBuf
PidFilenameCalc()29 PidFilenameCalc()
30 {
31     if (!Config.pidFilename || strcmp(Config.pidFilename, "none") == 0)
32         return SBuf();
33 
34     // If chroot has been requested, then we first read the PID file before
35     // chroot() and then create/update it inside a chrooted environment.
36     // TODO: Consider removing half-baked chroot support from Squid.
37     extern bool Chrooted;
38     if (!Config.chroot_dir || Chrooted) // no need to compensate
39         return SBuf(Config.pidFilename);
40 
41     SBuf filename;
42     filename.append(Config.chroot_dir);
43     filename.append("/");
44     filename.append(Config.pidFilename);
45     debugs(50, 3, "outside chroot: " << filename);
46     return filename;
47 }
48 
49 /// \returns PID file description for debugging messages and error reporting
50 static SBuf
PidFileDescription(const SBuf & filename)51 PidFileDescription(const SBuf &filename)
52 {
53     return ToSBuf("PID file (", filename, ')');
54 }
55 
56 /// Instance entry points are expected to call this first.
57 /// \returns PidFilenameCalc() result while updating TheFile context
58 static SBuf
PidFilename()59 PidFilename()
60 {
61     const auto name = PidFilenameCalc();
62     TheFile = PidFileDescription(name);
63     return name;
64 }
65 
66 /// \returns the PID of another Squid instance (or throws)
67 static pid_t
GetOtherPid(File & pidFile)68 GetOtherPid(File &pidFile)
69 {
70     const auto input = pidFile.readSmall(1, 32);
71     int64_t rawPid = -1;
72 
73     Parser::Tokenizer tok(input);
74     if (!(tok.int64(rawPid, 10, false) && // PID digits
75             (tok.skipOne(CharacterSet::CR)||true) && // optional CR (Windows/etc.)
76             tok.skipOne(CharacterSet::LF) && // required end of line
77             tok.atEnd())) { // no trailing garbage
78         throw TexcHere(ToSBuf("Malformed ", TheFile));
79     }
80 
81     debugs(50, 7, "found PID " << rawPid << " in " << TheFile);
82 
83     if (rawPid <= 1)
84         throw TexcHere(ToSBuf("Bad ", TheFile, " contains unreasonably small PID value: ", rawPid));
85     const auto finalPid = static_cast<pid_t>(rawPid);
86     if (static_cast<int64_t>(finalPid) != rawPid)
87         throw TexcHere(ToSBuf("Bad ", TheFile, " contains unreasonably large PID value: ", rawPid));
88 
89     return finalPid;
90 }
91 
92 /// determines whether a given process is running at the time of the call
93 static bool
ProcessIsRunning(const pid_t pid)94 ProcessIsRunning(const pid_t pid)
95 {
96     const auto result = kill(pid, 0);
97     const auto savedErrno = errno;
98     if (result != 0)
99         debugs(50, 3, "kill(" << pid << ", 0) failed: " << xstrerr(savedErrno));
100     // if we do not have permissions to signal the process, then it is running
101     return (result == 0 || savedErrno == EPERM);
102 }
103 
104 /// quits if another Squid instance (that owns the given PID file) is running
105 static void
ThrowIfAlreadyRunningWith(File & pidFile)106 ThrowIfAlreadyRunningWith(File &pidFile)
107 {
108     bool running = false;
109     SBuf description;
110     try {
111         const auto pid = GetOtherPid(pidFile);
112         description = ToSBuf(TheFile, " with PID ", pid);
113         running = ProcessIsRunning(pid);
114     }
115     catch (const std::exception &ex) {
116         debugs(50, 5, "assuming no other Squid instance: " << ex.what());
117         return;
118     }
119 
120     if (running)
121         throw TexcHere(ToSBuf("Squid is already running: Found fresh instance ", description));
122 
123     debugs(50, 5, "assuming stale instance " << description);
124 }
125 
126 pid_t
Other()127 Instance::Other()
128 {
129     const auto filename = PidFilename();
130     if (filename.isEmpty())
131         throw TexcHere("no pid_filename configured");
132 
133     File pidFile(filename, File::Be::ReadOnly().locked());
134     return GetOtherPid(pidFile);
135 }
136 
137 void
ThrowIfAlreadyRunning()138 Instance::ThrowIfAlreadyRunning()
139 {
140     const auto filename = PidFilename();
141     if (filename.isEmpty())
142         return; // the check is impossible
143 
144     if (const auto filePtr = File::Optional(filename, File::Be::ReadOnly().locked())) {
145         const std::unique_ptr<File> pidFile(filePtr);
146         ThrowIfAlreadyRunningWith(*pidFile);
147     } else {
148         // It is best to assume then to check because checking without a lock
149         // might lead to false positives that lead to no Squid starting at all!
150         debugs(50, 5, "cannot lock " << TheFile << "; assuming no other Squid is running");
151         // If our assumption is false, we will fail to _create_ the PID file,
152         // and, hence, will not start, allowing that other Squid to run.
153     }
154 }
155 
156 /// ties Instance::WriteOurPid() scheduler and RemoveInstance(void) handler
157 static SBuf ThePidFileToRemove;
158 
159 /// atexit() handler; removes the PID file created with Instance::WriteOurPid()
160 static void
RemoveInstance()161 RemoveInstance()
162 {
163     if (ThePidFileToRemove.isEmpty()) // not the PidFilename()!
164         return; // nothing to do
165 
166     debugs(50, DBG_IMPORTANT, "Removing " << PidFileDescription(ThePidFileToRemove));
167     const char *filename = ThePidFileToRemove.c_str(); // avoid complex operations inside enter_suid()
168     enter_suid();
169     safeunlink(filename, 0);
170     leave_suid();
171 
172     ThePidFileToRemove.clear();
173 }
174 
175 /// creates a PID file; throws on error
176 void
WriteOurPid()177 Instance::WriteOurPid()
178 {
179     // Instance code assumes that we do not support PID filename reconfiguration
180     static bool called = false;
181     Must(!called);
182     called = true;
183 
184     const auto filename = PidFilename();
185     if (filename.isEmpty())
186         return; // nothing to do
187 
188     File pidFile(filename, File::Be::ReadWrite().locked().createdIfMissing().openedByRoot());
189 
190     // another instance may have started after the caller checked (if it did)
191     ThrowIfAlreadyRunningWith(pidFile);
192 
193     /* now we know that we own the PID file created and/or locked above */
194 
195     // Cleanup is scheduled through atexit() to ensure both:
196     // - cleanup upon fatal() and similar "unplanned" exits and
197     // - enter_suid() existence and proper logging support during cleanup.
198     // Even without PID filename reconfiguration support, we have to remember
199     // the file name we have used because Config.pidFilename may change!
200     (void)std::atexit(&RemoveInstance); // failures leave the PID file on disk
201     ThePidFileToRemove = filename;
202 
203     /* write our PID to the locked file */
204     SBuf pidBuf;
205     pidBuf.Printf("%d\n", static_cast<int>(getpid()));
206     pidFile.truncate();
207     pidFile.writeAll(pidBuf);
208 
209     // We must fsync before releasing the lock or other Squid processes may not see
210     // our written PID (and decide that they are dealing with a corrupted PID file).
211     pidFile.synchronize();
212 
213     debugs(50, DBG_IMPORTANT, "Created " << TheFile);
214 }
215 
216