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 && ::close(fd: fd) == -1) {
76 fprintf(stderr, format: "Failed to close metadata file: %s\n", strerror(errno));
77 }
78 writable = false;
79}
80
81void 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
111bool MetadataINIWriter::isWritable() const
112{
113 return writable;
114}
115#endif
116
117Metadata::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
125void 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
134void Metadata::add(const char *key, const char *value)
135{
136 add(key, value, boolValue: BoolValue::No);
137}
138
139void Metadata::addBool(const char *key)
140{
141 add(key, value: "true", boolValue: BoolValue::Yes);
142}
143
144void 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
154void 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

source code of kcrash/src/metadata.cpp