aboutsummaryrefslogtreecommitdiff
path: root/libbutl/path.cxx
blob: 3b047302ccdc44c530b428581ca7ce56f88d8797 (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
// file      : libbutl/path.cxx -*- C++ -*-
// license   : MIT; see accompanying LICENSE file

#ifndef __cpp_modules_ts
#include <libbutl/path.mxx>
#endif

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

#  include <io.h>       // _find*()
#  include <stdlib.h>   // _MAX_PATH
#  include <direct.h>   // _getcwd(), _chdir()
#  include <shlobj.h>   // SHGetFolderPath*(), CSIDL_PROFILE
#  include <winerror.h> // SUCCEEDED()
#else
#  include <pwd.h>       // struct passwd, getpwuid_r()
#  include <errno.h>     // EINVAL
#  include <stdlib.h>    // realpath()
#  include <limits.h>    // PATH_MAX
#  include <unistd.h>    // getcwd(), chdir()
#  include <string.h>    // strlen(), strcpy()
#  include <sys/stat.h>  // stat(), S_IS*
#  include <sys/types.h> // stat
#endif

#include <cassert>

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

#include <atomic>
#include <cstring> // strcpy()
#endif

#ifdef __cpp_modules_ts
module butl.path;

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

import butl.utility; // throw_*_error()
import butl.process; // process::current_id()
#else
#include <libbutl/utility.mxx>
#include <libbutl/process.mxx>
#endif

#include <libbutl/export.hxx>

#ifndef _WIN32
#  ifndef PATH_MAX
#    define PATH_MAX 4096
#  endif
#endif

using namespace std;

#ifdef _WIN32
using namespace butl::win32;
#endif

namespace butl
{
  invalid_path_base::
  invalid_path_base ()
      : invalid_argument ("invalid filesystem path")
  {
  }

  //
  // char
  //

  template <>
  LIBBUTL_SYMEXPORT path_traits<char>::string_type path_traits<char>::
  current_directory ()
  {
#ifdef _WIN32
    char cwd[_MAX_PATH];
    if (_getcwd (cwd, _MAX_PATH) == 0)
      throw_generic_error (errno);
    cwd[0] = toupper (cwd[0]); // Canonicalize.
#else
    char cwd[PATH_MAX];
    if (getcwd (cwd, PATH_MAX) == 0)
      throw_generic_error (errno);
#endif

    return cwd;
  }

  template <>
  LIBBUTL_SYMEXPORT void path_traits<char>::
  current_directory (string_type const& s)
  {
#ifdef _WIN32
    // A path like 'C:', while being a root path in our terminology, is not as
    // such for Windows, that maintains current directory for each drive, and
    // so "change current directory to C:" means "change the process current
    // directory to current directory on the C drive". Changing it to the root
    // one of the drive requires the trailing directory separator to be
    // present.
    //
    string_type const& d (
      !root (s)
      ? s
      : string_type (s + directory_separator));

    if (_chdir (d.c_str ()) != 0)
      throw_generic_error (errno);
#else
    if (chdir (s.c_str ()) != 0)
      throw_generic_error (errno);
#endif
  }

#ifndef _WIN32
  static const small_vector<string, 4> tmp_vars (
    {"TMPDIR", "TMP", "TEMP", "TEMPDIR"});

  static string
  temp_directory ()
  {
    optional<string> dir;

    for (const string& v: tmp_vars)
    {
      dir = getenv (v);

      if (dir)
        break;
    }

    if (!dir)
      dir = "/tmp";

    struct stat s;
    if (stat (dir->c_str (), &s) != 0)
      throw_generic_error (errno);

    if (!S_ISDIR (s.st_mode))
      throw_generic_error (ENOTDIR);

    return *dir;
  }

  static string
  home ()
  {
    if (optional<string> h = getenv ("HOME"))
      return *h;

    // Struct passwd has 5 members that will use this buffer. Two are the
    // home directory and shell paths. The other three are the user login
    // name, password, and real name (comment). We expect them to fit into
    // PATH_MAX * 2.
    //
    char buf[PATH_MAX * 4];

    passwd pw;
    passwd* rpw;

    int r (getpwuid_r (getuid (), &pw, buf, sizeof (buf), &rpw));
    if (r == -1)
      throw_generic_error (errno);

    if (r == 0 && rpw == nullptr)
      // According to POSIX errno should be left unchanged if an entry is not
      // found.
      //
      throw_generic_error (ENOENT);

    return pw.pw_dir;
  }
#endif

  template <>
  LIBBUTL_SYMEXPORT path_traits<char>::string_type path_traits<char>::
  temp_directory ()
  {
#ifdef _WIN32
    char d[_MAX_PATH + 1];
    if (GetTempPathA (_MAX_PATH + 1, d) == 0)
      throw_system_error (GetLastError ());

    return d;
#else
    return butl::temp_directory ();
#endif
  }

  static atomic<size_t> temp_name_count (0);

  template <>
  LIBBUTL_SYMEXPORT path_traits<char>::string_type path_traits<char>::
  temp_name (string_type const& prefix)
  {
    // Otherwise compiler get confused with butl::to_string(timestamp).
    //
    using std::to_string;

    return prefix
      + "-" + to_string (process::current_id ())
      + "-" + to_string (temp_name_count++);
  }

  template <>
  LIBBUTL_SYMEXPORT path_traits<char>::string_type path_traits<char>::
  home_directory ()
  {
#ifndef _WIN32
    return home ();
#else
    // Could be set by, e.g., MSYS and Cygwin shells.
    //
    if (optional<string> h = getenv ("HOME"))
      return *h;

    char h[_MAX_PATH];
    HRESULT r (SHGetFolderPathA (NULL, CSIDL_PROFILE, NULL, 0, h));

    if (!SUCCEEDED (r))
      throw_system_error (r);

    return h;
#endif
  }

#ifndef _WIN32
  template <>
  LIBBUTL_SYMEXPORT void path_traits<char>::
  realize (string_type& s)
  {
    char r[PATH_MAX];
    if (realpath (s.c_str (), r) == nullptr)
    {
      // @@ If there were a message in invalid_basic_path, we could have said
      // what exactly is wrong with the path.
      //
      if (errno == EACCES || errno == ENOENT || errno == ENOTDIR)
        throw invalid_basic_path<char> (s);
      else
        throw_generic_error (errno);
    }

    s = r;
  }
#else
  template <>
  LIBBUTL_SYMEXPORT bool
  basic_path_append_actual_name<char> (string& r,
                                       const string& d,
                                       const string& n)
  {
    assert (d.size () + n.size () + 1 < _MAX_PATH);

    char p[_MAX_PATH];
    strcpy (p, d.c_str ());
    p[d.size ()] = '\\';
    strcpy (p + d.size () + 1, n.c_str ());

    // It could be that using FindFirstFile() is faster.
    //
    _finddata_t fi;
    intptr_t h (_findfirst (p, &fi));

    if (h == -1 && errno == ENOENT)
      return false;

    if (h == -1 || _findclose (h) == -1)
      throw_generic_error (errno);

    r += fi.name;
    return true;
  }
#endif
}