cpp

Coverage Report

Created: 2023-09-13 01:07

/home/andy/git/oilshell/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
Str* gethostname() {
17
  // Note: Fixed issue #1656 - OS X and FreeBSD don't have HOST_NAME_MAX
18
  // https://reviews.freebsd.org/D30062
19
2
  Str* 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
Str* realpath(Str* path) {
30
2
  Str* 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
4
int fnmatch(Str* pat, Str* str) {
40
  // TODO: We should detect this at ./configure time, and then maybe flag these
41
  // at parse time, not runtime
42
4
#ifdef FNM_EXTMATCH
43
4
  int flags = FNM_EXTMATCH;
44
#else
45
  int flags = 0;
46
#endif
47
48
4
  int result = ::fnmatch(pat->data_, str->data_, flags);
49
4
  switch (result) {
50
2
  case 0:
51
2
    return 1;
52
2
  case FNM_NOMATCH:
53
2
    return 0;
54
0
  default:
55
    // Other error
56
0
    return -1;
57
4
  }
58
4
}
59
60
2
List<Str*>* glob(Str* pat) {
61
2
  glob_t results;
62
  // Hm, it's weird that the first one can't be called with GLOB_APPEND.  You
63
  // get a segfault.
64
2
  int flags = 0;
65
  // int flags = GLOB_APPEND;
66
  // flags |= GLOB_NOMAGIC;
67
2
  int ret = glob(pat->data_, flags, NULL, &results);
68
69
2
  const char* err_str = NULL;
70
2
  switch (ret) {
71
1
  case 0:  // no error
72
1
    break;
73
0
  case GLOB_ABORTED:
74
0
    err_str = "read error";
75
0
    break;
76
1
  case GLOB_NOMATCH:
77
    // No error, because not matching isn't necessarily a problem.
78
    // NOTE: This can be turned on to log overaggressive calls to glob().
79
    // err_str = "nothing matched";
80
1
    break;
81
0
  case GLOB_NOSPACE:
82
0
    err_str = "no dynamic memory";
83
0
    break;
84
0
  default:
85
0
    err_str = "unknown problem";
86
0
    break;
87
2
  }
88
2
  if (err_str) {
89
0
    throw Alloc<RuntimeError>(StrFromC(err_str));
90
0
  }
91
92
  // http://stackoverflow.com/questions/3512414/does-this-pylist-appendlist-py-buildvalue-leak
93
2
  size_t n = results.gl_pathc;
94
2
  auto matches = NewList<Str*>();
95
96
  // Print array of results
97
2
  size_t i;
98
5
  for (i = 0; i < n; i++) {
99
3
    const char* m = results.gl_pathv[i];
100
3
    matches->append(StrFromC(m));
101
3
  }
102
2
  globfree(&results);
103
104
2
  return matches;
105
2
}
106
107
// Raises RuntimeError if the pattern is invalid.  TODO: Use a different
108
// exception?
109
2
List<Str*>* regex_match(Str* pattern, Str* str) {
110
2
  List<Str*>* results = NewList<Str*>();
111
112
2
  regex_t pat;
113
2
  if (regcomp(&pat, pattern->data_, REG_EXTENDED) != 0) {
114
    // TODO: check error code, as in func_regex_parse()
115
0
    throw Alloc<RuntimeError>(StrFromC("Invalid regex syntax (regex_match)"));
116
0
  }
117
118
2
  int outlen = pat.re_nsub + 1;  // number of captures
119
120
2
  const char* s0 = str->data_;
121
2
  regmatch_t* pmatch =
122
2
      static_cast<regmatch_t*>(malloc(sizeof(regmatch_t) * outlen));
123
2
  int match = regexec(&pat, s0, outlen, pmatch, 0) == 0;
124
2
  if (match) {
125
1
    int i;
126
4
    for (i = 0; i < outlen; i++) {
127
3
      int len = pmatch[i].rm_eo - pmatch[i].rm_so;
128
3
      Str* m = StrFromC(s0 + pmatch[i].rm_so, len);
129
3
      results->append(m);
130
3
    }
131
1
  }
132
133
2
  free(pmatch);
134
2
  regfree(&pat);
135
136
2
  if (!match) {
137
1
    return nullptr;
138
1
  }
139
140
1
  return results;
141
2
}
142
143
// For ${//}, the number of groups is always 1, so we want 2 match position
144
// results -- the whole regex (which we ignore), and then first group.
145
//
146
// For [[ =~ ]], do we need to count how many matches the user gave?
147
148
const int NMATCH = 2;
149
150
// Odd: This a Tuple2* not Tuple2 because it's Optional[Tuple2]!
151
3
Tuple2<int, int>* regex_first_group_match(Str* pattern, Str* str, int pos) {
152
3
  regex_t pat;
153
3
  regmatch_t m[NMATCH];
154
155
  // Could have been checked by regex_parse for [[ =~ ]], but not for glob
156
  // patterns like ${foo/x*/y}.
157
158
3
  if (regcomp(&pat, pattern->data_, REG_EXTENDED) != 0) {
159
0
    throw Alloc<RuntimeError>(
160
0
        StrFromC("Invalid regex syntax (func_regex_first_group_match)"));
161
0
  }
162
163
  // Match at offset 'pos'
164
3
  int result = regexec(&pat, str->data_ + pos, NMATCH, m, 0 /*flags*/);
165
3
  regfree(&pat);
166
167
3
  if (result != 0) {
168
0
    return nullptr;
169
0
  }
170
171
  // Assume there is a match
172
3
  regoff_t start = m[1].rm_so;
173
3
  regoff_t end = m[1].rm_eo;
174
3
  Tuple2<int, int>* tup = Alloc<Tuple2<int, int>>(pos + start, pos + end);
175
176
3
  return tup;
177
3
}
178
179
// TODO: SHARE with pyext
180
1
int wcswidth(Str* s) {
181
  // Behavior of mbstowcs() depends on LC_CTYPE
182
183
  // Calculate length first
184
1
  int num_wide_chars = mbstowcs(NULL, s->data_, 0);
185
1
  if (num_wide_chars == -1) {
186
0
    throw Alloc<UnicodeError>(StrFromC("mbstowcs() 1"));
187
0
  }
188
189
  // Allocate buffer
190
1
  int buf_size = (num_wide_chars + 1) * sizeof(wchar_t);
191
1
  wchar_t* wide_chars = static_cast<wchar_t*>(malloc(buf_size));
192
1
  assert(wide_chars != nullptr);
193
194
  // Convert to wide chars
195
0
  num_wide_chars = mbstowcs(wide_chars, s->data_, num_wide_chars);
196
1
  if (num_wide_chars == -1) {
197
0
    throw Alloc<UnicodeError>(StrFromC("mbstowcs() 2"));
198
0
  }
199
200
  // Find number of columns
201
1
  int width = ::wcswidth(wide_chars, num_wide_chars);
202
1
  if (width == -1) {
203
    // unprintable chars
204
0
    throw Alloc<UnicodeError>(StrFromC("wcswidth()"));
205
0
  }
206
1
  free(wide_chars);
207
208
1
  return width;
209
1
}
210
211
1
int get_terminal_width() {
212
1
  struct winsize w;
213
1
  if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) == -1) {
214
1
    throw Alloc<IOError>(errno);
215
1
  }
216
0
  return w.ws_col;
217
1
}
218
219
}  // namespace libc