1 | /* |
2 | SPDX-License-Identifier: LGPL-2.0-or-later |
3 | SPDX-FileCopyrightText: 2021 Harald Sitter <sitter@kde.org> |
4 | */ |
5 | |
6 | #include "metadata_p.h" |
7 | |
8 | #include <QByteArray> |
9 | #include <cerrno> |
10 | |
11 | #ifdef Q_OS_LINUX |
12 | #include <cstring> |
13 | #include <fcntl.h> |
14 | #include <span> |
15 | #include <sys/stat.h> |
16 | #include <sys/types.h> |
17 | #include <unistd.h> |
18 | #endif |
19 | |
20 | namespace KCrash |
21 | { |
22 | #ifdef Q_OS_LINUX |
23 | MetadataINIWriter::MetadataINIWriter(const QByteArray &path) |
24 | { |
25 | if (path.isEmpty()) { |
26 | return; |
27 | } |
28 | |
29 | fd = ::open(file: path.constData(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, S_IRUSR | S_IWUSR); |
30 | if (fd == -1) { |
31 | fprintf(stderr, format: "Failed to open metadata file: %s\n" , strerror(errno)); |
32 | } else if (fd >= 0) { |
33 | writable = true; |
34 | } else { |
35 | fprintf(stderr, format: "MetadataINIWriter: Unexpected fd %d\n" , fd); |
36 | Q_UNREACHABLE(); |
37 | } |
38 | } |
39 | |
40 | void MetadataINIWriter::startGroup(const char *group) const |
41 | { |
42 | Q_ASSERT(group); // not null |
43 | Q_ASSERT(strlen(group) > 0); // not empty |
44 | Q_ASSERT(group[strlen(group) - 1] == '\n'); // end in newline |
45 | |
46 | if (!writable) { |
47 | return; |
48 | } |
49 | |
50 | write(fd: fd, buf: group, n: strlen(s: group)); |
51 | } |
52 | |
53 | void MetadataINIWriter::startTagsGroup() const |
54 | { |
55 | startGroup(group: "[KCrashTags]\n" ); |
56 | } |
57 | |
58 | void MetadataINIWriter::() const |
59 | { |
60 | startGroup(group: "[KCrashExtra]\n" ); |
61 | } |
62 | |
63 | void MetadataINIWriter::startGPUGroup() const |
64 | { |
65 | startGroup(group: "[KCrashGPU]\n" ); |
66 | } |
67 | |
68 | void MetadataINIWriter::startKCrashGroup() const |
69 | { |
70 | startGroup(group: "[KCrash]\n" ); |
71 | } |
72 | |
73 | void MetadataINIWriter::close() |
74 | { |
75 | if (fd >= 0 && ::close(fd: fd) == -1) { |
76 | fprintf(stderr, format: "Failed to close metadata file: %s\n" , strerror(errno)); |
77 | } |
78 | writable = false; |
79 | } |
80 | |
81 | void MetadataINIWriter::add(const char *key, const char *value, BoolValue boolValue) |
82 | { |
83 | Q_ASSERT(key); |
84 | Q_ASSERT(value); |
85 | Q_ASSERT(key[0] == '-' && key[1] == '-'); // well-formed '--' prefix. This is important. MetadataWriter presume this |
86 | Q_UNUSED(boolValue); // value is a bool string but we don't care, we always write the value anyway |
87 | |
88 | if (!writable) { |
89 | return; |
90 | } |
91 | |
92 | const auto valueSpan = std::span{value, strlen(s: value)}; |
93 | |
94 | write(fd: fd, buf: key + 2, n: strlen(s: key + 2)); |
95 | write(fd: fd, buf: "=" , n: 1); |
96 | if (strstr(haystack: value, needle: "\n" )) { // if it contains \n then write literally \n (2 characters) |
97 | // Could appear in the exception what() string. KConfig knows what to do with this. |
98 | for (const auto &character : valueSpan) { |
99 | if (character == '\n') { |
100 | write(fd: fd, buf: "\\n" , n: 2); |
101 | } else { |
102 | write(fd: fd, buf: &character, n: 1); |
103 | } |
104 | } |
105 | } else { // fast write entire string in one go since it contains no newlines |
106 | write(fd: fd, buf: valueSpan.data(), n: valueSpan.size()); |
107 | } |
108 | write(fd: fd, buf: "\n" , n: 1); |
109 | } |
110 | |
111 | bool MetadataINIWriter::isWritable() const |
112 | { |
113 | return writable; |
114 | } |
115 | #endif |
116 | |
117 | Metadata::Metadata(const char *cmd) |
118 | { |
119 | // NB: cmd may be null! Just because we create metadata doesn't mean we'll execute drkonqi (we may only need the |
120 | // backing writers) |
121 | Q_ASSERT(argc == 0); |
122 | argv.at(n: argc++) = cmd; |
123 | } |
124 | |
125 | void Metadata::setAdditionalWriter(MetadataWriter *writer) |
126 | { |
127 | // Once set the writer oughtn't be reset as we have no use case for this and should we get one in the future |
128 | // it'll need at least review of the existing code to handle writer switching correctly. |
129 | Q_ASSERT(m_writer == nullptr); |
130 | Q_ASSERT(writer != nullptr); |
131 | m_writer = writer; |
132 | } |
133 | |
134 | void Metadata::add(const char *key, const char *value) |
135 | { |
136 | add(key, value, boolValue: BoolValue::No); |
137 | } |
138 | |
139 | void Metadata::addBool(const char *key) |
140 | { |
141 | add(key, value: "true" , boolValue: BoolValue::Yes); |
142 | } |
143 | |
144 | void Metadata::close() |
145 | { |
146 | // NULL terminated list |
147 | argv.at(n: argc) = nullptr; |
148 | |
149 | if (m_writer) { |
150 | m_writer->close(); |
151 | } |
152 | } |
153 | |
154 | void Metadata::add(const char *key, const char *value, BoolValue boolValue) |
155 | { |
156 | Q_ASSERT(key); |
157 | Q_ASSERT(value); |
158 | Q_ASSERT(key[0] == '-' && key[1] == '-'); // well-formed '--' prefix. This is important. MetadataWriter presume this |
159 | const auto belowCap = argc < argv.max_size(); |
160 | Q_ASSERT(belowCap); // argv has a static max size. guard against exhaustion |
161 | if (!belowCap) { |
162 | return; |
163 | } |
164 | |
165 | argv.at(n: argc++) = key; |
166 | if (!boolValue) { |
167 | argv.at(n: argc++) = value; |
168 | } |
169 | |
170 | if (m_writer) { |
171 | m_writer->add(key, value, boolValue); |
172 | } |
173 | } |
174 | |
175 | } // namespace KCrash |
176 | |