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