cpp

Coverage Report

Created: 2025-05-15 01:24

/home/uke/oil/cpp/libc.cc
Line
Count
Source (jump to first uncovered line)
1
// libc.cc: Replacement for pyext/libc.c
2
3
#include "cpp/libc.h"
4
5
#include <errno.h>
6
#include <fnmatch.h>
7
#include <glob.h>
8
#include <locale.h>
9
#include <regex.h>
10
#include <sys/ioctl.h>
11
#include <unistd.h>  // gethostname()
12
#include <wchar.h>
13
14
namespace libc {
15
16
2
BigStr* gethostname() {
17
  // Note: Fixed issue #1656 - OS X and FreeBSD don't have HOST_NAME_MAX
18
  // https://reviews.freebsd.org/D30062
19
2
  BigStr* result = OverAllocatedStr(_POSIX_HOST_NAME_MAX);
20
2
  int status = ::gethostname(result->data_, _POSIX_HOST_NAME_MAX);
21
2
  if (status != 0) {
22
0
    throw Alloc<OSError>(errno);
23
0
  }
24
  // Important: set the length of the string!
25
2
  result->MaybeShrink(strlen(result->data_));
26
2
  return result;
27
2
}
28
29
2
BigStr* realpath(BigStr* path) {
30
2
  BigStr* result = OverAllocatedStr(PATH_MAX);
31
2
  char* p = ::realpath(path->data_, result->data_);
32
2
  if (p == nullptr) {
33
1
    throw Alloc<OSError>(errno);
34
1
  }
35
1
  result->MaybeShrink(strlen(result->data_));
36
1
  return result;
37
2
}
38
39
5
int fnmatch(BigStr* pat, BigStr* str, int flags) {
40
5
#ifdef FNM_EXTMATCH
41
5
  flags |= FNM_EXTMATCH;
42
#else
43
  // Detected by ./configure
44
#endif
45
46
5
  int result = ::fnmatch(pat->data_, str->data_, flags);
47
5
  switch (result) {
48
3
  case 0:
49
3
    return 1;
50
2
  case FNM_NOMATCH:
51
2
    return 0;
52
0
  default:
53
    // Other error
54
0
    return -1;
55
5
  }
56
5
}
57
58
2
List<BigStr*>* glob(BigStr* pat, int flags) {
59
2
  glob_t results;
60
  // Hm, it's weird that the first one can't be called with GLOB_APPEND.  You
61
  // get a segfault.
62
  // int flags = GLOB_APPEND;
63
  // flags |= GLOB_NOMAGIC;
64
2
  int ret = glob(pat->data_, flags, NULL, &results);
65
66
2
  const char* err_str = NULL;
67
2
  switch (ret) {
68
1
  case 0:  // no error
69
1
    break;
70
0
  case GLOB_ABORTED:
71
0
    err_str = "read error";
72
0
    break;
73
1
  case GLOB_NOMATCH:
74
    // No error, because not matching isn't necessarily a problem.
75
    // NOTE: This can be turned on to log overaggressive calls to glob().
76
    // err_str = "nothing matched";
77
1
    break;
78
0
  case GLOB_NOSPACE:
79
0
    err_str = "no dynamic memory";
80
0
    break;
81
0
  default:
82
0
    err_str = "unknown problem";
83
0
    break;
84
2
  }
85
2
  if (err_str) {
86
0
    throw Alloc<RuntimeError>(StrFromC(err_str));
87
0
  }
88
89
  // http://stackoverflow.com/questions/3512414/does-this-pylist-appendlist-py-buildvalue-leak
90
2
  size_t n = results.gl_pathc;
91
2
  auto matches = NewList<BigStr*>();
92
93
  // Print array of results
94
2
  size_t i;
95
5
  for (i = 0; i < n; i++) {
96
3
    const char* m = results.gl_pathv[i];
97
3
    matches->append(StrFromC(m));
98
3
  }
99
2
  globfree(&results);
100
101
2
  return matches;
102
2
}
103
104
// Raises RuntimeError if the pattern is invalid.  TODO: Use a different
105
// exception?
106
List<int>* regex_search(BigStr* pattern, int cflags, BigStr* str, int eflags,
107
4
                        int pos) {
108
4
  cflags |= REG_EXTENDED;
109
4
  regex_t pat;
110
4
  int status = regcomp(&pat, pattern->data_, cflags);
111
4
  if (status != 0) {
112
0
    char error_desc[50];
113
0
    regerror(status, &pat, error_desc, 50);
114
115
0
    char error_message[80];
116
0
    snprintf(error_message, 80, "Invalid regex %s (%s)", pattern->data_,
117
0
             error_desc);
118
119
0
    throw Alloc<ValueError>(StrFromC(error_message));
120
0
  }
121
  // log("pat = %d, str = %d", len(pattern), len(str));
122
123
4
  int num_groups = pat.re_nsub + 1;  // number of captures
124
125
4
  List<int>* indices = NewList<int>();
126
4
  indices->reserve(num_groups * 2);
127
128
4
  const char* s = str->data_;
129
4
  regmatch_t* pmatch =
130
4
      static_cast<regmatch_t*>(malloc(sizeof(regmatch_t) * num_groups));
131
4
  bool match = regexec(&pat, s + pos, num_groups, pmatch, eflags) == 0;
132
4
  if (match) {
133
3
    int i;
134
10
    for (i = 0; i < num_groups; i++) {
135
7
      int start = pmatch[i].rm_so;
136
7
      if (start != -1) {
137
6
        start += pos;
138
6
      }
139
7
      indices->append(start);
140
141
7
      int end = pmatch[i].rm_eo;
142
7
      if (end != -1) {
143
6
        end += pos;
144
6
      }
145
7
      indices->append(end);
146
7
    }
147
3
  }
148
149
4
  free(pmatch);
150
4
  regfree(&pat);
151
152
4
  if (!match) {
153
1
    return nullptr;
154
1
  }
155
156
3
  return indices;
157
4
}
158
159
// For ${//}, the number of groups is always 1, so we want 2 match position
160
// results -- the whole regex (which we ignore), and then first group.
161
//
162
// For [[ =~ ]], do we need to count how many matches the user gave?
163
164
const int NMATCH = 2;
165
166
// Odd: This a Tuple2* not Tuple2 because it's Optional[Tuple2]!
167
Tuple2<int, int>* regex_first_group_match(BigStr* pattern, BigStr* str,
168
3
                                          int pos) {
169
3
  regex_t pat;
170
3
  regmatch_t m[NMATCH];
171
172
  // Could have been checked by regex_parse for [[ =~ ]], but not for glob
173
  // patterns like ${foo/x*/y}.
174
175
3
  if (regcomp(&pat, pattern->data_, REG_EXTENDED) != 0) {
176
0
    throw Alloc<RuntimeError>(
177
0
        StrFromC("Invalid regex syntax (func_regex_first_group_match)"));
178
0
  }
179
180
  // Match at offset 'pos'
181
3
  int result = regexec(&pat, str->data_ + pos, NMATCH, m, 0 /*flags*/);
182
3
  regfree(&pat);
183
184
3
  if (result != 0) {
185
0
    return nullptr;
186
0
  }
187
188
  // Assume there is a match
189
3
  regoff_t start = m[1].rm_so;
190
3
  regoff_t end = m[1].rm_eo;
191
3
  Tuple2<int, int>* tup = Alloc<Tuple2<int, int>>(pos + start, pos + end);
192
193
3
  return tup;
194
3
}
195
196
1
int wcswidth(BigStr* s) {
197
  // Behavior of mbstowcs() depends on LC_CTYPE
198
199
  // Calculate length first
200
1
  int num_wide_chars = ::mbstowcs(NULL, s->data_, 0);
201
1
  if (num_wide_chars == -1) {
202
0
    throw Alloc<UnicodeError>(StrFromC("mbstowcs() 1"));
203
0
  }
204
205
  // Allocate buffer
206
1
  int buf_size = (num_wide_chars + 1) * sizeof(wchar_t);
207
1
  wchar_t* wide_chars = static_cast<wchar_t*>(malloc(buf_size));
208
1
  DCHECK(wide_chars != nullptr);
209
210
  // Convert to wide chars
211
0
  num_wide_chars = ::mbstowcs(wide_chars, s->data_, num_wide_chars);
212
1
  if (num_wide_chars == -1) {
213
0
    free(wide_chars);  // cleanup
214
215
0
    throw Alloc<UnicodeError>(StrFromC("mbstowcs() 2"));
216
0
  }
217
218
  // Find number of columns
219
1
  int width = ::wcswidth(wide_chars, num_wide_chars);
220
1
  if (width == -1) {
221
0
    free(wide_chars);  // cleanup
222
223
    // unprintable chars
224
0
    throw Alloc<UnicodeError>(StrFromC("wcswidth()"));
225
0
  }
226
227
1
  free(wide_chars);
228
1
  return width;
229
1
}
230
231
1
int get_terminal_width() {
232
1
  struct winsize w;
233
1
  if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) == -1) {
234
1
    throw Alloc<IOError>(errno);
235
1
  }
236
0
  return w.ws_col;
237
1
}
238
239
}  // namespace libc