aboutsummaryrefslogtreecommitdiff
path: root/libbutl/utility.cxx
blob: b572d693596219fc75a10045c99ae90de68cb1b8 (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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
// file      : libbutl/utility.cxx -*- C++ -*-
// copyright : Copyright (c) 2014-2018 Code Synthesis Ltd
// license   : MIT; see accompanying LICENSE file

#ifndef __cpp_modules
#include <libbutl/utility.mxx>
#endif

#ifdef _WIN32
#include <libbutl/win32-utility.hxx>
#endif

#include <stdlib.h> // setenv(), unsetenv(), _putenv()

#ifndef __cpp_lib_modules
#include <string>
#include <cstddef>
#include <utility>

#include <ostream>
#include <type_traits>  // enable_if, is_base_of
#include <system_error>
#endif

#include <libbutl/ft/lang.hxx>
#include <libbutl/ft/exception.hxx>

#ifdef __cpp_modules
module butl.utility;

// Only imports additional to interface.
#ifdef __clang__
#ifdef __cpp_lib_modules
import std.core;
import std.io;
#endif
#endif

#endif

namespace butl
{
  using namespace std;

#ifndef __cpp_lib_uncaught_exceptions

#ifdef __cpp_thread_local
  thread_local
#else
  __thread
#endif
  bool exception_unwinding_dtor_ = false;

#ifdef _WIN32
  bool&
  exception_unwinding_dtor () {return exception_unwinding_dtor_;}
#endif

#endif

  [[noreturn]] void
  throw_generic_error (int errno_code, const char* what)
  {
    if (what == nullptr)
      throw system_error (errno_code, generic_category ());
    else
      throw system_error (errno_code, generic_category (), what);
  }

  [[noreturn]] void
#ifndef _WIN32
  throw_system_error (int system_code, int)
  {
    throw system_error (system_code, system_category ());
#else
  throw_system_error (int system_code, int fallback_errno_code)
  {
    // Here we work around MinGW libstdc++ that interprets Windows system error
    // codes (for example those returned by GetLastError()) as errno codes. The
    // resulting system_error description will have the following form:
    //
    // <system_code description>: <fallback_errno_code description>
    //
    // Also note that the fallback-related description suffix is stripped by
    // our custom operator<<(ostream, exception) for the common case (see
    // below).
    //
    throw system_error (fallback_errno_code,
                        system_category (),
                        win32::error_msg (system_code));
#endif
  }

  // Throw ios::failure differently depending on whether it is derived from
  // system_error.
  //
  template <bool v>
  [[noreturn]] static inline typename enable_if<v>::type
  throw_ios_failure (error_code e, const char* w)
  {
    // The idea here is to make an error code to be saved into failure
    // exception and to make a string returned by what() to contain the error
    // description plus an optional custom message if provided. Unfortunatelly
    // there is no way to say that the custom message is absent. Passing an
    // empty string results for libstdc++ (as of version 5.3.1) with a
    // description like this (note the ': ' prefix):
    //
    // : No such file or directory
    //
    // Note that our custom operator<<(ostream, exception) strips this prefix.
    //
    throw ios_base::failure (w != nullptr ? w : "", e);
  }

  template <bool v>
  [[noreturn]] static inline typename enable_if<!v>::type
  throw_ios_failure (error_code e, const char* w)
  {
    throw ios_base::failure (w != nullptr ? w : e.message ().c_str ());
  }

  [[noreturn]] void
  throw_generic_ios_failure (int errno_code, const char* w)
  {
    error_code ec (errno_code, generic_category ());
    throw_ios_failure<is_base_of<system_error, ios_base::failure>::value> (
      ec, w);
  }

  [[noreturn]] void
  throw_system_ios_failure (int system_code, const char* w)
  {
#ifdef _WIN32
    // Here we work around MinGW libstdc++ that interprets Windows system error
    // codes (for example those returned by GetLastError()) as errno codes.
    //
    // Note that the resulting system_error description will have ': Success.'
    // suffix that is stripped by our custom operator<<(ostream, exception).
    //
    error_code ec (0, system_category ());
    string m;

    if (w == nullptr)
    {
      m = win32::error_msg (system_code);
      w = m.c_str ();
    }
#else
    error_code ec (system_code, system_category ());
#endif

    throw_ios_failure<is_base_of<system_error, ios_base::failure>::value> (
      ec, w);
  }

  string&
  trim (string& l)
  {
    /*
    assert (trim (r = "") == "");
    assert (trim (r = " ") == "");
    assert (trim (r = " \t\r") == "");
    assert (trim (r = "a") == "a");
    assert (trim (r = " a") == "a");
    assert (trim (r = "a ") == "a");
    assert (trim (r = " \ta") == "a");
    assert (trim (r = "a \r") == "a");
    assert (trim (r = " a ") == "a");
    assert (trim (r = " \ta \r") == "a");
    assert (trim (r = "\na\n") == "a");
    */

    auto ws = [] (char c )
    {
      return c == ' ' || c == '\t' || c == '\n' || c == '\r';
    };

    size_t i (0), n (l.size ());

    for (; i != n && ws (l[i]);     ++i) ;
    for (; n != i && ws (l[n - 1]); --n) ;

    if (i != 0)
    {
      string s (l, i, n - i);
      l.swap (s);
    }
    else if (n != l.size ())
      l.resize (n);

    return l;
  }

  void
  setenv (const string& name, const string& value)
  {
#ifndef _WIN32
    if (::setenv (name.c_str (), value.c_str (), 1 /* overwrite */) == -1)
      throw_generic_error (errno);
#else
    // The documentation doesn't say how to obtain the failure reason, so we
    // will assume it to always be EINVAL (as the most probable one).
    //
    if (_putenv (string (name + '=' + value).c_str ()) == -1)
      throw_generic_error (EINVAL);
#endif
  }

  void
  unsetenv (const string& name)
  {
#ifndef _WIN32
    if (::unsetenv (name.c_str ()) == -1)
      throw_generic_error (errno);
#else
    // The documentation doesn't say how to obtain the failure reason, so we
    // will assume it to always be EINVAL (as the most probable one).
    //
    if (_putenv (string (name + '=').c_str ()) == -1)
      throw_generic_error (EINVAL);
#endif
  }
}

namespace std
{
  using namespace butl;

  ostream&
  operator<< (ostream& o, const exception& e)
  {
    const char* d (e.what ());
    const char* s (d);

    // Strip the leading junk (colons and spaces).
    //
    // Note that error descriptions for ios_base::failure exceptions thrown by
    // fdstream can have the ': ' prefix for libstdc++ (read more in comment
    // for throw_ios_failure()).
    //
    for (; *s == ' ' || *s == ':'; ++s) ;

    // Strip the trailing junk (periods, spaces, newlines).
    //
    // Note that msvcrt adds some junk like this:
    //
    // Invalid data.\r\n
    //
    size_t n (string::traits_type::length (s));
    for (; n > 0; --n)
    {
      switch (s[n-1])
      {
      case '\r':
      case '\n':
      case '.':
      case ' ': continue;
      }

      break;
    }

    // Strip the suffix for system_error thrown by
    // throw_system_error(system_code) on Windows. For example for the
    // ERROR_INVALID_DATA error code the original description will be 'Invalid
    // data. : Success' or 'Invalid data. : No error' for MinGW libstdc++ and
    // 'Invalid data. : Success.' or ". : The operation completed
    // successfully." for msvcrt.
    //
    // Check if the string ends with the specified suffix and return its
    // length if that's the case. So can be used as bool.
    //
    auto suffix = [s, n] (const char* v) -> size_t
    {
      size_t nv (string::traits_type::length (v));
      return (n >= nv && string::traits_type::compare (s + n - nv, v, nv) == 0
              ? nv
              : 0);
    };

    size_t ns;
    if ((ns = suffix (". : Success"))  ||
        (ns = suffix (". : No error")) ||
        (ns = suffix (". : The operation completed successfully")))
      n -= ns;

    // Lower-case the first letter if the beginning looks like a word (the
    // second character is the lower-case letter or space).
    //
    char c;
    bool lc (n > 0 && alpha (c = s[0]) && c == ucase (c) &&
             (n == 1 || (alpha (c = s[1]) && c == lcase (c)) || c == ' '));

    // Print the description as is if no adjustment is required.
    //
    if (!lc && s == d && s[n] == '\0')
      o << d;
    else
    {
      // We need to produce the resulting description and then write it
      // with a single formatted output operation.
      //
      string r (s, n);

      if (lc)
        r[0] = lcase (r[0]);

      o << r;
    }

    return o;
  }
}