1mod cri;
5mod journald;
6mod line_buffer;
7mod log_file;
8#[cfg(test)]
9pub(crate) mod testing;
10
11use crate::conmon_capnp::conmon::log_driver::{self, Owned, Type};
12use crate::{
13 container_io::Pipe,
14 container_log::{
15 cri::CriLogger, journald::JournaldLogger, line_buffer::LineBuffer, log_file::LogFile,
16 },
17 fs_util::check_absolute,
18 server::Server,
19};
20use anyhow::{Context, Result, bail};
21use bytes::Bytes;
22use capnp::struct_list::Reader;
23use std::{io, ops::Deref, path::Path, sync::Arc};
24use tokio::{
25 sync::{Mutex, Semaphore},
26 task,
27};
28
29pub(crate) type SharedContainerLog = Arc<Mutex<ContainerLog>>;
32
33#[derive(Debug)]
39enum Driver {
40 Cri(CriLogger),
41 Journald(JournaldLogger),
42}
43
44impl Driver {
45 fn buffers_lines(&self) -> bool {
47 match self {
48 Driver::Cri(_) => false,
49 Driver::Journald(_) => true,
50 }
51 }
52
53 fn file(&mut self) -> Option<&mut LogFile> {
55 match self {
56 Driver::Cri(logger) => Some(logger.file()),
57 Driver::Journald(_) => None,
58 }
59 }
60
61 async fn init(&mut self) -> Result<()> {
63 match self.file() {
64 Some(file) => file.open().await,
65 None => Ok(()),
66 }
67 }
68
69 async fn reopen(&mut self) -> Result<()> {
71 match self.file() {
72 Some(file) => file.reopen().await,
73 None => Ok(()),
74 }
75 }
76
77 async fn write_chunk(&mut self, pipe: Pipe, bytes: &[u8]) -> Result<()> {
80 match self {
81 Driver::Cri(logger) => logger.write_chunk(pipe, bytes).await,
82 Driver::Journald(_) => Ok(()),
83 }
84 }
85
86 async fn write_lines(&mut self, pipe: Pipe, lines: &[Line<'_>]) -> Result<()> {
89 match self {
90 Driver::Cri(_) => Ok(()),
91 Driver::Journald(logger) => logger.write_lines(pipe, lines).await,
92 }
93 }
94}
95
96#[derive(Debug, Default)]
98pub(crate) struct ContainerLog {
99 drivers: Vec<Driver>,
100
101 lines: LineBuffer,
104
105 buffers_lines: bool,
107}
108
109impl ContainerLog {
110 pub(crate) fn new() -> SharedContainerLog {
112 Arc::new(Mutex::new(Self::default()))
113 }
114
115 pub(crate) fn has_drivers(&self) -> bool {
117 !self.drivers.is_empty()
118 }
119
120 pub(crate) fn from_request(id: &str, reader: Reader<Owned>) -> Result<SharedContainerLog> {
123 let drivers = reader
124 .iter()
125 .map(|driver| -> Result<Driver> {
126 let max_size = Self::max_size(&driver);
127 Ok(match driver.get_type()? {
128 Type::ContainerRuntimeInterface => {
129 Driver::Cri(CriLogger::new(Self::path(&driver)?, max_size))
130 }
131 Type::JsonOld => {
134 debug_assert!(false, "the retired log driver type got through");
135 bail!("unsupported log driver type")
136 }
137 Type::Journald => Driver::Journald(JournaldLogger::new(
138 id,
139 driver.get_container_name()?.to_str()?,
140 )),
141 })
142 })
143 .collect::<Result<Vec<_>>>()?;
144 let buffers_lines = drivers.iter().any(Driver::buffers_lines);
145 Ok(Arc::new(Mutex::new(Self {
146 drivers,
147 lines: LineBuffer::default(),
148 buffers_lines,
149 })))
150 }
151
152 fn path<'a>(driver: &log_driver::Reader<'a>) -> Result<&'a Path> {
154 let path = Path::new(driver.get_path()?.to_str()?);
155 check_absolute("log path", path)?;
156 Ok(path)
157 }
158
159 fn max_size(driver: &log_driver::Reader<'_>) -> Option<usize> {
161 match driver.get_max_size() {
162 0 => None,
163 max => usize::try_from(max).ok(),
165 }
166 }
167
168 pub(crate) async fn init(&mut self) -> Result<()> {
170 for driver in &mut self.drivers {
171 driver.init().await?;
172 }
173 Ok(())
174 }
175
176 pub(crate) async fn reopen(&mut self) -> Result<()> {
179 let mut result = Ok(());
180 for driver in &mut self.drivers {
181 result = result.and(driver.reopen().await);
182 }
183 result
184 }
185
186 pub(crate) async fn write(&mut self, pipe: Pipe, bytes: &[u8]) -> Result<()> {
189 let mut result = Ok(());
190 for driver in &mut self.drivers {
191 result = result.and(driver.write_chunk(pipe, bytes).await);
192 }
193 if self.buffers_lines {
194 let lines = self.lines.push(pipe, bytes);
195 let res = self.write_lines(pipe, &lines).await;
196 result = result.and(res);
197 }
198 result
199 }
200
201 pub(crate) fn has_pending(&self, pipe: Pipe) -> bool {
203 self.buffers_lines && self.lines.has_pending(pipe)
204 }
205
206 pub(crate) async fn flush(&mut self, pipe: Pipe) -> Result<()> {
209 let lines = self.lines.flush(pipe);
210 self.write_lines(pipe, &lines).await
211 }
212
213 pub(crate) async fn finish(&mut self, pipe: Pipe) -> Result<()> {
216 let lines = self.lines.finish(pipe);
217 self.write_lines(pipe, &lines).await
218 }
219
220 pub(crate) async fn sync(log: &SharedContainerLog) -> Result<()> {
229 const PERMITS: usize = if cfg!(test) {
230 Semaphore::MAX_PERMITS
231 } else {
232 Server::MAX_BLOCKING_THREADS / 4
233 };
234 static SYNC: Semaphore = Semaphore::const_new(PERMITS);
235 let files: Vec<_> = log
236 .lock()
237 .await
238 .drivers
239 .iter_mut()
240 .filter_map(|driver| driver.file()?.file.clone())
241 .collect();
242 if files.is_empty() {
243 return Ok(());
244 }
245 let permit = SYNC.acquire().await.context("wait for log sync")?;
246 task::spawn_blocking(move || {
247 let _permit = permit;
248 files
249 .iter()
250 .map(|file| match file.sync_data() {
251 Err(e) if e.raw_os_error() == Some(libc::EINVAL) => Ok(()),
252 res => res,
253 })
254 .fold(Ok(()), io::Result::and)
255 })
256 .await
257 .context("join log sync")?
258 .context("sync log file")
259 }
260
261 async fn write_lines(&mut self, pipe: Pipe, lines: &[Line<'_>]) -> Result<()> {
264 if lines.is_empty() {
265 return Ok(());
266 }
267 let mut result = Ok(());
268 for driver in self.drivers.iter_mut().filter(|d| d.buffers_lines()) {
269 result = result.and(driver.write_lines(pipe, lines).await);
270 }
271 result
272 }
273}
274
275fn lines(bytes: &[u8]) -> impl Iterator<Item = (&[u8], bool)> {
279 let mut ends = memchr::memchr_iter(b'\n', bytes);
280 let mut start = 0;
281 std::iter::from_fn(move || {
282 let (end, partial) = match ends.next() {
283 Some(end) => (end + 1, false),
284 None if start < bytes.len() => (bytes.len(), true),
285 None => return None,
286 };
287 let line = &bytes[start..end];
288 start = end;
289 Some((line, partial))
290 })
291}
292
293fn strip_line_ending(line: &[u8]) -> &[u8] {
295 line.strip_suffix(b"\r\n")
296 .or_else(|| line.strip_suffix(b"\n"))
297 .unwrap_or(line)
298}
299
300#[derive(Clone, Debug, PartialEq, Eq)]
304struct Line<'a> {
305 text: Text<'a>,
306 partial: bool,
307}
308
309impl Line<'_> {
310 fn into_owned(self) -> Line<'static> {
312 Line {
313 text: self.text.into_owned(),
314 partial: self.partial,
315 }
316 }
317}
318
319#[derive(Clone, Debug, PartialEq, Eq)]
322enum Text<'a> {
323 Chunk(&'a [u8]),
324 Buffered(Bytes),
325}
326
327impl Text<'_> {
328 fn into_owned(self) -> Text<'static> {
329 match self {
330 Text::Chunk(text) => Text::Buffered(Bytes::copy_from_slice(text)),
331 Text::Buffered(text) => Text::Buffered(text),
332 }
333 }
334}
335
336impl Deref for Text<'_> {
337 type Target = [u8];
338
339 fn deref(&self) -> &[u8] {
340 match self {
341 Text::Chunk(text) => text,
342 Text::Buffered(text) => text,
343 }
344 }
345}
346
347#[cfg(any(test, feature = "fuzzing"))]
349pub(crate) mod fuzzing;
350
351#[cfg(test)]
352mod tests;