cpp

Coverage Report

Created: 2023-11-29 23:45

/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
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
4
int fnmatch(BigStr* pat, BigStr* str, int flags) {
40
4
#ifdef FNM_EXTMATCH
41
4
  flags |= FNM_EXTMATCH;
42
#else
43
  // TODO: We should detect this at ./configure time, and then maybe flag these
44
  // at parse time, not runtime
45
#endif
46
47
4
  int result = ::fnmatch(pat->data_, str->data_, flags);
48
4
  switch (result) {
49
2
  case 0:
50
2
    return 1;
51
2
  case FNM_NOMATCH:
52
2
    return 0;
53
0
  default:
54
    // Other error
55
0
    return -1;
56
4
  }
57
4
}
58
59
2
List<BigStr*>* glob(BigStr* pat) {
60
2
  glob_t results;
61
  // Hm, it's weird that the first one can't be called with GLOB_APPEND.  You
62
  // get a segfault.
63
2
  int flags = 0;
64
  // int flags = GLOB_APPEND;
65
  // flags |= GLOB_NOMAGIC;
66
2
  int ret = glob(pat->data_, flags, NULL, &results);
67
68
2
  const char* err_str = NULL;
69
2
  switch (ret) {
70
1
  case 0:  // no error
71
1
    break;
72
0
  case GLOB_ABORTED:
73
0
    err_str = "read error";
74
0
    break;
75
1
  case GLOB_NOMATCH:
76
    // No error, because not matching isn't necessarily a problem.
77
    // NOTE: This can be turned on to log overaggressive calls to glob().
78
    // err_str = "nothing matched";
79
1
    break;
80
0
  case GLOB_NOSPACE:
81
0
    err_str = "no dynamic memory";
82
0
    break;
83
0
  default:
84
0
    err_str = "unknown problem";
85
0
    break;
86
2
  }
87
2
  if (err_str) {
88
0
    throw Alloc<RuntimeError>(StrFromC(err_str));
89
0
  }
90
91
  // http://stackoverflow.com/questions/3512414/does-this-pylist-appendlist-py-buildvalue-leak
92
2
  size_t n = results.gl_pathc;
93
2
  auto matches = NewList<BigStr*>();
94
95
  // Print array of results
96
2
  size_t i;
97
5
  for (i = 0; i < n; i++) {
98
3
    const char* m = results.gl_pathv[i];
99
3
    matches->append(StrFromC(m));
100
3
  }
101
2
  globfree(&results);
102
103
2
  return matches;
104
2
}
105
106
// Raises RuntimeError if the pattern is invalid.  TODO: Use a different
107
// exception?
108
2
List<BigStr*>* regex_match(BigStr* pattern, BigStr* str, int flags) {
109
2
  List<BigStr*>* results = NewList<BigStr*>();
110
111
2
  flags |= REG_EXTENDED;
112
2
  regex_t pat;
113
2
  if (regcomp(&pat, pattern->data_, flags) != 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
      BigStr* 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
Tuple2<int, int>* regex_first_group_match(BigStr* pattern, BigStr* str,
152
3
                                          int pos) {
153
3
  regex_t pat;
154
3
  regmatch_t m[NMATCH];
155
156
  // Could have been checked by regex_parse for [[ =~ ]], but not for glob
157
  // patterns like ${foo/x*/y}.
158
159
3
  if (regcomp(&pat, pattern->data_, REG_EXTENDED) != 0) {
160
0
    throw Alloc<RuntimeError>(
161
0
        StrFromC("Invalid regex syntax (func_regex_first_group_match)"));
162
0
  }
163
164
  // Match at offset 'pos'
165
3
  int result = regexec(&pat, str->data_ + pos, NMATCH, m, 0 /*flags*/);
166
3
  regfree(&pat);
167
168
3
  if (result != 0) {
169
0
    return nullptr;
170
0
  }
171
172
  // Assume there is a match
173
3
  regoff_t start = m[1].rm_so;
174
3
  regoff_t end = m[1].rm_eo;
175
3
  Tuple2<int, int>* tup = Alloc<Tuple2<int, int>>(pos + start, pos + end);
176
177
3
  return tup;
178
3
}
179
180
// TODO: SHARE with pyext
181
1
int wcswidth(BigStr* s) {
182
  // Behavior of mbstowcs() depends on LC_CTYPE
183
184
  // Calculate length first
185
1
  int num_wide_chars = mbstowcs(NULL, s->data_, 0);
186
1
  if (num_wide_chars == -1) {
187
0
    throw Alloc<UnicodeError>(StrFromC("mbstowcs() 1"));
188
0
  }
189
190
  // Allocate buffer
191
1
  int buf_size = (num_wide_chars + 1) * sizeof(wchar_t);
192
1
  wchar_t* wide_chars = static_cast<wchar_t*>(malloc(buf_size));
193
1
  assert(wide_chars != nullptr);
194
195
  // Convert to wide chars
196
0
  num_wide_chars = mbstowcs(wide_chars, s->data_, num_wide_chars);
197
1
  if (num_wide_chars == -1) {
198
0
    throw Alloc<UnicodeError>(StrFromC("mbstowcs() 2"));
199
0
  }
200
201
  // Find number of columns
202
1
  int width = ::wcswidth(wide_chars, num_wide_chars);
203
1
  if (width == -1) {
204
    // unprintable chars
205
0
    throw Alloc<UnicodeError>(StrFromC("wcswidth()"));
206
0
  }
207
1
  free(wide_chars);
208
209
1
  return width;
210
1
}
211
212
1
int get_terminal_width() {
213
1
  struct winsize w;
214
1
  if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) == -1) {
215
1
    throw Alloc<IOError>(errno);
216
1
  }
217
0
  return w.ws_col;
218
1
}
219
220
}  // namespace libc