xref: /reactos/base/applications/cmdutils/reg/reg.c (revision 02e84521)
1 /*
2  * Copyright 2008 Andrew Riedi
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17  */
18 
19 #include <windows.h>
20 #include <errno.h>
21 #include <stdlib.h>
22 #include <wine/unicode.h>
23 #include <wine/debug.h>
24 #include <wine/heap.h>
25 #include "reg.h"
26 
27 WINE_DEFAULT_DEBUG_CHANNEL(reg);
28 
29 static const WCHAR short_hklm[] = {'H','K','L','M',0};
30 static const WCHAR short_hkcu[] = {'H','K','C','U',0};
31 static const WCHAR short_hkcr[] = {'H','K','C','R',0};
32 static const WCHAR short_hku[] = {'H','K','U',0};
33 static const WCHAR short_hkcc[] = {'H','K','C','C',0};
34 static const WCHAR long_hklm[] = {'H','K','E','Y','_','L','O','C','A','L','_','M','A','C','H','I','N','E',0};
35 static const WCHAR long_hkcu[] = {'H','K','E','Y','_','C','U','R','R','E','N','T','_','U','S','E','R',0};
36 static const WCHAR long_hkcr[] = {'H','K','E','Y','_','C','L','A','S','S','E','S','_','R','O','O','T',0};
37 static const WCHAR long_hku[] = {'H','K','E','Y','_','U','S','E','R','S',0};
38 static const WCHAR long_hkcc[] = {'H','K','E','Y','_','C','U','R','R','E','N','T','_','C','O','N','F','I','G',0};
39 
40 static const struct
41 {
42     HKEY key;
43     const WCHAR *short_name;
44     const WCHAR *long_name;
45 }
46 root_rels[] =
47 {
48     {HKEY_LOCAL_MACHINE, short_hklm, long_hklm},
49     {HKEY_CURRENT_USER, short_hkcu, long_hkcu},
50     {HKEY_CLASSES_ROOT, short_hkcr, long_hkcr},
51     {HKEY_USERS, short_hku, long_hku},
52     {HKEY_CURRENT_CONFIG, short_hkcc, long_hkcc},
53 };
54 
55 static const WCHAR type_none[] = {'R','E','G','_','N','O','N','E',0};
56 static const WCHAR type_sz[] = {'R','E','G','_','S','Z',0};
57 static const WCHAR type_expand_sz[] = {'R','E','G','_','E','X','P','A','N','D','_','S','Z',0};
58 static const WCHAR type_binary[] = {'R','E','G','_','B','I','N','A','R','Y',0};
59 static const WCHAR type_dword[] = {'R','E','G','_','D','W','O','R','D',0};
60 static const WCHAR type_dword_le[] = {'R','E','G','_','D','W','O','R','D','_','L','I','T','T','L','E','_','E','N','D','I','A','N',0};
61 static const WCHAR type_dword_be[] = {'R','E','G','_','D','W','O','R','D','_','B','I','G','_','E','N','D','I','A','N',0};
62 static const WCHAR type_multi_sz[] = {'R','E','G','_','M','U','L','T','I','_','S','Z',0};
63 
64 static const struct
65 {
66     DWORD type;
67     const WCHAR *name;
68 }
69 type_rels[] =
70 {
71     {REG_NONE, type_none},
72     {REG_SZ, type_sz},
73     {REG_EXPAND_SZ, type_expand_sz},
74     {REG_BINARY, type_binary},
75     {REG_DWORD, type_dword},
76     {REG_DWORD_LITTLE_ENDIAN, type_dword_le},
77     {REG_DWORD_BIG_ENDIAN, type_dword_be},
78     {REG_MULTI_SZ, type_multi_sz},
79 };
80 
81 static const WCHAR newlineW[] = {'\n',0};
82 
83 void *heap_xalloc(size_t size)
84 {
85     void *buf = heap_alloc(size);
86     if (!buf)
87     {
88         ERR("Out of memory!\n");
89         exit(1);
90     }
91     return buf;
92 }
93 
94 void *heap_xrealloc(void *buf, size_t size)
95 {
96     void *new_buf = heap_realloc(buf, size);
97 
98     if (!new_buf)
99     {
100         ERR("Out of memory!\n");
101         exit(1);
102     }
103 
104     return new_buf;
105 }
106 
107 void output_writeconsole(const WCHAR *str, DWORD wlen)
108 {
109     DWORD count, ret;
110 
111     ret = WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), str, wlen, &count, NULL);
112     if (!ret)
113     {
114         DWORD len;
115         char  *msgA;
116 
117         /* On Windows WriteConsoleW() fails if the output is redirected. So fall
118          * back to WriteFile(), assuming the console encoding is still the right
119          * one in that case.
120          */
121         len = WideCharToMultiByte(GetConsoleOutputCP(), 0, str, wlen, NULL, 0, NULL, NULL);
122         msgA = heap_xalloc(len);
123 
124         WideCharToMultiByte(GetConsoleOutputCP(), 0, str, wlen, msgA, len, NULL, NULL);
125         WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), msgA, len, &count, FALSE);
126         heap_free(msgA);
127     }
128 }
129 
130 static void output_formatstring(const WCHAR *fmt, __ms_va_list va_args)
131 {
132     WCHAR *str;
133     DWORD len;
134 
135     SetLastError(NO_ERROR);
136     len = FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER,
137                          fmt, 0, 0, (WCHAR *)&str, 0, &va_args);
138     if (len == 0 && GetLastError() != NO_ERROR)
139     {
140         WINE_FIXME("Could not format string: le=%u, fmt=%s\n", GetLastError(), wine_dbgstr_w(fmt));
141         return;
142     }
143     output_writeconsole(str, len);
144     LocalFree(str);
145 }
146 
147 void WINAPIV output_message(unsigned int id, ...)
148 {
149     WCHAR fmt[1024];
150     __ms_va_list va_args;
151 
152     if (!LoadStringW(GetModuleHandleW(NULL), id, fmt, ARRAY_SIZE(fmt)))
153     {
154         WINE_FIXME("LoadString failed with %d\n", GetLastError());
155         return;
156     }
157     __ms_va_start(va_args, id);
158     output_formatstring(fmt, va_args);
159     __ms_va_end(va_args);
160 }
161 
162 static void WINAPIV output_string(const WCHAR *fmt, ...)
163 {
164     __ms_va_list va_args;
165 
166     __ms_va_start(va_args, fmt);
167     output_formatstring(fmt, va_args);
168     __ms_va_end(va_args);
169 }
170 
171 /* ask_confirm() adapted from programs/cmd/builtins.c */
172 BOOL ask_confirm(unsigned int msgid, WCHAR *reg_info)
173 {
174     HMODULE hmod;
175     WCHAR Ybuffer[4];
176     WCHAR Nbuffer[4];
177     WCHAR defval[32];
178     WCHAR answer[MAX_PATH];
179     WCHAR *str;
180     DWORD count;
181 
182     hmod = GetModuleHandleW(NULL);
183     LoadStringW(hmod, STRING_YES, Ybuffer, ARRAY_SIZE(Ybuffer));
184     LoadStringW(hmod, STRING_NO,  Nbuffer, ARRAY_SIZE(Nbuffer));
185     LoadStringW(hmod, STRING_DEFAULT_VALUE, defval, ARRAY_SIZE(defval));
186 
187     str = (reg_info && *reg_info) ? reg_info : defval;
188 
189     while (1)
190     {
191         output_message(msgid, str);
192         output_message(STRING_YESNO);
193         ReadConsoleW(GetStdHandle(STD_INPUT_HANDLE), answer, ARRAY_SIZE(answer), &count, NULL);
194         answer[0] = toupperW(answer[0]);
195         if (answer[0] == Ybuffer[0])
196             return TRUE;
197         if (answer[0] == Nbuffer[0])
198             return FALSE;
199     }
200 }
201 
202 static inline BOOL path_rootname_cmp(const WCHAR *input_path, const WCHAR *rootkey_name)
203 {
204     DWORD length = strlenW(rootkey_name);
205 
206     return (!strncmpiW(input_path, rootkey_name, length) &&
207             (input_path[length] == 0 || input_path[length] == '\\'));
208 }
209 
210 HKEY path_get_rootkey(const WCHAR *path)
211 {
212     DWORD i;
213 
214     for (i = 0; i < ARRAY_SIZE(root_rels); i++)
215     {
216         if (path_rootname_cmp(path, root_rels[i].short_name) ||
217             path_rootname_cmp(path, root_rels[i].long_name))
218             return root_rels[i].key;
219     }
220 
221     return NULL;
222 }
223 
224 static DWORD wchar_get_type(const WCHAR *type_name)
225 {
226     DWORD i;
227 
228     if (!type_name)
229         return REG_SZ;
230 
231     for (i = 0; i < ARRAY_SIZE(type_rels); i++)
232     {
233         if (!strcmpiW(type_rels[i].name, type_name))
234             return type_rels[i].type;
235     }
236 
237     return ~0u;
238 }
239 
240 /* hexchar_to_byte from programs/regedit/hexedit.c */
241 static inline BYTE hexchar_to_byte(WCHAR ch)
242 {
243     if (ch >= '0' && ch <= '9')
244         return ch - '0';
245     else if (ch >= 'a' && ch <= 'f')
246         return ch - 'a' + 10;
247     else if (ch >= 'A' && ch <= 'F')
248         return ch - 'A' + 10;
249     else
250         return -1;
251 }
252 
253 static LPBYTE get_regdata(const WCHAR *data, DWORD reg_type, WCHAR separator, DWORD *reg_count)
254 {
255     static const WCHAR empty;
256     LPBYTE out_data = NULL;
257     *reg_count = 0;
258 
259     if (!data) data = &empty;
260 
261     switch (reg_type)
262     {
263         case REG_NONE:
264         case REG_SZ:
265         case REG_EXPAND_SZ:
266         {
267             *reg_count = (lstrlenW(data) + 1) * sizeof(WCHAR);
268             out_data = heap_xalloc(*reg_count);
269             lstrcpyW((LPWSTR)out_data,data);
270             break;
271         }
272         case REG_DWORD:
273      /* case REG_DWORD_LITTLE_ENDIAN: */
274         case REG_DWORD_BIG_ENDIAN: /* Yes, this is correct! */
275         {
276             LPWSTR rest;
277             unsigned long val;
278             val = wcstoul(data, &rest, (tolowerW(data[1]) == 'x') ? 16 : 10);
279             if (*rest || data[0] == '-' || (val == ~0u && errno == ERANGE)) {
280                 output_message(STRING_MISSING_INTEGER);
281                 break;
282             }
283             *reg_count = sizeof(DWORD);
284             out_data = heap_xalloc(*reg_count);
285             ((LPDWORD)out_data)[0] = val;
286             break;
287         }
288         case REG_BINARY:
289         {
290             BYTE hex0, hex1;
291             int i = 0, destByteIndex = 0, datalen = lstrlenW(data);
292             *reg_count = ((datalen + datalen % 2) / 2) * sizeof(BYTE);
293             out_data = heap_xalloc(*reg_count);
294             if(datalen % 2)
295             {
296                 hex1 = hexchar_to_byte(data[i++]);
297                 if(hex1 == 0xFF)
298                     goto no_hex_data;
299                 out_data[destByteIndex++] = hex1;
300             }
301             for(;i + 1 < datalen;i += 2)
302             {
303                 hex0 = hexchar_to_byte(data[i]);
304                 hex1 = hexchar_to_byte(data[i + 1]);
305                 if(hex0 == 0xFF || hex1 == 0xFF)
306                     goto no_hex_data;
307                 out_data[destByteIndex++] = (hex0 << 4) | hex1;
308             }
309             break;
310             no_hex_data:
311             /* cleanup, print error */
312             heap_free(out_data);
313             output_message(STRING_MISSING_HEXDATA);
314             out_data = NULL;
315             break;
316         }
317         case REG_MULTI_SZ:
318         {
319             int i, destindex, len = strlenW(data);
320             WCHAR *buffer = heap_xalloc((len + 2) * sizeof(WCHAR));
321 
322             for (i = 0, destindex = 0; i < len; i++, destindex++)
323             {
324                 if (!separator && data[i] == '\\' && data[i + 1] == '0')
325                 {
326                     buffer[destindex] = 0;
327                     i++;
328                 }
329                 else if (data[i] == separator)
330                     buffer[destindex] = 0;
331                 else
332                     buffer[destindex] = data[i];
333 
334                 if (destindex && !buffer[destindex - 1] && (!buffer[destindex] || destindex == 1))
335                 {
336                     heap_free(buffer);
337                     output_message(STRING_INVALID_STRING);
338                     return NULL;
339                 }
340             }
341             buffer[destindex] = 0;
342             if (destindex && buffer[destindex - 1])
343                 buffer[++destindex] = 0;
344             *reg_count = (destindex + 1) * sizeof(WCHAR);
345             return (BYTE *)buffer;
346         }
347         default:
348             output_message(STRING_UNHANDLED_TYPE, reg_type, data);
349     }
350 
351     return out_data;
352 }
353 
354 static BOOL sane_path(const WCHAR *key)
355 {
356     unsigned int i = strlenW(key);
357 
358     if (i < 3 || (key[i - 1] == '\\' && key[i - 2] == '\\'))
359     {
360         output_message(STRING_INVALID_KEY);
361         return FALSE;
362     }
363 
364     if (key[0] == '\\' && key[1] == '\\' && key[2] != '\\')
365     {
366         output_message(STRING_NO_REMOTE);
367         return FALSE;
368     }
369 
370     return TRUE;
371 }
372 
373 static int reg_add(HKEY root, WCHAR *path, WCHAR *value_name, BOOL value_empty,
374                    WCHAR *type, WCHAR separator, WCHAR *data, BOOL force)
375 {
376     HKEY key;
377 
378     if (RegCreateKeyW(root, path, &key) != ERROR_SUCCESS)
379     {
380         output_message(STRING_INVALID_KEY);
381         return 1;
382     }
383 
384     if (value_name || value_empty || data)
385     {
386         DWORD reg_type;
387         DWORD reg_count = 0;
388         BYTE* reg_data = NULL;
389 
390         if (!force)
391         {
392             if (RegQueryValueExW(key, value_name, NULL, NULL, NULL, NULL) == ERROR_SUCCESS)
393             {
394                 if (!ask_confirm(STRING_OVERWRITE_VALUE, value_name))
395                 {
396                     RegCloseKey(key);
397                     output_message(STRING_CANCELLED);
398                     return 0;
399                 }
400             }
401         }
402 
403         reg_type = wchar_get_type(type);
404         if (reg_type == ~0u)
405         {
406             RegCloseKey(key);
407             output_message(STRING_UNSUPPORTED_TYPE, type);
408             return 1;
409         }
410         if ((reg_type == REG_DWORD || reg_type == REG_DWORD_BIG_ENDIAN) && !data)
411         {
412              RegCloseKey(key);
413              output_message(STRING_INVALID_CMDLINE);
414              return 1;
415         }
416 
417         if (!(reg_data = get_regdata(data, reg_type, separator, &reg_count)))
418         {
419             RegCloseKey(key);
420             return 1;
421         }
422 
423         RegSetValueExW(key, value_name, 0, reg_type, reg_data, reg_count);
424         heap_free(reg_data);
425     }
426 
427     RegCloseKey(key);
428     output_message(STRING_SUCCESS);
429 
430     return 0;
431 }
432 
433 static int reg_delete(HKEY root, WCHAR *path, WCHAR *key_name, WCHAR *value_name,
434                       BOOL value_empty, BOOL value_all, BOOL force)
435 {
436     HKEY key;
437 
438     if (!force)
439     {
440         BOOL ret;
441 
442         if (value_name || value_empty)
443             ret = ask_confirm(STRING_DELETE_VALUE, value_name);
444         else if (value_all)
445             ret = ask_confirm(STRING_DELETE_VALUEALL, key_name);
446         else
447             ret = ask_confirm(STRING_DELETE_SUBKEY, key_name);
448 
449         if (!ret)
450         {
451             output_message(STRING_CANCELLED);
452             return 0;
453         }
454     }
455 
456     /* Delete subtree only if no /v* option is given */
457     if (!value_name && !value_empty && !value_all)
458     {
459         if (RegDeleteTreeW(root, path) != ERROR_SUCCESS)
460         {
461             output_message(STRING_CANNOT_FIND);
462             return 1;
463         }
464         output_message(STRING_SUCCESS);
465         return 0;
466     }
467 
468     if (RegOpenKeyW(root, path, &key) != ERROR_SUCCESS)
469     {
470         output_message(STRING_CANNOT_FIND);
471         return 1;
472     }
473 
474     if (value_all)
475     {
476         DWORD max_value_len = 256, value_len;
477         WCHAR *value_name;
478         LONG rc;
479 
480         value_name = heap_xalloc(max_value_len * sizeof(WCHAR));
481 
482         while (1)
483         {
484             value_len = max_value_len;
485             rc = RegEnumValueW(key, 0, value_name, &value_len, NULL, NULL, NULL, NULL);
486             if (rc == ERROR_SUCCESS)
487             {
488                 rc = RegDeleteValueW(key, value_name);
489                 if (rc != ERROR_SUCCESS)
490                 {
491                     heap_free(value_name);
492                     RegCloseKey(key);
493                     output_message(STRING_VALUEALL_FAILED, key_name);
494                     return 1;
495                 }
496             }
497             else if (rc == ERROR_MORE_DATA)
498             {
499                 max_value_len *= 2;
500                 value_name = heap_xrealloc(value_name, max_value_len * sizeof(WCHAR));
501             }
502             else break;
503         }
504         heap_free(value_name);
505     }
506     else if (value_name || value_empty)
507     {
508         if (RegDeleteValueW(key, value_empty ? NULL : value_name) != ERROR_SUCCESS)
509         {
510             RegCloseKey(key);
511             output_message(STRING_CANNOT_FIND);
512             return 1;
513         }
514     }
515 
516     RegCloseKey(key);
517     output_message(STRING_SUCCESS);
518     return 0;
519 }
520 
521 static WCHAR *reg_data_to_wchar(DWORD type, const BYTE *src, DWORD size_bytes)
522 {
523     WCHAR *buffer = NULL;
524     int i;
525 
526     switch (type)
527     {
528         case REG_SZ:
529         case REG_EXPAND_SZ:
530             buffer = heap_xalloc(size_bytes);
531             strcpyW(buffer, (WCHAR *)src);
532             break;
533         case REG_NONE:
534         case REG_BINARY:
535         {
536             WCHAR *ptr;
537             static const WCHAR fmt[] = {'%','0','2','X',0};
538 
539             buffer = heap_xalloc((size_bytes * 2 + 1) * sizeof(WCHAR));
540             ptr = buffer;
541             for (i = 0; i < size_bytes; i++)
542                 ptr += sprintfW(ptr, fmt, src[i]);
543             break;
544         }
545         case REG_DWORD:
546      /* case REG_DWORD_LITTLE_ENDIAN: */
547         case REG_DWORD_BIG_ENDIAN:
548         {
549             const int zero_x_dword = 10;
550             static const WCHAR fmt[] = {'0','x','%','x',0};
551 
552             buffer = heap_xalloc((zero_x_dword + 1) * sizeof(WCHAR));
553             sprintfW(buffer, fmt, *(DWORD *)src);
554             break;
555         }
556         case REG_MULTI_SZ:
557         {
558             const int two_wchars = 2 * sizeof(WCHAR);
559             DWORD tmp_size;
560             const WCHAR *tmp = (const WCHAR *)src;
561             int len, destindex;
562 
563             if (size_bytes <= two_wchars)
564             {
565                 buffer = heap_xalloc(sizeof(WCHAR));
566                 *buffer = 0;
567                 return buffer;
568             }
569 
570             tmp_size = size_bytes - two_wchars; /* exclude both null terminators */
571             buffer = heap_xalloc(tmp_size * 2 + sizeof(WCHAR));
572             len = tmp_size / sizeof(WCHAR);
573 
574             for (i = 0, destindex = 0; i < len; i++, destindex++)
575             {
576                 if (tmp[i])
577                     buffer[destindex] = tmp[i];
578                 else
579                 {
580                     buffer[destindex++] = '\\';
581                     buffer[destindex] = '0';
582                 }
583             }
584             buffer[destindex] = 0;
585             break;
586         }
587     }
588     return buffer;
589 }
590 
591 static const WCHAR *reg_type_to_wchar(DWORD type)
592 {
593     int i, array_size = ARRAY_SIZE(type_rels);
594 
595     for (i = 0; i < array_size; i++)
596     {
597         if (type == type_rels[i].type)
598             return type_rels[i].name;
599     }
600     return NULL;
601 }
602 
603 static void output_value(const WCHAR *value_name, DWORD type, BYTE *data, DWORD data_size)
604 {
605     static const WCHAR fmt[] = {' ',' ',' ',' ','%','1',0};
606     WCHAR defval[32];
607     WCHAR *reg_data;
608 
609     if (value_name && value_name[0])
610         output_string(fmt, value_name);
611     else
612     {
613         LoadStringW(GetModuleHandleW(NULL), STRING_DEFAULT_VALUE, defval, ARRAY_SIZE(defval));
614         output_string(fmt, defval);
615     }
616     output_string(fmt, reg_type_to_wchar(type));
617 
618     if (data)
619     {
620         reg_data = reg_data_to_wchar(type, data, data_size);
621         output_string(fmt, reg_data);
622         heap_free(reg_data);
623     }
624     else
625     {
626         LoadStringW(GetModuleHandleW(NULL), STRING_VALUE_NOT_SET, defval, ARRAY_SIZE(defval));
627         output_string(fmt, defval);
628     }
629     output_string(newlineW);
630 }
631 
632 WCHAR *build_subkey_path(WCHAR *path, DWORD path_len, WCHAR *subkey_name, DWORD subkey_len)
633 {
634     WCHAR *subkey_path;
635     static const WCHAR fmt[] = {'%','s','\\','%','s',0};
636 
637     subkey_path = heap_xalloc((path_len + subkey_len + 2) * sizeof(WCHAR));
638     sprintfW(subkey_path, fmt, path, subkey_name);
639 
640     return subkey_path;
641 }
642 
643 static unsigned int num_values_found = 0;
644 
645 static int query_value(HKEY key, WCHAR *value_name, WCHAR *path, BOOL recurse)
646 {
647     LONG rc;
648     DWORD max_data_bytes = 2048, data_size;
649     DWORD subkey_len;
650     DWORD type, path_len, i;
651     BYTE *data;
652     WCHAR fmt[] = {'%','1','\n',0};
653     WCHAR *subkey_name, *subkey_path;
654     HKEY subkey;
655 
656     data = heap_xalloc(max_data_bytes);
657 
658     for (;;)
659     {
660         data_size = max_data_bytes;
661         rc = RegQueryValueExW(key, value_name, NULL, &type, data, &data_size);
662         if (rc == ERROR_MORE_DATA)
663         {
664             max_data_bytes = data_size;
665             data = heap_xrealloc(data, max_data_bytes);
666         }
667         else break;
668     }
669 
670     if (rc == ERROR_SUCCESS)
671     {
672         output_string(fmt, path);
673         output_value(value_name, type, data, data_size);
674         output_string(newlineW);
675         num_values_found++;
676     }
677 
678     heap_free(data);
679 
680     if (!recurse)
681     {
682         if (rc == ERROR_FILE_NOT_FOUND)
683         {
684             if (value_name && *value_name)
685             {
686                 output_message(STRING_CANNOT_FIND);
687                 return 1;
688             }
689             output_string(fmt, path);
690             output_value(NULL, REG_SZ, NULL, 0);
691         }
692         return 0;
693     }
694 
695     subkey_name = heap_xalloc(MAX_SUBKEY_LEN * sizeof(WCHAR));
696 
697     path_len = strlenW(path);
698 
699     i = 0;
700     for (;;)
701     {
702         subkey_len = MAX_SUBKEY_LEN;
703         rc = RegEnumKeyExW(key, i, subkey_name, &subkey_len, NULL, NULL, NULL, NULL);
704         if (rc == ERROR_SUCCESS)
705         {
706             subkey_path = build_subkey_path(path, path_len, subkey_name, subkey_len);
707             if (!RegOpenKeyExW(key, subkey_name, 0, KEY_READ, &subkey))
708             {
709                 query_value(subkey, value_name, subkey_path, recurse);
710                 RegCloseKey(subkey);
711             }
712             heap_free(subkey_path);
713             i++;
714         }
715         else break;
716     }
717 
718     heap_free(subkey_name);
719     return 0;
720 }
721 
722 static int query_all(HKEY key, WCHAR *path, BOOL recurse)
723 {
724     LONG rc;
725     DWORD max_value_len = 256, value_len;
726     DWORD max_data_bytes = 2048, data_size;
727     DWORD subkey_len;
728     DWORD i, type, path_len;
729     WCHAR fmt[] = {'%','1','\n',0};
730     WCHAR fmt_path[] = {'%','1','\\','%','2','\n',0};
731     WCHAR *value_name, *subkey_name, *subkey_path;
732     BYTE *data;
733     HKEY subkey;
734 
735     output_string(fmt, path);
736 
737     value_name = heap_xalloc(max_value_len * sizeof(WCHAR));
738     data = heap_xalloc(max_data_bytes);
739 
740     i = 0;
741     for (;;)
742     {
743         value_len = max_value_len;
744         data_size = max_data_bytes;
745         rc = RegEnumValueW(key, i, value_name, &value_len, NULL, &type, data, &data_size);
746         if (rc == ERROR_SUCCESS)
747         {
748             output_value(value_name, type, data, data_size);
749             i++;
750         }
751         else if (rc == ERROR_MORE_DATA)
752         {
753             if (data_size > max_data_bytes)
754             {
755                 max_data_bytes = data_size;
756                 data = heap_xrealloc(data, max_data_bytes);
757             }
758             else
759             {
760                 max_value_len *= 2;
761                 value_name = heap_xrealloc(value_name, max_value_len * sizeof(WCHAR));
762             }
763         }
764         else break;
765     }
766 
767     heap_free(data);
768     heap_free(value_name);
769 
770     if (i || recurse)
771         output_string(newlineW);
772 
773     subkey_name = heap_xalloc(MAX_SUBKEY_LEN * sizeof(WCHAR));
774 
775     path_len = strlenW(path);
776 
777     i = 0;
778     for (;;)
779     {
780         subkey_len = MAX_SUBKEY_LEN;
781         rc = RegEnumKeyExW(key, i, subkey_name, &subkey_len, NULL, NULL, NULL, NULL);
782         if (rc == ERROR_SUCCESS)
783         {
784             if (recurse)
785             {
786                 subkey_path = build_subkey_path(path, path_len, subkey_name, subkey_len);
787                 if (!RegOpenKeyExW(key, subkey_name, 0, KEY_READ, &subkey))
788                 {
789                     query_all(subkey, subkey_path, recurse);
790                     RegCloseKey(subkey);
791                 }
792                 heap_free(subkey_path);
793             }
794             else output_string(fmt_path, path, subkey_name);
795             i++;
796         }
797         else break;
798     }
799 
800     heap_free(subkey_name);
801 
802     if (i && !recurse)
803         output_string(newlineW);
804 
805     return 0;
806 }
807 
808 static int reg_query(HKEY root, WCHAR *path, WCHAR *key_name, WCHAR *value_name,
809                      BOOL value_empty, BOOL recurse)
810 {
811     HKEY key;
812     int ret;
813 
814     if (RegOpenKeyExW(root, path, 0, KEY_READ, &key) != ERROR_SUCCESS)
815     {
816         output_message(STRING_CANNOT_FIND);
817         return 1;
818     }
819 
820     output_string(newlineW);
821 
822     if (value_name || value_empty)
823     {
824         ret = query_value(key, value_name, key_name, recurse);
825         if (recurse)
826             output_message(STRING_MATCHES_FOUND, num_values_found);
827     }
828     else
829         ret = query_all(key, key_name, recurse);
830 
831     RegCloseKey(key);
832 
833     return ret;
834 }
835 
836 static WCHAR *get_long_key(HKEY root, WCHAR *path)
837 {
838     DWORD i, array_size = ARRAY_SIZE(root_rels), len;
839     WCHAR *long_key;
840     WCHAR fmt[] = {'%','s','\\','%','s',0};
841 
842     for (i = 0; i < array_size; i++)
843     {
844         if (root == root_rels[i].key)
845             break;
846     }
847 
848     len = strlenW(root_rels[i].long_name);
849 
850     if (!path)
851     {
852         long_key = heap_xalloc((len + 1) * sizeof(WCHAR));
853         strcpyW(long_key, root_rels[i].long_name);
854         return long_key;
855     }
856 
857     len += strlenW(path) + 1; /* add one for the backslash */
858     long_key = heap_xalloc((len + 1) * sizeof(WCHAR));
859     sprintfW(long_key, fmt, root_rels[i].long_name, path);
860     return long_key;
861 }
862 
863 BOOL parse_registry_key(const WCHAR *key, HKEY *root, WCHAR **path, WCHAR **long_key)
864 {
865     if (!sane_path(key))
866         return FALSE;
867 
868     *path = strchrW(key, '\\');
869     if (*path) (*path)++;
870 
871     *root = path_get_rootkey(key);
872     if (!*root)
873     {
874         if (*path) *(*path - 1) = 0;
875         output_message(STRING_INVALID_SYSTEM_KEY, key);
876         return FALSE;
877     }
878 
879     *long_key = get_long_key(*root, *path);
880 
881     return TRUE;
882 }
883 
884 static BOOL is_switch(const WCHAR *s, const WCHAR c)
885 {
886     if (strlenW(s) > 2)
887         return FALSE;
888 
889     if ((s[0] == '/' || s[0] == '-') && (s[1] == c || s[1] == toupperW(c)))
890         return TRUE;
891 
892     return FALSE;
893 }
894 
895 static BOOL is_help_switch(const WCHAR *s)
896 {
897     if (is_switch(s, '?') || is_switch(s, 'h'))
898         return TRUE;
899 
900     return FALSE;
901 }
902 
903 enum operations {
904     REG_ADD,
905     REG_DELETE,
906     REG_IMPORT,
907     REG_EXPORT,
908     REG_QUERY,
909     REG_INVALID
910 };
911 
912 static enum operations get_operation(const WCHAR *str, int *op_help)
913 {
914     struct op_info { const WCHAR *op; int id; int help_id; };
915 
916     static const WCHAR add[] = {'a','d','d',0};
917     static const WCHAR delete[] = {'d','e','l','e','t','e',0};
918     static const WCHAR import[] = {'i','m','p','o','r','t',0};
919     static const WCHAR export[] = {'e','x','p','o','r','t',0};
920     static const WCHAR query[] = {'q','u','e','r','y',0};
921 
922     static const struct op_info op_array[] =
923     {
924         { add,     REG_ADD,     STRING_ADD_USAGE },
925         { delete,  REG_DELETE,  STRING_DELETE_USAGE },
926         { import,  REG_IMPORT,  STRING_IMPORT_USAGE },
927         { export,  REG_EXPORT,  STRING_EXPORT_USAGE },
928         { query,   REG_QUERY,   STRING_QUERY_USAGE },
929         { NULL,    -1,          0 }
930     };
931 
932     const struct op_info *ptr;
933 
934     for (ptr = op_array; ptr->op; ptr++)
935     {
936         if (!lstrcmpiW(str, ptr->op))
937         {
938             *op_help = ptr->help_id;
939             return ptr->id;
940         }
941     }
942 
943     return REG_INVALID;
944 }
945 
946 int wmain(int argc, WCHAR *argvW[])
947 {
948     int i, op, op_help, ret;
949     BOOL show_op_help = FALSE;
950     static const WCHAR switchVAW[] = {'v','a',0};
951     static const WCHAR switchVEW[] = {'v','e',0};
952     WCHAR *key_name, *path, *value_name = NULL, *type = NULL, *data = NULL, separator = '\0';
953     BOOL value_empty = FALSE, value_all = FALSE, recurse = FALSE, force = FALSE;
954     HKEY root;
955 
956     if (argc == 1)
957     {
958         output_message(STRING_INVALID_SYNTAX);
959         output_message(STRING_REG_HELP);
960         return 1;
961     }
962 
963     if (is_help_switch(argvW[1]))
964     {
965         output_message(STRING_USAGE);
966         return 0;
967     }
968 
969     op = get_operation(argvW[1], &op_help);
970 
971     if (op == REG_INVALID)
972     {
973         output_message(STRING_INVALID_OPTION, argvW[1]);
974         output_message(STRING_REG_HELP);
975         return 1;
976     }
977 
978     if (argc > 2)
979         show_op_help = is_help_switch(argvW[2]);
980 
981     if (argc == 2 || ((show_op_help || op == REG_IMPORT) && argc > 3))
982     {
983         output_message(STRING_INVALID_SYNTAX);
984         output_message(STRING_FUNC_HELP, struprW(argvW[1]));
985         return 1;
986     }
987     else if (show_op_help)
988     {
989         output_message(op_help);
990         return 0;
991     }
992 
993     if (op == REG_IMPORT)
994         return reg_import(argvW[2]);
995 
996     if (op == REG_EXPORT)
997         return reg_export(argc, argvW);
998 
999     if (!parse_registry_key(argvW[2], &root, &path, &key_name))
1000         return 1;
1001 
1002     for (i = 3; i < argc; i++)
1003     {
1004         if (argvW[i][0] == '/' || argvW[i][0] == '-')
1005         {
1006             WCHAR *ptr = &argvW[i][1];
1007 
1008             if (!lstrcmpiW(ptr, switchVEW))
1009             {
1010                 value_empty = TRUE;
1011                 continue;
1012             }
1013             else if (!lstrcmpiW(ptr, switchVAW))
1014             {
1015                 value_all = TRUE;
1016                 continue;
1017             }
1018             else if (!ptr[0] || ptr[1])
1019             {
1020                 output_message(STRING_INVALID_CMDLINE);
1021                 return 1;
1022             }
1023 
1024             switch(tolowerW(argvW[i][1]))
1025             {
1026             case 'v':
1027                 if (value_name || !(value_name = argvW[++i]))
1028                 {
1029                     output_message(STRING_INVALID_CMDLINE);
1030                     return 1;
1031                 }
1032                 break;
1033             case 't':
1034                 if (type || !(type = argvW[++i]))
1035                 {
1036                     output_message(STRING_INVALID_CMDLINE);
1037                     return 1;
1038                 }
1039                 break;
1040             case 'd':
1041                 if (data || !(data = argvW[++i]))
1042                 {
1043                     output_message(STRING_INVALID_CMDLINE);
1044                     return 1;
1045                 }
1046                 break;
1047             case 's':
1048                 if (op == REG_QUERY)
1049                 {
1050                     recurse = TRUE;
1051                     break;
1052                 }
1053 
1054                 ptr = argvW[++i];
1055                 if (!ptr || strlenW(ptr) != 1)
1056                 {
1057                     output_message(STRING_INVALID_CMDLINE);
1058                     return 1;
1059                 }
1060                 separator = ptr[0];
1061                 break;
1062             case 'f':
1063                 force = TRUE;
1064                 break;
1065             default:
1066                 output_message(STRING_INVALID_CMDLINE);
1067                 return 1;
1068             }
1069         }
1070     }
1071 
1072     if ((value_name && value_empty) || (value_name && value_all) || (value_empty && value_all))
1073     {
1074         output_message(STRING_INVALID_CMDLINE);
1075         return 1;
1076     }
1077 
1078     if (op == REG_ADD)
1079         ret = reg_add(root, path, value_name, value_empty, type, separator, data, force);
1080     else if (op == REG_DELETE)
1081         ret = reg_delete(root, path, key_name, value_name, value_empty, value_all, force);
1082     else
1083         ret = reg_query(root, path, key_name, value_name, value_empty, recurse);
1084     return ret;
1085 }
1086