OILS / cpp / libc.cc View on Github | oilshell.org

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