1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
11 #include <string_view>
13 #include "base/process_util.h"
14 #include "mozilla/FileUtils.h"
15 #include "mozilla/Result.h"
16 #include "mozilla/ResultVariant.h"
17 #include "mozilla/ipc/LaunchError.h"
19 namespace mozilla::widget::lsb
{
21 static const char gLsbReleasePath
[] = "/usr/bin/lsb_release";
22 static const char gEtcOsReleasePath
[] = "/etc/os-release";
23 static const char gUsrOsReleasePath
[] = "/usr/lib/os-release";
25 // See https://www.freedesktop.org/software/systemd/man/latest/os-release.html
26 bool ExtractAndSetValue(nsACString
& aContainer
, std::string_view
& aValue
) {
27 // We assume the value is well formed and doesn't contain escape characters.
28 if (aValue
.size() > 1 && (aValue
.front() == '"' || aValue
.front() == '\'')) {
29 // We assume the quote is properly balanced.
30 aValue
= aValue
.substr(1, aValue
.size() - 2);
32 aContainer
.Assign(aValue
.data(), aValue
.size());
33 return !aValue
.empty();
36 bool GetOSRelease(nsACString
& aDistributor
, nsACString
& aDescription
,
37 nsACString
& aRelease
, nsACString
& aCodename
) {
38 std::ifstream
stream(gEtcOsReleasePath
);
40 stream
.open(gUsrOsReleasePath
);
45 bool seen_id
= false, seen_pretty_name
= false, seen_version_id
= false;
48 nsAutoCString build_id
;
49 while (std::getline(stream
, rawline
)) {
50 std::string_view
line(rawline
);
51 size_t pos
= line
.find('=');
52 if (pos
!= std::string_view::npos
) {
53 auto key
= line
.substr(0, pos
);
54 auto value
= line
.substr(pos
+ 1);
56 if (ExtractAndSetValue(aDistributor
, value
)) {
57 // Capitalize the first letter of the id. This mimics what
58 // lsb_release does on Debian and derivatives. On RH derivatives,
59 // ID tends to be capitalized already.
60 char* c
= aDistributor
.BeginWriting();
61 if (*c
>= 'a' && *c
<= 'z') {
66 } else if (key
== "NAME") {
67 ExtractAndSetValue(name
, value
);
68 } else if (key
== "PRETTY_NAME") {
69 if (ExtractAndSetValue(aDescription
, value
)) seen_pretty_name
= true;
70 } else if (key
== "VERSION_ID") {
71 if (ExtractAndSetValue(aRelease
, value
)) seen_version_id
= true;
72 } else if (key
== "BUILD_ID") {
73 ExtractAndSetValue(build_id
, value
);
74 } else if (key
== "VERSION_CODENAME") {
75 ExtractAndSetValue(aCodename
, value
);
79 // If NAME is set and only differs from ID in case, use NAME.
80 if (seen_id
&& !name
.IsEmpty() && name
.EqualsIgnoreCase(aDistributor
)) {
83 // If VERSION_ID is not set but BUILD_ID is, use BUILD_ID.
84 if (!seen_version_id
&& !build_id
.IsEmpty()) {
86 seen_version_id
= true;
88 // Only consider our work done if we've seen at least ID, PRETTY_NAME and
90 return seen_id
&& seen_pretty_name
&& seen_version_id
;
93 bool GetLSBRelease(nsACString
& aDistributor
, nsACString
& aDescription
,
94 nsACString
& aRelease
, nsACString
& aCodename
) {
95 // Nowadays, /etc/os-release is more likely to be available than
96 // /usr/bin/lsb_release. Relying on the former also avoids forking.
97 if (GetOSRelease(aDistributor
, aDescription
, aRelease
, aCodename
)) {
101 if (access(gLsbReleasePath
, R_OK
) != 0) return false;
104 if (pipe(pipefd
) == -1) {
105 NS_WARNING("pipe() failed!");
109 std::vector
<std::string
> argv
= {gLsbReleasePath
, "-idrc"};
111 base::LaunchOptions options
;
112 options
.fds_to_remap
.push_back({pipefd
[1], STDOUT_FILENO
});
115 base::ProcessHandle process
;
116 Result
<Ok
, ipc::LaunchError
> err
=
117 base::LaunchApp(argv
, std::move(options
), &process
);
120 NS_WARNING("Failed to spawn lsb_release!");
125 ScopedCloseFile
stream(fdopen(pipefd
[0], "r"));
127 NS_WARNING("Could not wrap fd!");
132 char dist
[256], desc
[256], release
[256], codename
[256];
133 if (fscanf(stream
.get(),
134 "Distributor ID:\t%255[^\n]\n"
135 "Description:\t%255[^\n]\n"
136 "Release:\t%255[^\n]\n"
137 "Codename:\t%255[^\n]\n",
138 dist
, desc
, release
, codename
) != 4) {
139 NS_WARNING("Failed to parse lsb_release!");
143 aDistributor
.Assign(dist
);
144 aDescription
.Assign(desc
);
145 aRelease
.Assign(release
);
146 aCodename
.Assign(codename
);
150 } // namespace mozilla::widget::lsb