OILS / cpp / libc.cc View on Github | oils.pub

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