aboutsummaryrefslogtreecommitdiff
path: root/libbutl/uuid-linux.cxx
blob: 76890885ec117bed9ef95a2d4394016155932455 (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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// file      : libbutl/uuid-linux.cxx -*- C++ -*-
// license   : MIT; see accompanying LICENSE file

#ifndef BUILD2_BOOTSTRAP

#include <libbutl/uuid.hxx>

#include <errno.h>
#include <dlfcn.h>

#include <mutex>
#include <cassert>
#include <utility>      // move()
#include <system_error>

#include <libbutl/utility.mxx> // function_cast()

using namespace std;

namespace butl
{
  // While we can safely assume libuuid.so.1 is present on every Linux machine
  // (it is part of the essential util-linux package since version 2.15.1),
  // the development files (uuid.h, .so/.a symlinks) are a different story.
  // So for maximum portability we will use dlopen()/dlsym() to "link" to this
  // library without any development files.
  //
  // It appears that not all execution paths in uuid_generate() are thread-
  // safe (see Ubuntu bug #1005878). So for now the calls are serialized but
  // maybe this could be optimized (e.g., if we can be sure a thread-safe path
  // will be taken). Note also that we may still end up in trouble if someone
  // else in the process calls libuuid directly.
  //
  // Note also that the Linux kernel has support for generatin random UUIDs
  // which is exposed to userspace via /proc/sys/kernel/random/uuid. This
  // could be another implementation option (though it's not clear since which
  // version it is available, seem at least from the 2.6 days).
  //
  using lock = unique_lock<mutex>;

  static mutex uuid_mutex;

  // <uuid.h>
  //
  using uuid_t = unsigned char[16];

  static void (*uuid_generate)           (uuid_t);
  static int  (*uuid_generate_time_safe) (uuid_t);

  static void* libuuid;

  static inline void
  dlfail (string what)
  {
    what += ": ";
    what += dlerror ();
    throw system_error (ENOSYS, system_category (), move (what));
  };

  void uuid_system_generator::
  initialize ()
  {
    assert (libuuid == nullptr);

    libuuid = dlopen ("libuuid.so.1", RTLD_LAZY | RTLD_GLOBAL);

    if (libuuid == nullptr)
      dlfail ("unable to load libuuid.so.1");

    uuid_generate =
      function_cast<decltype(uuid_generate)> (
        dlsym (libuuid, "uuid_generate"));

    if (uuid_generate == nullptr)
      dlfail ("unable to lookup uuid_generate() in libuuid.so.1");

    uuid_generate_time_safe =
      function_cast<decltype(uuid_generate_time_safe)> (
        dlsym (libuuid, "uuid_generate_time_safe"));

    // Delay the failure until/if we need this function (it was only added in
    // 2011 so may not be available on older systems).
    //
    //if (uuid_generate_time_safe == nullptr)
    //  dlfail ("unable to lookup uuid_generate_time_safe() in libuuid.so.1");
  }

  void uuid_system_generator::
  terminate ()
  {
    assert (libuuid != nullptr);

    if (dlclose (libuuid) != 0)
      dlfail ("unable to unload libuuid.so.1");

    libuuid = nullptr;
  }

  void
  uuid_throw_weak (); // uuid.cxx

  uuid uuid_system_generator::
  generate (bool strong)
  {
    lock l (uuid_mutex);

    if (libuuid == nullptr)
      initialize ();

    uuid_t d;
    uuid_generate (d);

    uuid r (d);
    assert (r.variant () == uuid_variant::dce); // Sanity check.

    // According to the uuid_generate() documentation (and confirmed by the
    // implementation) it generates a random uuid if high-quality randomness
    // is available and a MAC/time-based one otherwise (in other words, it
    // should never generate a pseudo-random UUID).
    //
    if (strong && r.version () != uuid_version::random)
    {
      if (uuid_generate_time_safe == nullptr ||
          uuid_generate_time_safe (d) == -1)
        uuid_throw_weak ();

      r.assign (d);
      assert (r.variant () == uuid_variant::dce);
    }

    return r;
  }
}

#endif // BUILD2_BOOTSTRAP