/*
* %CopyrightBegin%
*
* Copyright Ericsson AB 1997-2013. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* %CopyrightEnd%
*/
/*
* Purpose: Interface to the registry API.
*/
#include <windows.h>
#include "erl_driver.h"
#include "sys.h"
/*
* Commands recognised by this driver.
*/
#define CMD_GET_CURRENT 0
#define CMD_OPEN_KEY 1
#define CMD_CREATE_KEY 2
#define CMD_GET_ALL_SUBKEYS 3
#define CMD_GET_VALUE 4
#define CMD_GET_ALL_VALUES 5
#define CMD_SET_VALUE 6
#define CMD_DELETE_KEY 7
#define CMD_DELETE_VALUE 8
/*
* Microsoft-specific function to map a WIN32 error code to a Posix errno.
*/
extern void _dosmaperr(DWORD);
/*
* Information kept for a registry port (since there is no controlling
* Erlang process, all state must be kept here).
*/
typedef struct {
ErlDrvPort port; /* Port handle. */
REGSAM sam; /* Access for handles. */
HKEY hkey; /* Handle to open key. */
HKEY hkey_root; /* Root handle for current key. */
char* key; /* Name of key. */
DWORD key_size; /* Size of key. */
LPSTR name_buf; /* Buffer for names. */
DWORD name_buf_size; /* Size of name buffer. */
LPSTR value_buf; /* Buffer for values. */
DWORD value_buf_size; /* Size of value buffer. */
} RegPort;
/*
* Local functions.
*/
static void reply(RegPort* rp, LONG result);
static BOOL fix_value_result(RegPort* rp, LONG result, DWORD type,
LPSTR name, DWORD nameSize, LPSTR value,
DWORD valueSize);
static int key_reply(RegPort* rp, LPSTR name, DWORD nameSize);
static int value_reply(RegPort* rp, DWORD type, LPSTR name, DWORD nameSize,
LPSTR value, DWORD valueSize);
static int state_reply(RegPort* rp, HKEY root, LPSTR name, DWORD nameSize);
static int maperror(DWORD error);
/*
* Local variables.
*/
static int reg_init(void);
static ErlDrvData reg_start(ErlDrvPort, char*);
static void reg_stop(ErlDrvData);
static void reg_from_erlang(ErlDrvData, char*, ErlDrvSizeT);
struct erl_drv_entry registry_driver_entry = {
reg_init,
reg_start,
reg_stop,
reg_from_erlang,
NULL,
NULL,
"registry__drv__",
NULL,
NULL, /* handle */
NULL, /* control */
NULL, /* timeout */
NULL, /* outputv */
NULL, /* ready_async */
NULL, /* flush */
NULL, /* call */
NULL, /* event */
ERL_DRV_EXTENDED_MARKER,
ERL_DRV_EXTENDED_MAJOR_VERSION,
ERL_DRV_EXTENDED_MINOR_VERSION,
0,
NULL,
NULL,
NULL,
};
static int
reg_init(void)
{
DEBUGF(("reg_init()\n"));
return 0;
}
static ErlDrvData
reg_start(ErlDrvPort port, char* buf)
{
RegPort* rp;
char* s;
REGSAM sam = KEY_READ;
if ((s = strchr(buf, ' ')) != NULL) {
while (isspace(*s))
s++;
while (*s != '\0') {
if (*s == 'r') {
sam |= KEY_READ;
} else if (*s == 'w') {
sam |= KEY_WRITE;
}
s++;
}
}
rp = driver_alloc(sizeof(RegPort));
if (rp == NULL) {
return ERL_DRV_ERROR_GENERAL;
}
rp->port = port;
rp->hkey = rp->hkey_root = HKEY_CLASSES_ROOT;
rp->sam = sam;
rp->key = driver_alloc(1);
rp->key_size = 0;
rp->name_buf_size = 64;
rp->name_buf = driver_alloc(rp->name_buf_size);
rp->value_buf_size = 64;
rp->value_buf = driver_alloc(rp->value_buf_size);
return (ErlDrvData) rp;
}
static void
reg_stop(ErlDrvData clientData)
{
RegPort* rp = (RegPort *) clientData;
(void) RegCloseKey(rp->hkey);
driver_free(rp->name_buf);
driver_free(rp->value_buf);
driver_free(rp->key);
driver_free(rp);
/* return 1; */
}
static void
reg_from_erlang(ErlDrvData clientData, char* buf, ErlDrvSizeT count)
{
RegPort* rp = (RegPort *) clientData;
int cmd;
HKEY hkey;
LONG result;
DWORD nameSize;
DWORD type; /* Type of data in buffer. */
DWORD valueSize; /* Size of value buffer. */
cmd = buf[0];
buf++, count--;
switch (cmd) {
case CMD_GET_CURRENT:
state_reply(rp, rp->hkey_root, rp->key, rp->key_size);
break;
case CMD_OPEN_KEY:
{
char* key;
HKEY newKey;
/*
* [HKEY(DWORD), KeyString(string)]
*/
hkey = (HKEY) get_int32(buf+0);
rp->hkey_root = hkey;
key = buf+4;
result = RegOpenKeyEx(hkey, key, 0, rp->sam, &newKey);
if (result == ERROR_SUCCESS) {
RegCloseKey(rp->hkey);
rp->hkey = newKey;
driver_free(rp->key);
rp->key_size = strlen(key);
rp->key = driver_alloc(rp->key_size+1);
strcpy(rp->key, key);
}
reply(rp, result);
return;
}
break;
case CMD_CREATE_KEY:
{
char* key;
HKEY newKey;
DWORD disposition;
hkey = (HKEY) get_int32(buf+0);
rp->hkey_root = hkey;
key = buf+4;
result = RegCreateKeyEx(hkey, key, 0, "", 0, rp->sam, NULL,
&newKey, &disposition);
if (result == ERROR_SUCCESS) {
RegCloseKey(rp->hkey);
rp->hkey = newKey;
driver_free(rp->key);
rp->key_size = strlen(key);
rp->key = driver_alloc(rp->key_size+1);
strcpy(rp->key, key);
}
reply(rp, result);
return;
}
break;
case CMD_GET_ALL_SUBKEYS:
{
int i;
i = 0;
for (;;) {
nameSize = rp->name_buf_size;
result = RegEnumKeyEx(rp->hkey, i, rp->name_buf, &nameSize,
NULL, NULL, NULL, NULL);
if (result == ERROR_MORE_DATA) {
rp->name_buf_size *= 2;
rp->name_buf = driver_realloc(rp->name_buf,
rp->name_buf_size);
continue;
} else if (result == ERROR_NO_MORE_ITEMS) {
reply(rp, ERROR_SUCCESS);
return;
} else if (result != ERROR_SUCCESS) {
reply(rp, result);
return;
}
key_reply(rp, rp->name_buf, nameSize);
i++;
}
}
break;
case CMD_GET_VALUE:
do {
valueSize = rp->value_buf_size;
result = RegQueryValueEx(rp->hkey, buf, NULL, &type,
rp->value_buf, &valueSize);
} while (!fix_value_result(rp, result, type, buf, strlen(buf),
rp->value_buf, valueSize));
break;
case CMD_GET_ALL_VALUES:
{
int i;
i = 0;
for (;;) {
nameSize = rp->name_buf_size;
valueSize = rp->value_buf_size;
result = RegEnumValue(rp->hkey, i, rp->name_buf, &nameSize,
NULL, &type, rp->value_buf, &valueSize);
if (result == ERROR_NO_MORE_ITEMS) {
reply(rp, ERROR_SUCCESS);
return;
}
if (fix_value_result(rp, result, type, rp->name_buf, nameSize,
rp->value_buf, valueSize)) {
i++;
}
}
}
break;
case CMD_SET_VALUE:
{
LPSTR name;
DWORD dword;
/*
* [Type(DWORD), Name(string), Value(bytes)]
*/
type = get_int32(buf);
buf += 4;
count -= 4;
name = buf;
nameSize = strlen(buf) + 1;
buf += nameSize;
count -= nameSize;
if (type == REG_DWORD) {
/*
* Must pass a pointer to a DWORD in host byte order.
*/
dword = get_int32(buf);
buf = (char *) &dword;
ASSERT(count == 4);
}
result = RegSetValueEx(rp->hkey, name, 0, type, buf, (DWORD)count);
reply(rp, result);
}
break;
case CMD_DELETE_KEY:
result = RegDeleteKey(rp->hkey, NULL);
reply(rp, result);
break;
case CMD_DELETE_VALUE:
result = RegDeleteValue(rp->hkey, buf);
reply(rp, result);
break;
}
/* return 1; */
}
static BOOL
fix_value_result(RegPort* rp, LONG result, DWORD type,
LPSTR name, DWORD nameSize, LPSTR value, DWORD valueSize)
{
if (result == ERROR_MORE_DATA) {
DWORD max_name1;
DWORD max_name2;
DWORD max_value;
int ok;
ok = RegQueryInfoKey(rp->hkey, NULL, NULL, NULL,
NULL, &max_name1, NULL, NULL, &max_name2,
&max_value, NULL, NULL);
#ifdef DEBUG
if (ok != ERROR_SUCCESS) {
char buff[256];
erts_snprintf(buff, sizeof(buff), "Failure in registry_drv line %d, error = %d",
__LINE__, GetLastError());
MessageBox(NULL, buff, "Internal error", MB_OK);
ASSERT(ok == ERROR_SUCCESS);
}
#endif
rp->name_buf_size = (max_name1 > max_name2 ? max_name1 : max_name2)
+ 1;
rp->value_buf_size = max_value + 1;
rp->name_buf = driver_realloc(rp->name_buf, rp->name_buf_size);
rp->value_buf = driver_realloc(rp->value_buf, rp->value_buf_size);
return FALSE;
} else if (result != ERROR_SUCCESS) {
reply(rp, result);
return TRUE;
}
/*
* Do some data conversion which is easier to do here
* than in Erlang.
*/
switch (type) {
case REG_SZ:
case REG_EXPAND_SZ:
valueSize--; /* No reason to send the '\0' to Erlang. */
break;
case REG_DWORD_LITTLE_ENDIAN:
case REG_DWORD_BIG_ENDIAN:
/*
* The value is a DWORD stored in host byte order.
* We must retrieve it and store it in network byte order.
*/
{
DWORD dword = * ((DWORD *) value);
put_int32(dword, value);
type = REG_DWORD; /* Simplify life for Erlang code. */
break;
}
}
return value_reply(rp, type, name, nameSize, value, valueSize);
}
/*
* Sends one of the following replies back to Erlang,
* depending on result:
*
* [$e|Posix error(string)] Error
* [$o] Ok
*/
static void
reply(RegPort* rp, LONG result)
{
char sbuf[256];
if (result == ERROR_SUCCESS) {
sbuf[0] = 'o';
driver_output(rp->port, sbuf, 1);
} else {
char* s;
char* t;
int err;
sbuf[0] = 'e';
err = maperror(result);
for (s = erl_errno_id(err), t = sbuf+1; *s; s++, t++) {
*t = tolower(*s);
}
driver_output(rp->port, sbuf, t-sbuf);
}
/* return 1; */
}
/*
* Sends a key to Erlang:
*
* [$k, Keyname(string)]
*/
static int
key_reply(RegPort* rp, /* Pointer to port structure. */
LPSTR name, /* Pointer to name. */
DWORD nameSize) /* Length of name. */
{
char sbuf[512];
char* s = sbuf;
int needed = 1+nameSize;
if (sizeof sbuf < needed) {
s = driver_alloc(needed);
}
s[0] = 'k';
memcpy(s+1, name, nameSize);
driver_output(rp->port, s, needed);
if (s != sbuf) {
driver_free(s);
}
return 1;
}
/*
* Sends a value to Erlang:
*
* [$v, Type(DWORD), Valuename(string), 0, Value(bytes)]
*/
static int
value_reply(RegPort* rp, /* Pointer to port structure. */
DWORD type, /* Type of value */
LPSTR name, /* Pointer to name. */
DWORD nameSize, /* Length of name. */
LPSTR value, /* Pointer to value. */
DWORD valueSize) /* Size of value. */
{
char sbuf[512];
char* s = sbuf;
int needed = 1+4+nameSize+1+valueSize;
int i;
if (sizeof sbuf < needed) {
s = driver_alloc(needed);
}
s[0] = 'v';
i = 1;
put_int32(type, s+i);
i += 4;
memcpy(s+i, name, nameSize);
i += nameSize;
s[i++] = '\0';
memcpy(s+i, value, valueSize);
ASSERT(i+valueSize == needed);
driver_output(rp->port, s, needed);
if (s != sbuf) {
driver_free(s);
}
return 1;
}
/*
* Sends a key to Erlang:
*
* [$s, HKEY(DWORD), Keyname(string)] State
*/
static int
state_reply(RegPort* rp, /* Pointer to port structure. */
HKEY root, /* Handle to root key for this key. */
LPSTR name, /* Pointer to name. */
DWORD nameSize) /* Length of name. */
{
char sbuf[512];
char* s = sbuf;
int needed = 1+4+nameSize;
int i;
if (sizeof sbuf < needed) {
s = driver_alloc(needed);
}
s[0] = 's';
i = 1;
put_int32((DWORD) root, s+i);
i += 4;
memcpy(s+i, name, nameSize);
ASSERT(i+nameSize == needed);
driver_output(rp->port, s, needed);
if (s != sbuf) {
driver_free(s);
}
return 1;
}
static int
maperror(DWORD error)
{
DEBUGF(("Mapping %d\n", error));
switch (error) {
case ERROR_BADDB:
case ERROR_BADKEY:
case ERROR_CANTOPEN:
case ERROR_CANTWRITE:
case ERROR_REGISTRY_RECOVERED:
case ERROR_REGISTRY_CORRUPT:
case ERROR_REGISTRY_IO_FAILED:
case ERROR_NOT_REGISTRY_FILE:
return EIO;
case ERROR_KEY_DELETED:
return EINVAL;
default:
_dosmaperr(error);
return errno;
}
}