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
20namespace KCrash
21{
22#ifdef Q_OS_LINUX
23MetadataINIWriter::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
40void 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
53void MetadataINIWriter::startTagsGroup() const
54{
55 startGroup(group: "[KCrashTags]\n");
56}
57
58void MetadataINIWriter::startExtraGroup() const
59{
60 startGroup(group: "[KCrashExtra]\n");
61}
62
63void MetadataINIWriter::startGPUGroup() const
64{
65 startGroup(group: "[KCrashGPU]\n");
66}
67
68void MetadataINIWriter::startKCrashGroup() const
69{
70 startGroup(group: "[KCrash]\n");
71}
72
73void MetadataINIWriter::close()
74{
75 if (fd >= 0) {
76 constexpr std::string_view endGroup = "[KCrashComplete]\n";
77 write(fd: fd, buf: endGroup.data(), n: endGroup.size());
78 if (::close(fd: fd) == -1) {
79 fprintf(stderr, format: "Failed to close metadata file: %s\n", strerror(errno));
80 }
81 }
82 writable = false;
83}
84
85void MetadataINIWriter::add(const char *key, const char *value, BoolValue boolValue)
86{
87 Q_ASSERT(key);
88 Q_ASSERT(value);
89 Q_ASSERT(key[0] == '-' && key[1] == '-'); // well-formed '--' prefix. This is important. MetadataWriter presume this
90 Q_UNUSED(boolValue); // value is a bool string but we don't care, we always write the value anyway
91
92 if (!writable) {
93 return;
94 }
95
96 const auto valueSpan = std::span{value, strlen(s: value)};
97
98 write(fd: fd, buf: key + 2, n: strlen(s: key + 2));
99 write(fd: fd, buf: "=", n: 1);
100 if (strstr(haystack: value, needle: "\n")) { // if it contains \n then write literally \n (2 characters)
101 // Could appear in the exception what() string. KConfig knows what to do with this.
102 for (const auto &character : valueSpan) {
103 if (character == '\n') {
104 write(fd: fd, buf: "\\n", n: 2);
105 } else {
106 write(fd: fd, buf: &character, n: 1);
107 }
108 }
109 } else { // fast write entire string in one go since it contains no newlines
110 write(fd: fd, buf: valueSpan.data(), n: valueSpan.size());
111 }
112 write(fd: fd, buf: "\n", n: 1);
113}
114
115bool MetadataINIWriter::isWritable() const
116{
117 return writable;
118}
119#endif
120
121Metadata::Metadata(const char *cmd)
122{
123 // NB: cmd may be null! Just because we create metadata doesn't mean we'll execute drkonqi (we may only need the
124 // backing writers)
125 Q_ASSERT(argc == 0);
126 argv.at(n: argc++) = cmd;
127}
128
129void Metadata::setAdditionalWriter(MetadataWriter *writer)
130{
131 // Once set the writer oughtn't be reset as we have no use case for this and should we get one in the future
132 // it'll need at least review of the existing code to handle writer switching correctly.
133 Q_ASSERT(m_writer == nullptr);
134 Q_ASSERT(writer != nullptr);
135 m_writer = writer;
136}
137
138void Metadata::add(const char *key, const char *value)
139{
140 add(key, value, boolValue: BoolValue::No);
141}
142
143void Metadata::addBool(const char *key)
144{
145 add(key, value: "true", boolValue: BoolValue::Yes);
146}
147
148void Metadata::close()
149{
150 // NULL terminated list
151 argv.at(n: argc) = nullptr;
152
153 if (m_writer) {
154 m_writer->close();
155 }
156}
157
158void Metadata::add(const char *key, const char *value, BoolValue boolValue)
159{
160 Q_ASSERT(key);
161 Q_ASSERT(value);
162 Q_ASSERT(key[0] == '-' && key[1] == '-'); // well-formed '--' prefix. This is important. MetadataWriter presume this
163 const auto belowCap = argc < argv.max_size();
164 Q_ASSERT(belowCap); // argv has a static max size. guard against exhaustion
165 if (!belowCap) {
166 return;
167 }
168
169 argv.at(n: argc++) = key;
170 if (!boolValue) {
171 argv.at(n: argc++) = value;
172 }
173
174 if (m_writer) {
175 m_writer->add(key, value, boolValue);
176 }
177}
178
179} // namespace KCrash
180

source code of kcrash/src/metadata.cpp