Coverage Report

Created: 2026-09-14 07:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ocudu/lib/ocudulog/sinks/file_utils.h
Line
Count
Source
1
// SPDX-FileCopyrightText: Copyright (C) 2021-2026 Software Radio Systems Limited
2
// SPDX-License-Identifier: BSD-3-Clause-Open-MPI
3
4
#pragma once
5
6
#include "ocudu/ocudulog/detail/support/error_string.h"
7
#include "ocudu/ocudulog/detail/support/memory_buffer.h"
8
#include "fmt/format.h"
9
10
namespace ocudulog {
11
12
namespace file_utils {
13
14
/// Helper function that formats errnos nicely.
15
inline std::string format_error(const std::string& error, int error_code)
16
0
{
17
0
  fmt::memory_buffer result;
18
0
  fmt::format_system_error(result, error_code, error.c_str());
19
20
0
  return fmt::to_string(result);
21
0
}
22
23
/// Splits the specified path into a filename and its extension (if present).
24
inline std::pair<std::string, std::string> split_filename_extension(const std::string& filename)
25
0
{
26
  // Search for the last dot.
27
0
  auto dot_pos = filename.find_last_of('.');
28
29
  // Check for the following corner cases (returns {filename, ""}):
30
  //   a) No dot found: my_file
31
  //   b) Dot found at the beginning: .my_file
32
  //   c) Dot found at the end: my_file.
33
0
  if (dot_pos == std::string::npos || dot_pos == 0 || dot_pos == filename.size() - 1) {
34
0
    return {filename, ""};
35
0
  }
36
37
  // Handle directories that contain dots, search for the last separator
38
  // character.
39
0
  auto separator_pos = filename.find_last_of('/');
40
41
  // Check for the following corner cases (returns {filename, ""}):
42
  //   a) /my_folder.1/my_file
43
  //   b) /my_folder.1/.my_file
44
0
  if (separator_pos != std::string::npos && separator_pos >= dot_pos - 1) {
45
0
    return {filename, ""};
46
0
  }
47
48
0
  return {filename.substr(0, dot_pos), filename.substr(dot_pos)};
49
0
}
50
51
/// Builds a file name formatting the input base name and file index.
52
inline std::string build_filename_with_index(const std::string& basename, size_t index)
53
0
{
54
0
  if (index == 0) {
55
0
    return basename;
56
0
  }
57
58
0
  auto result = split_filename_extension(basename);
59
0
  return fmt::format("{}.{}{}", result.first, index, result.second);
60
0
}
61
62
/// This class provides basic file operations and disables itself when it
63
/// encounters an error.
64
class file
65
{
66
  std::string path;
67
  std::FILE*  handle = nullptr;
68
69
public:
70
0
  ~file() { close(); }
71
72
0
  explicit operator bool() const { return handle; }
73
74
  /// Returns the handle of the underlying file.
75
0
  std::FILE* get_handle() const { return handle; }
76
77
  /// Returns the path of the file.
78
0
  const std::string& get_path() const { return path; }
79
80
  /// Creates a new file in the specified path by previously closing any opened
81
  /// file.
82
  detail::error_string create(const std::string& new_path)
83
0
  {
84
0
    close();
85
86
0
    if ((handle = std::fopen(new_path.c_str(), "wb"))) {
87
0
      path = new_path;
88
0
      return {};
89
0
    }
90
91
0
    return format_error(fmt::format("Unable to create log file \"{}\"", new_path), errno);
92
0
  }
93
94
  /// Writes the provided memory buffer into an open file, otherwise does
95
  /// nothing.
96
  detail::error_string write(detail::memory_buffer buffer)
97
0
  {
98
0
    if (handle && std::fwrite(buffer.data(), sizeof(char), buffer.size(), handle) != buffer.size()) {
99
0
      auto err_str = format_error(fmt::format("Unable to write log file \"{}\"", path), errno);
100
0
      close();
101
0
      return err_str;
102
0
    }
103
104
0
    return {};
105
0
  }
106
107
  /// Flushes the contents of an open file, otherwise does nothing.
108
  detail::error_string flush()
109
0
  {
110
0
    if (handle && ::fflush(handle) == EOF) {
111
0
      auto err_str = format_error(fmt::format("Error encountered while flushing log file \"{}\"", path), errno);
112
0
      close();
113
0
      return err_str;
114
0
    }
115
116
0
    return {};
117
0
  }
118
119
  /// Closes an open file, otherwise does nothing.
120
  void close()
121
0
  {
122
0
    if (handle) {
123
0
      ::fclose(handle);
124
0
      handle = nullptr;
125
0
      path.clear();
126
0
    }
127
0
  }
128
};
129
130
} // namespace file_utils
131
132
} // namespace ocudulog