cpp

Coverage Report

Created: 2023-03-07 20:24

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