xref: /vim-8.2.3635/src/dosinst.h (revision 4c05fa08)
1 /* vi:set ts=8 sts=4 sw=4 noet:
2  *
3  * VIM - Vi IMproved	by Bram Moolenaar
4  *
5  * Do ":help uganda"  in Vim to read copying and usage conditions.
6  * Do ":help credits" in Vim to see a list of people who contributed.
7  * See README.txt for an overview of the Vim source code.
8  */
9 /*
10  * dosinst.h: Common code for dosinst.c and uninstal.c
11  */
12 
13 /* Visual Studio 2005 has 'deprecated' many of the standard CRT functions */
14 #if _MSC_VER >= 1400
15 # define _CRT_SECURE_NO_DEPRECATE
16 # define _CRT_NONSTDC_NO_DEPRECATE
17 #endif
18 
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <sys/stat.h>
23 #include <fcntl.h>
24 
25 #ifndef UNIX_LINT
26 # include "vimio.h"
27 # include <ctype.h>
28 
29 # include <direct.h>
30 
31 # include <windows.h>
32 # include <shlobj.h>
33 #endif
34 
35 #ifdef UNIX_LINT
36 /* Running lint on Unix: Some things are missing. */
37 char *searchpath(char *name);
38 #endif
39 
40 #if defined(UNIX_LINT)
41 # include <unistd.h>
42 # include <errno.h>
43 #endif
44 
45 #include "version.h"
46 
47 #if defined(UNIX_LINT)
48 # define vim_mkdir(x, y) mkdir((char *)(x), y)
49 #else
50 # ifndef __BORLANDC__
51 #  define vim_mkdir(x, y) _mkdir((char *)(x))
52 # else
53 #  define vim_mkdir(x, y) mkdir((char *)(x))
54 # endif
55 #endif
56 
57 #define sleep(n) Sleep((n) * 1000)
58 
59 /* ---------------------------------------- */
60 
61 
62 #define BUFSIZE 512		/* long enough to hold a file name path */
63 #define NUL 0
64 
65 #define FAIL 0
66 #define OK 1
67 
68 #ifndef FALSE
69 # define FALSE 0
70 #endif
71 #ifndef TRUE
72 # define TRUE 1
73 #endif
74 
75 /*
76  * Modern way of creating registry entries, also works on 64 bit windows when
77  * compiled as a 32 bit program.
78  */
79 # ifndef KEY_WOW64_64KEY
80 #  define KEY_WOW64_64KEY 0x0100
81 # endif
82 # ifndef KEY_WOW64_32KEY
83 #  define KEY_WOW64_32KEY 0x0200
84 # endif
85 
86 #define VIM_STARTMENU "Programs\\Vim " VIM_VERSION_SHORT
87 
88 int	interactive;		/* non-zero when running interactively */
89 
90 /*
91  * Call malloc() and exit when out of memory.
92  */
93     static void *
94 alloc(int len)
95 {
96     char *s;
97 
98     s = malloc(len);
99     if (s == NULL)
100     {
101 	printf("ERROR: out of memory\n");
102 	exit(1);
103     }
104     return (void *)s;
105 }
106 
107 /*
108  * The toupper() in Bcc 5.5 doesn't work, use our own implementation.
109  */
110     static int
111 mytoupper(int c)
112 {
113     if (c >= 'a' && c <= 'z')
114 	return c - 'a' + 'A';
115     return c;
116 }
117 
118     static void
119 myexit(int n)
120 {
121     if (!interactive)
122     {
123 	/* Present a prompt, otherwise error messages can't be read. */
124 	printf("Press Enter to continue\n");
125 	rewind(stdin);
126 	(void)getchar();
127     }
128     exit(n);
129 }
130 
131 
132 typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS)(HANDLE, PBOOL);
133 /*
134  * Check if this is a 64-bit OS.
135  */
136     static BOOL
137 is_64bit_os(void)
138 {
139 #ifdef _WIN64
140     return TRUE;
141 #else
142     BOOL bIsWow64 = FALSE;
143     LPFN_ISWOW64PROCESS pIsWow64Process;
144 
145     pIsWow64Process = (LPFN_ISWOW64PROCESS)GetProcAddress(
146 	    GetModuleHandle("kernel32"), "IsWow64Process");
147     if (pIsWow64Process != NULL)
148 	pIsWow64Process(GetCurrentProcess(), &bIsWow64);
149     return bIsWow64;
150 #endif
151 }
152 
153 #ifdef __BORLANDC__
154 /* Borland defines its own searchpath() in dir.h */
155 # include <dir.h>
156 #else
157     static char *
158 searchpath(char *name)
159 {
160     static char widename[2 * BUFSIZE];
161     static char location[2 * BUFSIZE + 2];
162 
163     /* There appears to be a bug in FindExecutableA() on Windows NT.
164      * Use FindExecutableW() instead... */
165     MultiByteToWideChar(CP_ACP, 0, (LPCTSTR)name, -1,
166 	    (LPWSTR)widename, BUFSIZE);
167     if (FindExecutableW((LPCWSTR)widename, (LPCWSTR)"",
168 		(LPWSTR)location) > (HINSTANCE)32)
169     {
170 	WideCharToMultiByte(CP_ACP, 0, (LPWSTR)location, -1,
171 		(LPSTR)widename, 2 * BUFSIZE, NULL, NULL);
172 	return widename;
173     }
174     return NULL;
175 }
176 #endif
177 
178 /*
179  * Call searchpath() and save the result in allocated memory, or return NULL.
180  */
181     static char *
182 searchpath_save(char *name)
183 {
184     char	*p;
185     char	*s;
186 
187     p = searchpath(name);
188     if (p == NULL)
189 	return NULL;
190     s = alloc(strlen(p) + 1);
191     strcpy(s, p);
192     return s;
193 }
194 
195 
196 #ifndef CSIDL_COMMON_PROGRAMS
197 # define CSIDL_COMMON_PROGRAMS 0x0017
198 #endif
199 #ifndef CSIDL_COMMON_DESKTOPDIRECTORY
200 # define CSIDL_COMMON_DESKTOPDIRECTORY 0x0019
201 #endif
202 
203 /*
204  * Get the path to a requested Windows shell folder.
205  *
206  * Return FAIL on error, OK on success
207  */
208     int
209 get_shell_folder_path(
210 	char *shell_folder_path,
211 	const char *shell_folder_name)
212 {
213     /*
214      * The following code was successfully built with make_mvc.mak.
215      * The resulting executable worked on Windows 95, Millennium Edition, and
216      * 2000 Professional.  But it was changed after testing...
217      */
218     LPITEMIDLIST    pidl = 0; /* Pointer to an Item ID list allocated below */
219     LPMALLOC	    pMalloc;  /* Pointer to an IMalloc interface */
220     int		    csidl;
221     int		    alt_csidl = -1;
222     static int	    desktop_csidl = -1;
223     static int	    programs_csidl = -1;
224     int		    *pcsidl;
225     int		    r;
226 
227     if (strcmp(shell_folder_name, "desktop") == 0)
228     {
229 	pcsidl = &desktop_csidl;
230 	csidl = CSIDL_COMMON_DESKTOPDIRECTORY;
231 	alt_csidl = CSIDL_DESKTOP;
232     }
233     else if (strncmp(shell_folder_name, "Programs", 8) == 0)
234     {
235 	pcsidl = &programs_csidl;
236 	csidl = CSIDL_COMMON_PROGRAMS;
237 	alt_csidl = CSIDL_PROGRAMS;
238     }
239     else
240     {
241 	printf("\nERROR (internal) unrecognised shell_folder_name: \"%s\"\n\n",
242 							   shell_folder_name);
243 	return FAIL;
244     }
245 
246     /* Did this stuff before, use the same ID again. */
247     if (*pcsidl >= 0)
248     {
249 	csidl = *pcsidl;
250 	alt_csidl = -1;
251     }
252 
253 retry:
254     /* Initialize pointer to IMalloc interface */
255     if (NOERROR != SHGetMalloc(&pMalloc))
256     {
257 	printf("\nERROR getting interface for shell_folder_name: \"%s\"\n\n",
258 							   shell_folder_name);
259 	return FAIL;
260     }
261 
262     /* Get an ITEMIDLIST corresponding to the folder code */
263     if (NOERROR != SHGetSpecialFolderLocation(0, csidl, &pidl))
264     {
265 	if (alt_csidl < 0 || NOERROR != SHGetSpecialFolderLocation(0,
266 							    alt_csidl, &pidl))
267 	{
268 	    printf("\nERROR getting ITEMIDLIST for shell_folder_name: \"%s\"\n\n",
269 							   shell_folder_name);
270 	    return FAIL;
271 	}
272 	csidl = alt_csidl;
273 	alt_csidl = -1;
274     }
275 
276     /* Translate that ITEMIDLIST to a string */
277     r = SHGetPathFromIDList(pidl, shell_folder_path);
278 
279     /* Free the data associated with pidl */
280     pMalloc->lpVtbl->Free(pMalloc, pidl);
281     /* Release the IMalloc interface */
282     pMalloc->lpVtbl->Release(pMalloc);
283 
284     if (!r)
285     {
286 	if (alt_csidl >= 0)
287 	{
288 	    /* We probably get here for Windows 95: the "all users"
289 	     * desktop/start menu entry doesn't exist. */
290 	    csidl = alt_csidl;
291 	    alt_csidl = -1;
292 	    goto retry;
293 	}
294 	printf("\nERROR translating ITEMIDLIST for shell_folder_name: \"%s\"\n\n",
295 							   shell_folder_name);
296 	return FAIL;
297     }
298 
299     /* If there is an alternative: verify we can write in this directory.
300      * This should cause a retry when the "all users" directory exists but we
301      * are a normal user and can't write there. */
302     if (alt_csidl >= 0)
303     {
304 	char tbuf[BUFSIZE];
305 	FILE *fd;
306 
307 	strcpy(tbuf, shell_folder_path);
308 	strcat(tbuf, "\\vim write test");
309 	fd = fopen(tbuf, "w");
310 	if (fd == NULL)
311 	{
312 	    csidl = alt_csidl;
313 	    alt_csidl = -1;
314 	    goto retry;
315 	}
316 	fclose(fd);
317 	unlink(tbuf);
318     }
319 
320     /*
321      * Keep the found csidl for next time, so that we don't have to do the
322      * write test every time.
323      */
324     if (*pcsidl < 0)
325 	*pcsidl = csidl;
326 
327     if (strncmp(shell_folder_name, "Programs\\", 9) == 0)
328 	strcat(shell_folder_path, shell_folder_name + 8);
329 
330     return OK;
331 }
332 
333 /*
334  * List of targets.  The first one (index zero) is used for the default path
335  * for the batch files.
336  */
337 #define TARGET_COUNT  9
338 
339 struct
340 {
341     char	*name;		/* Vim exe name (without .exe) */
342     char	*batname;	/* batch file name */
343     char	*lnkname;	/* shortcut file name */
344     char	*exename;	/* exe file name */
345     char	*exenamearg;	/* exe file name when using exearg */
346     char	*exearg;	/* argument for vim.exe or gvim.exe */
347     char	*oldbat;	/* path to existing xxx.bat or NULL */
348     char	*oldexe;	/* path to existing xxx.exe or NULL */
349     char	batpath[BUFSIZE];  /* path of batch file to create; not
350 				      created when it's empty */
351 } targets[TARGET_COUNT] =
352 {
353     {"all",	"batch files"},
354     {"vim",	"vim.bat",	"Vim.lnk",
355 					"vim.exe",    "vim.exe",  ""},
356     {"gvim",	"gvim.bat",	"gVim.lnk",
357 					"gvim.exe",   "gvim.exe", ""},
358     {"evim",	"evim.bat",	"gVim Easy.lnk",
359 					"evim.exe",   "gvim.exe", "-y"},
360     {"view",	"view.bat",	"Vim Read-only.lnk",
361 					"view.exe",   "vim.exe",  "-R"},
362     {"gview",	"gview.bat",	"gVim Read-only.lnk",
363 					"gview.exe",  "gvim.exe", "-R"},
364     {"vimdiff", "vimdiff.bat",	"Vim Diff.lnk",
365 					"vimdiff.exe","vim.exe",  "-d"},
366     {"gvimdiff","gvimdiff.bat",	"gVim Diff.lnk",
367 					"gvimdiff.exe","gvim.exe", "-d"},
368     {"vimtutor","vimtutor.bat", "Vim tutor.lnk",
369 					"vimtutor.bat",  "vimtutor.bat", ""},
370 };
371 
372 #define ICON_COUNT 3
373 char *(icon_names[ICON_COUNT]) =
374 	{"gVim " VIM_VERSION_SHORT,
375 	 "gVim Easy " VIM_VERSION_SHORT,
376 	 "gVim Read only " VIM_VERSION_SHORT};
377 char *(icon_link_names[ICON_COUNT]) =
378 	{"gVim " VIM_VERSION_SHORT ".lnk",
379 	 "gVim Easy " VIM_VERSION_SHORT ".lnk",
380 	 "gVim Read only " VIM_VERSION_SHORT ".lnk"};
381 
382 /* This is only used for dosinst.c. */
383 #if defined(DOSINST)
384 /*
385  * Run an external command and wait for it to finish.
386  */
387     static void
388 run_command(char *cmd)
389 {
390     char	*cmd_path;
391     char	cmd_buf[BUFSIZE];
392     char	*p;
393 
394     /* On WinNT, 'start' is a shell built-in for cmd.exe rather than an
395      * executable (start.exe) like in Win9x. */
396     cmd_path = searchpath_save("cmd.exe");
397     if (cmd_path != NULL)
398     {
399 	/* There is a cmd.exe, so this might be Windows NT.  If it is,
400 	 * we need to call cmd.exe explicitly.  If it is a later OS,
401 	 * calling cmd.exe won't hurt if it is present.
402 	 * Also, "start" on NT expects a window title argument.
403 	 */
404 	/* Replace the slashes with backslashes. */
405 	while ((p = strchr(cmd_path, '/')) != NULL)
406 	    *p = '\\';
407 	sprintf(cmd_buf, "%s /c start \"vimcmd\" /wait %s", cmd_path, cmd);
408 	free(cmd_path);
409     }
410     else
411     {
412 	/* No cmd.exe, just make the call and let the system handle it. */
413 	sprintf(cmd_buf, "start /w %s", cmd);
414     }
415     system(cmd_buf);
416 }
417 #endif
418 
419 /*
420  * Append a backslash to "name" if there isn't one yet.
421  */
422     void
423 add_pathsep(char *name)
424 {
425     int		len = strlen(name);
426 
427     if (len > 0 && name[len - 1] != '\\' && name[len - 1] != '/')
428 	strcat(name, "\\");
429 }
430 
431 /*
432  * The normal chdir() does not change the default drive.  This one does.
433  */
434 /*ARGSUSED*/
435     int
436 change_drive(int drive)
437 {
438     char temp[3] = "-:";
439     temp[0] = (char)(drive + 'A' - 1);
440     return !SetCurrentDirectory(temp);
441 }
442 
443 /*
444  * Change directory to "path".
445  * Return 0 for success, -1 for failure.
446  */
447     int
448 mch_chdir(char *path)
449 {
450     if (path[0] == NUL)		/* just checking... */
451 	return 0;
452     if (path[1] == ':')		/* has a drive name */
453     {
454 	if (change_drive(mytoupper(path[0]) - 'A' + 1))
455 	    return -1;		/* invalid drive name */
456 	path += 2;
457     }
458     if (*path == NUL)		/* drive name only */
459 	return 0;
460     return chdir(path);		/* let the normal chdir() do the rest */
461 }
462 
463 /*
464  * Expand the executable name into a full path name.
465  */
466 #if defined(__BORLANDC__)
467 
468 /* Only Borland C++ has this. */
469 # define my_fullpath(b, n, l) _fullpath(b, n, l)
470 
471 #else
472     static char *
473 my_fullpath(char *buf, char *fname, int len)
474 {
475     /* Only GetModuleFileName() will get the long file name path.
476      * GetFullPathName() may still use the short (FAT) name. */
477     DWORD len_read = GetModuleFileName(NULL, buf, (size_t)len);
478 
479     return (len_read > 0 && len_read < (DWORD)len) ? buf : NULL;
480 }
481 #endif
482 
483 /*
484  * Remove the tail from a file or directory name.
485  * Puts a NUL on the last '/' or '\'.
486  */
487     static void
488 remove_tail(char *path)
489 {
490     int		i;
491 
492     for (i = strlen(path) - 1; i > 0; --i)
493 	if (path[i] == '/' || path[i] == '\\')
494 	{
495 	    path[i] = NUL;
496 	    break;
497 	}
498 }
499 
500 
501 char	installdir[BUFSIZE];	/* top of the installation dir, where the
502 				   install.exe is located, E.g.:
503 				   "c:\vim\vim60" */
504 int	runtimeidx;		/* index in installdir[] where "vim60" starts */
505 char	*sysdrive;		/* system drive or "c:\" */
506 
507 /*
508  * Setup for using this program.
509  * Sets "installdir[]".
510  */
511     static void
512 do_inits(char **argv)
513 {
514     /* Find out the full path of our executable. */
515     if (my_fullpath(installdir, argv[0], BUFSIZE) == NULL)
516     {
517 	printf("ERROR: Cannot get name of executable\n");
518 	myexit(1);
519     }
520     /* remove the tail, the executable name "install.exe" */
521     remove_tail(installdir);
522 
523     /* change to the installdir */
524     mch_chdir(installdir);
525 
526     /* Find the system drive.  Only used for searching the Vim executable, not
527      * very important. */
528     sysdrive = getenv("SYSTEMDRIVE");
529     if (sysdrive == NULL || *sysdrive == NUL)
530 	sysdrive = "C:\\";
531 }
532