1 /* windows.c
2  * Copyright 1984-2017 Cisco Systems, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 /* much of the following code courtesy of Bob Burger, burgerrg@sagian.com */
18 
19 #include "system.h"
20 #include <objbase.h>
21 #include <io.h>
22 #include <sys/stat.h>
23 
24 static ptr s_ErrorStringImp(DWORD dwMessageId, const char *lpcDefault);
25 static ptr s_ErrorString(DWORD dwMessageId);
26 static IUnknown *s_CreateInstance(CLSID *pCLSID, IID *iid);
27 static ptr s_GetRegistry(wchar_t *s);
28 static void s_PutRegistry(wchar_t *s, wchar_t *val);
29 static void s_RemoveRegistry(wchar_t *s);
30 
S_machine_init()31 void S_machine_init() {
32     Sregister_symbol("(com)CreateInstance", (void *)s_CreateInstance);
33     Sregister_symbol("(windows)GetRegistry", (void *)s_GetRegistry);
34     Sregister_symbol("(windows)PutRegistry", (void *)s_PutRegistry);
35     Sregister_symbol("(windows)RemoveRegistry", (void *)s_RemoveRegistry);
36     Sregister_symbol("(windows)ErrorString", (void *)s_ErrorString);
37 }
38 
S_getpagesize()39 INT S_getpagesize() {
40   SYSTEM_INFO si;
41   GetSystemInfo(&si);
42   return si.dwPageSize;
43 }
44 
S_ntdlopen(const char * path)45 void *S_ntdlopen(const char *path) {
46   wchar_t *pathw = Sutf8_to_wide(path);
47   void *r = (void *)LoadLibraryW(pathw);
48   free(pathw);
49   return r;
50 }
51 
S_ntdlsym(void * h,const char * s)52 void *S_ntdlsym(void *h, const char *s) {
53     return (void *)GetProcAddress(h, s);
54 }
55 
56 /* Initial version of S_ntdlerror courtesy of Bob Burger
57  * Modifications by James-Adam Renquinha Henri, jarhmander@gmail.com */
S_ntdlerror(void)58 ptr S_ntdlerror(void) {
59     return s_ErrorStringImp(GetLastError(), "unable to load library");
60 }
61 
62 #ifdef FLUSHCACHE
63 oops, no S_flushcache_max_gap or S_doflush
64 #endif /* FLUSHCACHE */
65 
66 static void SplitRegistryKey(char *who, wchar_t *wholekey, HKEY *key, wchar_t **subkey, wchar_t **last) {
67   wchar_t c, *s;
68 
69  /* Determine the base key */
70   if (_wcsnicmp(wholekey, L"HKEY_CLASSES_ROOT\\", 18) == 0) {
71     *key = HKEY_CLASSES_ROOT;
72     *subkey = wholekey+18;
73   } else if (_wcsnicmp(wholekey, L"HKEY_CURRENT_USER\\", 18) == 0) {
74     *key = HKEY_CURRENT_USER;
75     *subkey = wholekey+18;
76   } else if (_wcsnicmp(wholekey, L"HKEY_LOCAL_MACHINE\\", 19) == 0) {
77     *key = HKEY_LOCAL_MACHINE;
78     *subkey = wholekey+19;
79   } else if (_wcsnicmp(wholekey, L"HKEY_USERS\\", 11) == 0) {
80     *key = HKEY_USERS;
81     *subkey = wholekey+11;
82   } else if (_wcsnicmp(wholekey, L"HKEY_CURRENT_CONFIG\\", 20) == 0) {
83     *key = HKEY_CURRENT_CONFIG;
84     *subkey = wholekey+20;
85   } else if (_wcsnicmp(wholekey, L"HKEY_DYN_DATA\\", 14) == 0) {
86     *key = HKEY_DYN_DATA;
87     *subkey = wholekey+14;
88   } else {
89     char *wholekey_utf8 = Swide_to_utf8(wholekey);
90     ptr wholekey_scheme = Sstring_utf8(wholekey_utf8, -1);
91     free(wholekey_utf8);
92     S_error1(who, "invalid registry key ~s", wholekey_scheme);
93   }
94 
95   for (*last = s = *subkey, c = *s; c != '\0'; c = *++s)
96     if (c == '\\') *last = s;
97 }
98 
s_GetRegistry(wchar_t * s)99 static ptr s_GetRegistry(wchar_t *s) {
100   HKEY key, result;
101   wchar_t *subkey, *last;
102   DWORD rc, type, size;
103   ptr ans;
104 
105   SplitRegistryKey("get-registry", s, &key, &subkey, &last);
106 
107  /* open the key */
108   if (last == subkey) {
109     rc = RegOpenKeyExW(key, L"", 0, KEY_QUERY_VALUE, &result);
110   } else {
111     *last = '\0'; /* Truncate subkey at backslash */
112     rc = RegOpenKeyExW(key, subkey, 0, KEY_QUERY_VALUE, &result);
113     *last++ = '\\'; /* Restore backslash */
114   }
115   if (rc != ERROR_SUCCESS) return Sfalse;
116 
117  /* Get the size of the value */
118   rc = RegQueryValueExW(result, last, NULL, &type, NULL, &size);
119   if (rc != ERROR_SUCCESS) {
120     RegCloseKey(result);
121     return Sfalse;
122   }
123 
124  /* Allocate a Scheme bytevector of the proper size */
125   ans = S_bytevector(size);
126 
127  /* Load up the bytevector */
128   rc = RegQueryValueExW(result, last, NULL, &type, &BVIT(ans,0), &size);
129   RegCloseKey(result);
130   if (rc != ERROR_SUCCESS) return Sfalse;
131 
132  /* discard unwanted terminating null character, if present */
133   if (((type == REG_SZ) || (type == REG_EXPAND_SZ)) &&
134       (size >= 2) &&
135       (*(wchar_t*)(&BVIT(ans, size-2)) == 0))
136     BYTEVECTOR_TYPE(ans) = ((size-2) << bytevector_length_offset) | type_bytevector;
137 
138   return ans;
139 }
140 
s_PutRegistry(wchar_t * s,wchar_t * val)141 static void s_PutRegistry(wchar_t *s, wchar_t *val) {
142   HKEY key, result;
143   wchar_t *subkey, *last;
144   DWORD rc, type;
145   size_t n = (wcslen(val) + 1) * sizeof(wchar_t);
146 #if (size_t_bits > 32)
147   if ((DWORD)n != n)  {
148     char *s_utf8 = Swide_to_utf8(s);
149     ptr s_scheme = Sstring_utf8(s_utf8, -1);
150     free(s_utf8);
151     S_error2("put-registry!", "cannot set ~a (~a)", s_scheme, Sstring("too long"));
152   }
153 #endif
154 
155   SplitRegistryKey("put-registry!", s, &key, &subkey, &last);
156 
157  /* create/open the key */
158   if (last == subkey) {
159     rc = RegCreateKeyExW(key, L"", 0, NULL, 0, KEY_SET_VALUE, NULL, &result, NULL);
160   } else {
161     *last = '\0'; /* Truncate subkey at backslash */
162     rc = RegCreateKeyExW(key, subkey, 0, NULL, 0, KEY_SET_VALUE, NULL, &result, NULL);
163     *last++ = '\\'; /* Restore backslash */
164   }
165 
166   if (rc == ERROR_SUCCESS) {
167    /* lookup type for key (if it exists), if not assume REG_SZ */
168     if (ERROR_SUCCESS != RegQueryValueExW(result, last, NULL, &type, NULL, NULL))
169       type = REG_SZ;
170 
171    /* set the value */
172     rc = RegSetValueExW(result, last, 0, type, (const BYTE*)val, (DWORD)n);
173     RegCloseKey(result);
174   }
175 
176   if (rc != ERROR_SUCCESS) {
177     char *s_utf8 = Swide_to_utf8(s);
178     ptr s_scheme = Sstring_utf8(s_utf8, -1);
179     free(s_utf8);
180     S_error2("put-registry!", "cannot set ~a (~a)", s_scheme,
181       rc == ERROR_FILE_NOT_FOUND ? Sstring("not found") : s_ErrorString(rc));
182   }
183 }
184 
185 
s_RemoveRegistry(wchar_t * s)186 static void s_RemoveRegistry(wchar_t *s) {
187   HKEY key, result;
188   wchar_t *subkey, *last;
189   DWORD rc;
190 
191   SplitRegistryKey("remove-registry!", s, &key, &subkey, &last);
192 
193  /* open the key */
194   if (last == subkey) {
195     rc = RegOpenKeyExW(key, L"", 0, KEY_ALL_ACCESS, &result);
196   } else {
197     *last = '\0'; /* Truncate subkey at backslash */
198     rc = RegOpenKeyExW(key, subkey, 0, KEY_ALL_ACCESS, &result);
199     *last++ = '\\'; /* Restore backslash */
200   }
201   if (rc == ERROR_SUCCESS) {
202    /* delete the value */
203     rc = RegDeleteValueW(result, last);
204     if (rc == ERROR_FILE_NOT_FOUND)
205      /* value by given name not found; try deleting as key */
206       rc = RegDeleteKeyW(result, last);
207     RegCloseKey(result);
208   }
209 
210   if (rc != ERROR_SUCCESS) {
211     char *s_utf8 = Swide_to_utf8(s);
212     ptr s_scheme = Sstring_utf8(s_utf8, -1);
213     free(s_utf8);
214     S_error2("remove-registry!", "cannot remove ~a (~a)", s_scheme,
215       rc == ERROR_FILE_NOT_FOUND ? Sstring("not found") :
216       rc == ERROR_ACCESS_DENIED ? Sstring("insufficient permission or subkeys exist") :
217       s_ErrorString(rc));
218   }
219 }
220 
s_CreateInstance(CLSID * pCLSID,IID * iid)221 static IUnknown *s_CreateInstance(CLSID *pCLSID, IID *iid) {
222   IUnknown *pIface;
223   HRESULT hr;
224 
225   hr = CoCreateInstance(pCLSID,
226                         NULL,
227                         CLSCTX_INPROC_SERVER,
228                         iid,
229                         (void **)&pIface);
230   if (SUCCEEDED(hr)) {
231      return (IUnknown *)pIface;
232   } else {
233      S_error1("", "unable to create instance: ~s", s_ErrorString(hr));
234      return (IUnknown *)0 /* not reached */;
235   }
236 }
237 
s_ErrorString(DWORD dwMessageId)238 static ptr s_ErrorString(DWORD dwMessageId) {
239     return s_ErrorStringImp(dwMessageId, NULL);
240 }
241 
s_ErrorStringImp(DWORD dwMessageId,const char * lpcDefault)242 static ptr s_ErrorStringImp(DWORD dwMessageId, const char *lpcDefault) {
243     wchar_t *lpMsgBuf;
244     DWORD len;
245     char *u8str;
246     ptr result;
247 
248     len = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
249                          NULL, dwMessageId, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&lpMsgBuf, 0, NULL);
250     /* If FormatMessage fails... */
251     if (len == 0) {
252         if (lpcDefault) {
253             /* ... use the default string if provided... */
254             return Sstring_utf8(lpcDefault, -1);
255         } else {
256             /* ...otherwise, use the error code in hexadecimal. */
257             char buf[(sizeof(dwMessageId) * 2) + 3];
258             int n = snprintf(buf, sizeof(buf), "0x%lx", dwMessageId);
259             if ((unsigned)n < sizeof(buf))
260                 return Sstring_utf8(buf, n);
261             else
262                 return Sstring("??");
263         }
264     }
265     /* Otherwise remove trailing newlines & returns and strip trailing period, if present. */
266     while (len > 0) {
267         wchar_t c = lpMsgBuf[len - 1];
268         if (c == L'\n' || c == '\r')
269             len--;
270         else if (c == L'.') {
271             len--;
272             break;
273         }
274         else break;
275     }
276     lpMsgBuf[len] = 0;
277     u8str = Swide_to_utf8(lpMsgBuf);
278     LocalFree(lpMsgBuf);
279     result = Sstring_utf8(u8str, -1);
280     free(u8str);
281     return result;
282 }
283 
S_LastErrorString(void)284 ptr S_LastErrorString(void) {
285   return s_ErrorString(GetLastError());
286 }
287 
288 #ifdef CHAFF
S_windows_open_exclusive(char * who,char * path,int flags)289 int S_windows_open_exclusive(char *who, char *path, int flags) {
290   HANDLE hfile;
291   int fd;
292   DWORD access = 0;
293   DWORD crdisp = 0;
294 
295  /* could implement this later with more difficulty */
296   if ((flags & (O_TRUNC|O_CREAT)) == (O_TRUNC|O_CREAT))
297     S_error("open_exclusive", "O_TRUNC|O_CREAT not supported");
298 
299   if (flags & O_RDWR) access |= GENERIC_READ|GENERIC_WRITE;
300   if (flags & O_RDONLY) access |= GENERIC_READ;
301   if (flags & O_WRONLY) access |= GENERIC_WRITE;
302 
303   if (flags & O_CREAT) crdisp = OPEN_ALWAYS;
304   if (flags & O_TRUNC) crdisp = TRUNCATE_EXISTING;
305 
306   hfile = CreateFile(path, access, 0, (SECURITY_ATTRIBUTES *)0,
307                      crdisp, FILE_ATTRIBUTE_NORMAL, (HANDLE)0);
308   if (hfile == INVALID_HANDLE_VALUE)
309     S_error1(who, "~a", s_ErrorString(GetLastError()));
310 
311   flags &= O_RDONLY|O_WRONLY|O_RDWR|O_APPEND;
312   fd = _open_osfhandle((long)hfile, flags);
313   if (fd == -1) S_error(who, "open_osfhandle failed");
314 
315   return fd;
316 }
317 #endif
318 
319 #include <winbase.h>
320 
321 /* primitive version of flock compatible with Windows 95/98/ME.  A better
322    version could be implemented for Windows NT/2000/XP using LockFileEx. */
S_windows_flock(int fd,int operation)323 int S_windows_flock(int fd, int operation) {
324   HANDLE hfile = (HANDLE)_get_osfhandle(fd);
325 
326   switch (operation) {
327     case LOCK_EX|LOCK_NB:
328       if (LockFile(hfile, 0, 0, 0x0fffffff, 0)) return 0;
329       errno = EWOULDBLOCK;
330       return -1;
331     case LOCK_EX:
332       while (LockFile(hfile, 0, 0, 0x0fffffff, 0) == 0) Sleep(10);
333       return 0;
334     case LOCK_SH:
335     case LOCK_SH|LOCK_NB:
336       S_error("flock", "shared locks unsupported");
337       return -1;
338     case LOCK_UN:
339     case LOCK_UN|LOCK_NB:
340       UnlockFile(hfile, 0, 0, 0x0fffffff, 0);
341       return 0;
342     default:
343       errno = EINVAL;
344       return -1;
345   }
346 }
347 
S_windows_chdir(const char * pathname)348 int S_windows_chdir(const char *pathname) {
349   wchar_t wpathname[PATH_MAX];
350   if (MultiByteToWideChar(CP_UTF8,0,pathname,-1,wpathname,PATH_MAX) == 0)
351     return _chdir(pathname);
352   else
353     return _wchdir(wpathname);
354 }
355 
S_windows_chmod(const char * pathname,int mode)356 int S_windows_chmod(const char *pathname, int mode) {
357   wchar_t wpathname[PATH_MAX];
358   if (MultiByteToWideChar(CP_UTF8,0,pathname,-1,wpathname,PATH_MAX) == 0)
359     return _chmod(pathname, mode);
360   else
361     return _wchmod(wpathname, mode);
362 }
363 
S_windows_mkdir(const char * pathname)364 int S_windows_mkdir(const char *pathname) {
365   wchar_t wpathname[PATH_MAX];
366   if (MultiByteToWideChar(CP_UTF8,0,pathname,-1,wpathname,PATH_MAX) == 0)
367     return _mkdir(pathname);
368   else
369     return _wmkdir(wpathname);
370 }
371 
S_windows_open(const char * pathname,int flags,int mode)372 int S_windows_open(const char *pathname, int flags, int mode) {
373   wchar_t wpathname[PATH_MAX];
374   if (MultiByteToWideChar(CP_UTF8,0,pathname,-1,wpathname,PATH_MAX) == 0)
375     return _open(pathname,flags, mode);
376   else
377     return _wopen(wpathname,flags,mode);
378 }
379 
S_windows_rename(const char * oldpathname,const char * newpathname)380 int S_windows_rename(const char *oldpathname, const char *newpathname) {
381   wchar_t woldpathname[PATH_MAX], wnewpathname[PATH_MAX];
382   if (MultiByteToWideChar(CP_UTF8,0,oldpathname,-1,woldpathname,PATH_MAX) == 0 ||
383         MultiByteToWideChar(CP_UTF8,0,newpathname,-1,wnewpathname,PATH_MAX) == 0)
384     return rename(oldpathname, newpathname);
385   else
386     return _wrename(woldpathname, wnewpathname);
387 }
388 
S_windows_rmdir(const char * pathname)389 int S_windows_rmdir(const char *pathname) {
390   wchar_t wpathname[PATH_MAX];
391   if (MultiByteToWideChar(CP_UTF8,0,pathname,-1,wpathname,PATH_MAX) == 0)
392     return _rmdir(pathname);
393   else {
394     int rc;
395     if (!(rc = _wrmdir(wpathname))) {
396       // Spin loop until Windows deletes the directory.
397       int n;
398       for (n = 1000; n > 0; n--) {
399         if (_wrmdir(wpathname) && (errno == ENOENT)) break;
400       }
401       return 0;
402     }
403     return rc;
404   }
405 }
406 
S_windows_stat64(const char * pathname,struct STATBUF * buffer)407 int S_windows_stat64(const char *pathname, struct STATBUF *buffer) {
408   wchar_t wpathname[PATH_MAX];
409   if (MultiByteToWideChar(CP_UTF8,0,pathname,-1,wpathname,PATH_MAX) == 0)
410     return _stat64(pathname, buffer);
411   else
412     return _wstat64(wpathname, buffer);
413 }
414 
S_windows_system(const char * command)415 int S_windows_system(const char *command) {
416   wchar_t wcommand[PATH_MAX];
417   if (MultiByteToWideChar(CP_UTF8,0,command,-1,wcommand,PATH_MAX) == 0)
418     return system(command);
419   else
420     return _wsystem(wcommand);
421 }
422 
S_windows_unlink(const char * pathname)423 int S_windows_unlink(const char *pathname) {
424   wchar_t wpathname[PATH_MAX];
425   if (MultiByteToWideChar(CP_UTF8,0,pathname,-1,wpathname,PATH_MAX) == 0)
426     return _unlink(pathname);
427   else {
428     int rc;
429     if (!(rc = _wunlink(wpathname))) {
430       // Spin loop until Windows deletes the file.
431       int n;
432       for (n = 1000; n > 0; n--) {
433         if (_wunlink(wpathname) && (errno == ENOENT)) break;
434       }
435       return 0;
436     }
437     return rc;
438   }
439 }
440 
S_windows_getcwd(char * buffer,UNUSED int maxlen)441 char *S_windows_getcwd(char *buffer, UNUSED int maxlen) {
442   wchar_t wbuffer[PATH_MAX];
443   if (_wgetcwd(wbuffer, PATH_MAX) == NULL) return NULL;
444   if (WideCharToMultiByte(CP_UTF8,0,wbuffer,-1,buffer,PATH_MAX,NULL,NULL) == 0) {
445     switch (GetLastError()) {
446       case ERROR_INSUFFICIENT_BUFFER:
447         errno = ERANGE;
448         break;
449       default:
450         errno = EINVAL;
451         break;
452     }
453     return NULL;
454   } else
455     return buffer;
456 }
457 
Swide_to_utf8(const wchar_t * arg)458 char *Swide_to_utf8(const wchar_t *arg) {
459   int len = WideCharToMultiByte(CP_UTF8, 0, arg, -1, NULL, 0, NULL, NULL);
460   if (0 == len) return NULL;
461   char* arg8 = (char*)malloc(len * sizeof(char));
462   if (0 == WideCharToMultiByte(CP_UTF8, 0, arg, -1, arg8, len, NULL, NULL)) {
463     free(arg8);
464     return NULL;
465   }
466   return arg8;
467 }
468 
Sutf8_to_wide(const char * arg)469 wchar_t *Sutf8_to_wide(const char *arg) {
470   int len = MultiByteToWideChar(CP_UTF8, 0, arg, -1, NULL, 0);
471   if (0 == len) return NULL;
472   wchar_t* argw = (wchar_t*)malloc(len * sizeof(wchar_t));
473   if (0 == MultiByteToWideChar(CP_UTF8, 0, arg, -1, argw, len)) {
474     free(argw);
475     return NULL;
476   }
477   return argw;
478 }
479 
Sgetenv(const char * name)480 char *Sgetenv(const char *name) {
481   wchar_t* wname;
482   DWORD n;
483   wchar_t buffer[256];
484   wname = Sutf8_to_wide(name);
485   if (NULL == wname) return NULL;
486   n = GetEnvironmentVariableW(wname, buffer, 256);
487   if (n == 0) {
488     free(wname);
489     return NULL;
490   } else if (n <= 256) {
491     free(wname);
492     return Swide_to_utf8(buffer);
493   } else {
494     wchar_t* value = (wchar_t*)malloc(n * sizeof(wchar_t));
495     if (0 == GetEnvironmentVariableW(wname, value, n)) {
496       free(wname);
497       free(value);
498       return NULL;
499     } else {
500       char* result = Swide_to_utf8(value);
501       free(wname);
502       free(value);
503       return result;
504     }
505   }
506 }
507