xref: /vim-8.2.3635/src/dosinst.h (revision 2bf24176)
1 /* vi:set ts=8 sts=4 sw=4:
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 # ifndef __CYGWIN__
30 #  include <direct.h>
31 # endif
32 
33 # if defined(_WIN64) || defined(WIN32)
34 #  define WIN3264
35 #  include <windows.h>
36 #  include <shlobj.h>
37 # else
38 #  include <dir.h>
39 #  include <bios.h>
40 #  include <dos.h>
41 # endif
42 #endif
43 
44 #ifdef UNIX_LINT
45 /* Running lint on Unix: Some things are missing. */
46 char *searchpath(char *name);
47 #endif
48 
49 #if defined(DJGPP) || defined(UNIX_LINT)
50 # include <unistd.h>
51 # include <errno.h>
52 #endif
53 
54 #include "version.h"
55 
56 #if defined(DJGPP) || defined(UNIX_LINT)
57 # define vim_mkdir(x, y) mkdir((char *)(x), y)
58 #else
59 # if defined(WIN3264) && !defined(__BORLANDC__)
60 #  define vim_mkdir(x, y) _mkdir((char *)(x))
61 # else
62 #  define vim_mkdir(x, y) mkdir((char *)(x))
63 # endif
64 #endif
65 
66 #ifndef DJGPP
67 # define sleep(n) Sleep((n) * 1000)
68 #endif
69 
70 /* ---------------------------------------- */
71 
72 
73 #define BUFSIZE 512		/* long enough to hold a file name path */
74 #define NUL 0
75 
76 #define FAIL 0
77 #define OK 1
78 
79 #ifndef FALSE
80 # define FALSE 0
81 #endif
82 #ifndef TRUE
83 # define TRUE 1
84 #endif
85 
86 /*
87  * Modern way of creating registry entries, also works on 64 bit windows when
88  * compiled as a 32 bit program.
89  */
90 # ifndef KEY_WOW64_64KEY
91 #  define KEY_WOW64_64KEY 0x0100
92 # endif
93 
94 #define VIM_STARTMENU "Programs\\Vim " VIM_VERSION_SHORT
95 
96 int	interactive;		/* non-zero when running interactively */
97 
98 /*
99  * Call malloc() and exit when out of memory.
100  */
101     static void *
102 alloc(int len)
103 {
104     char *s;
105 
106     s = malloc(len);
107     if (s == NULL)
108     {
109 	printf("ERROR: out of memory\n");
110 	exit(1);
111     }
112     return (void *)s;
113 }
114 
115 /*
116  * The toupper() in Bcc 5.5 doesn't work, use our own implementation.
117  */
118     static int
119 mytoupper(int c)
120 {
121     if (c >= 'a' && c <= 'z')
122 	return c - 'a' + 'A';
123     return c;
124 }
125 
126     static void
127 myexit(int n)
128 {
129     if (!interactive)
130     {
131 	/* Present a prompt, otherwise error messages can't be read. */
132 	printf("Press Enter to continue\n");
133 	rewind(stdin);
134 	(void)getchar();
135     }
136     exit(n);
137 }
138 
139 #ifdef WIN3264
140 /* This symbol is not defined in older versions of the SDK or Visual C++ */
141 
142 #ifndef VER_PLATFORM_WIN32_WINDOWS
143 # define VER_PLATFORM_WIN32_WINDOWS 1
144 #endif
145 
146 static DWORD g_PlatformId;
147 
148 /*
149  * Set g_PlatformId to VER_PLATFORM_WIN32_NT (NT) or
150  * VER_PLATFORM_WIN32_WINDOWS (Win95).
151  */
152     static void
153 PlatformId(void)
154 {
155     static int done = FALSE;
156 
157     if (!done)
158     {
159 	OSVERSIONINFO ovi;
160 
161 	ovi.dwOSVersionInfoSize = sizeof(ovi);
162 	GetVersionEx(&ovi);
163 
164 	g_PlatformId = ovi.dwPlatformId;
165 	done = TRUE;
166     }
167 }
168 
169 # ifdef __BORLANDC__
170 /* Borland defines its own searchpath() in dir.h */
171 #  include <dir.h>
172 # else
173     static char *
174 searchpath(char *name)
175 {
176     static char widename[2 * BUFSIZE];
177     static char location[2 * BUFSIZE + 2];
178 
179     /* There appears to be a bug in FindExecutableA() on Windows NT.
180      * Use FindExecutableW() instead... */
181     PlatformId();
182     if (g_PlatformId == VER_PLATFORM_WIN32_NT)
183     {
184 	MultiByteToWideChar(CP_ACP, 0, (LPCTSTR)name, -1,
185 		(LPWSTR)widename, BUFSIZE);
186 	if (FindExecutableW((LPCWSTR)widename, (LPCWSTR)"",
187 		    (LPWSTR)location) > (HINSTANCE)32)
188 	{
189 	    WideCharToMultiByte(CP_ACP, 0, (LPWSTR)location, -1,
190 		    (LPSTR)widename, 2 * BUFSIZE, NULL, NULL);
191 	    return widename;
192 	}
193     }
194     else
195     {
196 	if (FindExecutableA((LPCTSTR)name, (LPCTSTR)"",
197 		    (LPTSTR)location) > (HINSTANCE)32)
198 	    return location;
199     }
200     return NULL;
201 }
202 # endif
203 #endif
204 
205 /*
206  * Call searchpath() and save the result in allocated memory, or return NULL.
207  */
208     static char *
209 searchpath_save(char *name)
210 {
211     char	*p;
212     char	*s;
213 
214     p = searchpath(name);
215     if (p == NULL)
216 	return NULL;
217     s = alloc(strlen(p) + 1);
218     strcpy(s, p);
219     return s;
220 }
221 
222 #ifdef WIN3264
223 
224 #ifndef CSIDL_COMMON_PROGRAMS
225 # define CSIDL_COMMON_PROGRAMS 0x0017
226 #endif
227 #ifndef CSIDL_COMMON_DESKTOPDIRECTORY
228 # define CSIDL_COMMON_DESKTOPDIRECTORY 0x0019
229 #endif
230 
231 /*
232  * Get the path to a requested Windows shell folder.
233  *
234  * Return FAIL on error, OK on success
235  */
236     int
237 get_shell_folder_path(
238 	char *shell_folder_path,
239 	const char *shell_folder_name)
240 {
241     /*
242      * The following code was successfully built with make_mvc.mak.
243      * The resulting executable worked on Windows 95, Millennium Edition, and
244      * 2000 Professional.  But it was changed after testing...
245      */
246     LPITEMIDLIST    pidl = 0; /* Pointer to an Item ID list allocated below */
247     LPMALLOC	    pMalloc;  /* Pointer to an IMalloc interface */
248     int		    csidl;
249     int		    alt_csidl = -1;
250     static int	    desktop_csidl = -1;
251     static int	    programs_csidl = -1;
252     int		    *pcsidl;
253     int		    r;
254 
255     if (strcmp(shell_folder_name, "desktop") == 0)
256     {
257 	pcsidl = &desktop_csidl;
258 	csidl = CSIDL_COMMON_DESKTOPDIRECTORY;
259 	alt_csidl = CSIDL_DESKTOP;
260     }
261     else if (strncmp(shell_folder_name, "Programs", 8) == 0)
262     {
263 	pcsidl = &programs_csidl;
264 	csidl = CSIDL_COMMON_PROGRAMS;
265 	alt_csidl = CSIDL_PROGRAMS;
266     }
267     else
268     {
269 	printf("\nERROR (internal) unrecognised shell_folder_name: \"%s\"\n\n",
270 							   shell_folder_name);
271 	return FAIL;
272     }
273 
274     /* Did this stuff before, use the same ID again. */
275     if (*pcsidl >= 0)
276     {
277 	csidl = *pcsidl;
278 	alt_csidl = -1;
279     }
280 
281 retry:
282     /* Initialize pointer to IMalloc interface */
283     if (NOERROR != SHGetMalloc(&pMalloc))
284     {
285 	printf("\nERROR getting interface for shell_folder_name: \"%s\"\n\n",
286 							   shell_folder_name);
287 	return FAIL;
288     }
289 
290     /* Get an ITEMIDLIST corresponding to the folder code */
291     if (NOERROR != SHGetSpecialFolderLocation(0, csidl, &pidl))
292     {
293 	if (alt_csidl < 0 || NOERROR != SHGetSpecialFolderLocation(0,
294 							    alt_csidl, &pidl))
295 	{
296 	    printf("\nERROR getting ITEMIDLIST for shell_folder_name: \"%s\"\n\n",
297 							   shell_folder_name);
298 	    return FAIL;
299 	}
300 	csidl = alt_csidl;
301 	alt_csidl = -1;
302     }
303 
304     /* Translate that ITEMIDLIST to a string */
305     r = SHGetPathFromIDList(pidl, shell_folder_path);
306 
307     /* Free the data associated with pidl */
308     pMalloc->lpVtbl->Free(pMalloc, pidl);
309     /* Release the IMalloc interface */
310     pMalloc->lpVtbl->Release(pMalloc);
311 
312     if (!r)
313     {
314 	if (alt_csidl >= 0)
315 	{
316 	    /* We probably get here for Windows 95: the "all users"
317 	     * desktop/start menu entry doesn't exist. */
318 	    csidl = alt_csidl;
319 	    alt_csidl = -1;
320 	    goto retry;
321 	}
322 	printf("\nERROR translating ITEMIDLIST for shell_folder_name: \"%s\"\n\n",
323 							   shell_folder_name);
324 	return FAIL;
325     }
326 
327     /* If there is an alternative: verify we can write in this directory.
328      * This should cause a retry when the "all users" directory exists but we
329      * are a normal user and can't write there. */
330     if (alt_csidl >= 0)
331     {
332 	char tbuf[BUFSIZE];
333 	FILE *fd;
334 
335 	strcpy(tbuf, shell_folder_path);
336 	strcat(tbuf, "\\vim write test");
337 	fd = fopen(tbuf, "w");
338 	if (fd == NULL)
339 	{
340 	    csidl = alt_csidl;
341 	    alt_csidl = -1;
342 	    goto retry;
343 	}
344 	fclose(fd);
345 	unlink(tbuf);
346     }
347 
348     /*
349      * Keep the found csidl for next time, so that we don't have to do the
350      * write test every time.
351      */
352     if (*pcsidl < 0)
353 	*pcsidl = csidl;
354 
355     if (strncmp(shell_folder_name, "Programs\\", 9) == 0)
356 	strcat(shell_folder_path, shell_folder_name + 8);
357 
358     return OK;
359 }
360 #endif
361 
362 /*
363  * List of targets.  The first one (index zero) is used for the default path
364  * for the batch files.
365  */
366 #define TARGET_COUNT  9
367 
368 struct
369 {
370     char	*name;		/* Vim exe name (without .exe) */
371     char	*batname;	/* batch file name */
372     char	*lnkname;	/* shortcut file name */
373     char	*exename;	/* exe file name */
374     char	*exenamearg;	/* exe file name when using exearg */
375     char	*exearg;	/* argument for vim.exe or gvim.exe */
376     char	*oldbat;	/* path to existing xxx.bat or NULL */
377     char	*oldexe;	/* path to existing xxx.exe or NULL */
378     char	batpath[BUFSIZE];  /* path of batch file to create; not
379 				      created when it's empty */
380 } targets[TARGET_COUNT] =
381 {
382     {"all",	"batch files"},
383     {"vim",	"vim.bat",	"Vim.lnk",
384 					"vim.exe",    "vim.exe",  ""},
385     {"gvim",	"gvim.bat",	"gVim.lnk",
386 					"gvim.exe",   "gvim.exe", ""},
387     {"evim",	"evim.bat",	"gVim Easy.lnk",
388 					"evim.exe",   "gvim.exe", "-y"},
389     {"view",	"view.bat",	"Vim Read-only.lnk",
390 					"view.exe",   "vim.exe",  "-R"},
391     {"gview",	"gview.bat",	"gVim Read-only.lnk",
392 					"gview.exe",  "gvim.exe", "-R"},
393     {"vimdiff", "vimdiff.bat",	"Vim Diff.lnk",
394 					"vimdiff.exe","vim.exe",  "-d"},
395     {"gvimdiff","gvimdiff.bat",	"gVim Diff.lnk",
396 					"gvimdiff.exe","gvim.exe", "-d"},
397     {"vimtutor","vimtutor.bat", "Vim tutor.lnk",
398 					"vimtutor.bat",  "vimtutor.bat", ""},
399 };
400 
401 #define ICON_COUNT 3
402 char *(icon_names[ICON_COUNT]) =
403 	{"gVim " VIM_VERSION_SHORT,
404 	 "gVim Easy " VIM_VERSION_SHORT,
405 	 "gVim Read only " VIM_VERSION_SHORT};
406 char *(icon_link_names[ICON_COUNT]) =
407 	{"gVim " VIM_VERSION_SHORT ".lnk",
408 	 "gVim Easy " VIM_VERSION_SHORT ".lnk",
409 	 "gVim Read only " VIM_VERSION_SHORT ".lnk"};
410 
411 /* This is only used for dosinst.c when WIN3264 is defined and for uninstal.c
412  * when not being able to directly access registry entries. */
413 #if (defined(DOSINST) && defined(WIN3264)) \
414 	|| (!defined(DOSINST) && !defined(WIN3264))
415 /*
416  * Run an external command and wait for it to finish.
417  */
418     static void
419 run_command(char *cmd)
420 {
421     char	*cmd_path;
422     char	cmd_buf[BUFSIZE];
423     char	*p;
424 
425     /* On WinNT, 'start' is a shell built-in for cmd.exe rather than an
426      * executable (start.exe) like in Win9x.  DJGPP, being a DOS program,
427      * is given the COMSPEC command.com by WinNT, so we have to find
428      * cmd.exe manually and use it. */
429     cmd_path = searchpath_save("cmd.exe");
430     if (cmd_path != NULL)
431     {
432 	/* There is a cmd.exe, so this might be Windows NT.  If it is,
433 	 * we need to call cmd.exe explicitly.  If it is a later OS,
434 	 * calling cmd.exe won't hurt if it is present.
435 	 * Also, "start" on NT expects a window title argument.
436 	 */
437 	/* Replace the slashes with backslashes. */
438 	while ((p = strchr(cmd_path, '/')) != NULL)
439 	    *p = '\\';
440 	sprintf(cmd_buf, "%s /c start \"vimcmd\" /wait %s", cmd_path, cmd);
441 	free(cmd_path);
442     }
443     else
444     {
445 	/* No cmd.exe, just make the call and let the system handle it. */
446 	sprintf(cmd_buf, "start /w %s", cmd);
447     }
448     system(cmd_buf);
449 }
450 #endif
451 
452 /*
453  * Append a backslash to "name" if there isn't one yet.
454  */
455     static void
456 add_pathsep(char *name)
457 {
458     int		len = strlen(name);
459 
460     if (len > 0 && name[len - 1] != '\\' && name[len - 1] != '/')
461 	strcat(name, "\\");
462 }
463 
464 /*
465  * The normal chdir() does not change the default drive.  This one does.
466  */
467 /*ARGSUSED*/
468     int
469 change_drive(int drive)
470 {
471 #ifdef WIN3264
472     char temp[3] = "-:";
473     temp[0] = (char)(drive + 'A' - 1);
474     return !SetCurrentDirectory(temp);
475 #else
476 # ifndef UNIX_LINT
477     union REGS regs;
478 
479     regs.h.ah = 0x0e;
480     regs.h.dl = drive - 1;
481     intdos(&regs, &regs);   /* set default drive */
482     regs.h.ah = 0x19;
483     intdos(&regs, &regs);   /* get default drive */
484     if (regs.h.al == drive - 1)
485 	return 0;
486 # endif
487     return -1;
488 #endif
489 }
490 
491 /*
492  * Change directory to "path".
493  * Return 0 for success, -1 for failure.
494  */
495     int
496 mch_chdir(char *path)
497 {
498     if (path[0] == NUL)		/* just checking... */
499 	return 0;
500     if (path[1] == ':')		/* has a drive name */
501     {
502 	if (change_drive(mytoupper(path[0]) - 'A' + 1))
503 	    return -1;		/* invalid drive name */
504 	path += 2;
505     }
506     if (*path == NUL)		/* drive name only */
507 	return 0;
508     return chdir(path);		/* let the normal chdir() do the rest */
509 }
510 
511 /*
512  * Expand the executable name into a full path name.
513  */
514 #if defined(__BORLANDC__) && !defined(WIN3264)
515 
516 /* Only Borland C++ has this. */
517 # define my_fullpath(b, n, l) _fullpath(b, n, l)
518 
519 #else
520     static char *
521 my_fullpath(char *buf, char *fname, int len)
522 {
523 # ifdef WIN3264
524     /* Only GetModuleFileName() will get the long file name path.
525      * GetFullPathName() may still use the short (FAT) name. */
526     DWORD len_read = GetModuleFileName(NULL, buf, (size_t)len);
527 
528     return (len_read > 0 && len_read < (DWORD)len) ? buf : NULL;
529 # else
530     char	olddir[BUFSIZE];
531     char	*p, *q;
532     int		c;
533     char	*retval = buf;
534 
535     if (strchr(fname, ':') != NULL)	/* already expanded */
536     {
537 	strncpy(buf, fname, len);
538     }
539     else
540     {
541 	*buf = NUL;
542 	/*
543 	 * change to the directory for a moment,
544 	 * and then do the getwd() (and get back to where we were).
545 	 * This will get the correct path name with "../" things.
546 	 */
547 	p = strrchr(fname, '/');
548 	q = strrchr(fname, '\\');
549 	if (q != NULL && (p == NULL || q > p))
550 	    p = q;
551 	q = strrchr(fname, ':');
552 	if (q != NULL && (p == NULL || q > p))
553 	    p = q;
554 	if (p != NULL)
555 	{
556 	    if (getcwd(olddir, BUFSIZE) == NULL)
557 	    {
558 		p = NULL;		/* can't get current dir: don't chdir */
559 		retval = NULL;
560 	    }
561 	    else
562 	    {
563 		if (p == fname)		/* /fname		*/
564 		    q = p + 1;		/* -> /			*/
565 		else if (q + 1 == p)	/* ... c:\foo		*/
566 		    q = p + 1;		/* -> c:\		*/
567 		else			/* but c:\foo\bar	*/
568 		    q = p;		/* -> c:\foo		*/
569 
570 		c = *q;			/* truncate at start of fname */
571 		*q = NUL;
572 		if (mch_chdir(fname))	/* change to the directory */
573 		    retval = NULL;
574 		else
575 		{
576 		    fname = q;
577 		    if (c == '\\')	/* if we cut the name at a */
578 			fname++;	/* '\', don't add it again */
579 		}
580 		*q = c;
581 	    }
582 	}
583 	if (getcwd(buf, len) == NULL)
584 	{
585 	    retval = NULL;
586 	    *buf = NUL;
587 	}
588 	/*
589 	 * Concatenate the file name to the path.
590 	 */
591 	if (strlen(buf) + strlen(fname) >= len - 1)
592 	{
593 	    printf("ERROR: File name too long!\n");
594 	    myexit(1);
595 	}
596 	add_pathsep(buf);
597 	strcat(buf, fname);
598 	if (p)
599 	    mch_chdir(olddir);
600     }
601 
602     /* Replace forward slashes with backslashes, required for the path to a
603      * command. */
604     while ((p = strchr(buf, '/')) != NULL)
605 	*p = '\\';
606 
607     return retval;
608 # endif
609 }
610 #endif
611 
612 /*
613  * Remove the tail from a file or directory name.
614  * Puts a NUL on the last '/' or '\'.
615  */
616     static void
617 remove_tail(char *path)
618 {
619     int		i;
620 
621     for (i = strlen(path) - 1; i > 0; --i)
622 	if (path[i] == '/' || path[i] == '\\')
623 	{
624 	    path[i] = NUL;
625 	    break;
626 	}
627 }
628 
629 
630 char	installdir[BUFSIZE];	/* top of the installation dir, where the
631 				   install.exe is located, E.g.:
632 				   "c:\vim\vim60" */
633 int	runtimeidx;		/* index in installdir[] where "vim60" starts */
634 char	*sysdrive;		/* system drive or "c:\" */
635 
636 /*
637  * Setup for using this program.
638  * Sets "installdir[]".
639  */
640     static void
641 do_inits(char **argv)
642 {
643 #ifdef DJGPP
644     /*
645      * Use Long File Names by default, if $LFN not set.
646      */
647     if (getenv("LFN") == NULL)
648 	putenv("LFN=y");
649 #endif
650 
651     /* Find out the full path of our executable. */
652     if (my_fullpath(installdir, argv[0], BUFSIZE) == NULL)
653     {
654 	printf("ERROR: Cannot get name of executable\n");
655 	myexit(1);
656     }
657     /* remove the tail, the executable name "install.exe" */
658     remove_tail(installdir);
659 
660     /* change to the installdir */
661     mch_chdir(installdir);
662 
663     /* Find the system drive.  Only used for searching the Vim executable, not
664      * very important. */
665     sysdrive = getenv("SYSTEMDRIVE");
666     if (sysdrive == NULL || *sysdrive == NUL)
667 	sysdrive = "C:\\";
668 }
669