aboutsummaryrefslogtreecommitdiff
path: root/bpkg/host-os-release.cxx
blob: 1a5a980f276ea948a6c0544d8e665239ab78de97 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
// file      : bpkg/host-os-release.cxx -*- C++ -*-
// license   : MIT; see accompanying LICENSE file

#include <bpkg/host-os-release.hxx>

#include <libbutl/string-parser.hxx> // parse_quoted()

#include <bpkg/diagnostics.hxx>

using namespace butl;

namespace bpkg
{
  // Note: not static for access from the unit test.
  //
  os_release
  host_os_release_linux (path f = {})
  {
    os_release r;

    // According to os-release(5), we should use /etc/os-release and fallback
    // to /usr/lib/os-release if the former does not exist. It also lists the
    // fallback values for individual variables, in case some are not present.
    //
    if (!f.empty ()
        ? exists (f)
        : (exists (f = path ("/etc/os-release")) ||
           exists (f = path ("/usr/lib/os-release"))))
    {
      try
      {
        ifdstream ifs (f, ifdstream::badbit);

        string l;
        for (uint64_t ln (1); !eof (getline (ifs, l)); ++ln)
        {
          trim (l);

          // Skip blanks lines and comments.
          //
          if (l.empty () || l[0] == '#')
            continue;

          // The variable assignments are in the "shell style" and so can be
          // quoted/escaped. For now we only handle quoting, which is what all
          // the instances seen in the wild seems to use.
          //
          size_t p (l.find ('='));
          if (p == string::npos)
            continue;

          string n (l, 0, p);
          l.erase (0, p + 1);

          using string_parser::parse_quoted;
          using string_parser::invalid_string;

          try
          {
            if (n == "ID_LIKE")
            {
              r.like_ids.clear ();

              vector<string> vs (parse_quoted (l, true /* unquote */));
              for (const string& v: vs)
              {
                for (size_t b (0), e (0); next_word (v, b, e); )
                {
                  r.like_ids.push_back (string (v, b, e - b));
                }
              }
            }
            else if (string* p = (n == "ID"               ?  &r.name_id :
                                  n == "VERSION_ID"       ?  &r.version_id :
                                  n == "VARIANT_ID"       ?  &r.variant_id :
                                  n == "NAME"             ?  &r.name :
                                  n == "VERSION_CODENAME" ?  &r.version_codename :
                                  n == "VARIANT"          ?  &r.variant :
                                  nullptr))
            {
              vector<string> vs (parse_quoted (l, true /* unquote */));
              switch (vs.size ())
              {
              case 0:  *p =  ""; break;
              case 1:  *p = move (vs.front ()); break;
              default: throw invalid_string (0, "multiple values");
              }
            }
          }
          catch (const invalid_string& e)
          {
            location loc (move (f).string (), ln);
            fail (loc) << "invalid " << n << " value: " << e;
          }
        }

        ifs.close ();
      }
      catch (const io_error& e)
      {
        fail << "unable to read from " << f << ": " << e;
      }
    }

    // Assign fallback values.
    //
    if (r.name_id.empty ()) r.name_id = "linux";
    if (r.name.empty ())    r.name    = "Linux";

    return r;
  }

  optional<os_release>
  host_os_release (const target_triplet& host)
  {
    if (host.class_ == "linux")
      return host_os_release_linux ();
    else
      return nullopt;
  }
}