1 /*
2 * make-cygwin-symlink.c Copyright (C) 2014-16 Codemist
3 *
4 * This program is intended to allow one to find a cygwin installation
5 * on the current machine. It makes fairly strong assumptions about where
6 * one might be!
7 *
8 * Usage:
9 *
10 *(a) make-cygwin-symlink name-for-cygwin
11 * Delete the cygwin link in all /usr/local/bin directories
12 * that it is found in.
13 *
14 *(b) make-cygwin-symlink d:/installed/somewhere/foo.exe cygfoo
15 * This is like
16 * ln -s /cygdrive/d/installed/somewhere.foo.exe \
17 * /usr/local/bin/cygfoo
18 *
19 *(c) make-cygwin-symlink WINPATH1 WINPATH2 name-for-cygwin
20 * This is much the same but it links to WINPATH1 for any 32-bit
21 * installation of cygwin and WINPATH2 for any 64-bit one.
22 *
23 */
24
25 /* $Id: make-cygwin-symlink.c 4006 2017-04-17 16:21:43Z arthurcnorman $ */
26
27
28 /**************************************************************************
29 * Copyright (C) 2016, Codemist. A C Norman *
30 * *
31 * Redistribution and use in source and binary forms, with or without *
32 * modification, are permitted provided that the following conditions are *
33 * met: *
34 * *
35 * * Redistributions of source code must retain the relevant *
36 * copyright notice, this list of conditions and the following *
37 * disclaimer. *
38 * * Redistributions in binary form must reproduce the above *
39 * copyright notice, this list of conditions and the following *
40 * disclaimer in the documentation and/or other materials provided *
41 * with the distribution. *
42 * *
43 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS *
44 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT *
45 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS *
46 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE *
47 * COPYRIGHT OWNERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, *
48 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, *
49 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS *
50 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND *
51 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
52 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF *
53 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH *
54 * DAMAGE. *
55 *************************************************************************/
56
57 #include <stdio.h>
58 #include <windows.h>
59
60 const char *cygname, *winname, *winname64;
61
processFile(char * path)62 int processFile(char *path)
63 { FILE *f;
64 const char *p, *wname;
65 DWORD a = GetFileAttributes(path);
66 if(a == INVALID_FILE_ATTRIBUTES ||
67 (a & (FILE_ATTRIBUTE_DEVICE | FILE_ATTRIBUTE_DIRECTORY |
68 FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_OFFLINE)) != 0)
69 return 0;
70 printf("%s seems plausible\n", path);
71 /*
72 * I will look for a "bash.exe" adjacant to cygwin1.dll
73 */
74 strcpy(path + strlen(path) - strlen("cygwin1.dll"), "bash.exe");
75 printf("Now check %s\n", path);
76 a = GetFileAttributes(path);
77 if(a == INVALID_FILE_ATTRIBUTES ||
78 (a & (FILE_ATTRIBUTE_DEVICE | FILE_ATTRIBUTE_DIRECTORY |
79 FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_OFFLINE)) != 0)
80 return 0;
81 printf("%s seems plausible\n", path);
82 if(GetBinaryType(path, &a) == 0) return 0;
83 printf("Type = %d = %x\n", a, a);
84 switch(a)
85 { default: /* Not a useful type */
86 return 0;
87 case SCS_32BIT_BINARY:
88 printf("32-bit cygwin location identified\n");
89 wname = winname;
90 break;
91 case SCS_64BIT_BINARY:
92 printf("64-bit cygwin location identified\n");
93 wname = winname64;
94 break;
95 }
96 sprintf(path + strlen(path) - strlen("/bin/bash.exe"),
97 "/usr/local/bin/%s", cygname);
98 DeleteFile(path); /* delete before trying to create it */
99 if(wname != NULL)
100 { f = fopen(path, "wb");
101 if(f == NULL)
102 { printf("Unable to write to %s\n", path);
103 return 1;
104 }
105 fwrite("!<symlink>", 1, 10, f);
106 putc(0xff, f);
107 putc(0xfe, f);
108 /*
109 * Here I have to do two things. One is to convert the Windows name to
110 * on in cygwin format.
111 */
112 for(p = "/cygdrive/"; *p != 0; p++)
113 { putc(*p, f);
114 putc(0, f);
115 }
116 p = wname; // Expected to be of form "x:\..."
117 putc(*p++, f);
118 putc(0, f);
119 p++; // Skip the ":"
120 while(*p != 0)
121 { int c = *p++;
122 if(c == '\\') c = '/';
123 putc(c, f);
124 putc(0, f);
125 }
126 putc(0, f);
127 putc(0, f);
128 fclose(f);
129 SetFileAttributes(path, FILE_ATTRIBUTE_SYSTEM);
130 }
131 return 0;
132 }
133
processDrive(const char * drive)134 int processDrive(const char *drive)
135 { WIN32_FIND_DATA data;
136 HANDLE h;
137 char name[256];
138 printf("Look in %s\n", drive);
139 if(strlen(drive) > sizeof(name) - 16) return 1;
140 /*
141 * It seems that I can only put wildcards in the final component of
142 * the path given to FindFirstFile, so the pattern I sort of wanted to
143 * use, viz "x:\cyg*\bin\cygwin1.dll" is not acceptable. I need to find
144 * directories that match "x:\cyg*" and then check within them as a
145 * separate step.
146 */
147 sprintf(name, "%scyg*", drive);
148 printf("Pattern is %s\n", name);
149 memset(&data, 0, sizeof(data));
150 h = FindFirstFile(name, &data);
151 if(h != INVALID_HANDLE_VALUE)
152 { for(;;)
153 { if((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0)
154 { char path[256];
155 sprintf(path, "%s%s\\bin\\cygwin1.dll", drive, data.cFileName);
156 if(processFile(path)) return 1;
157 }
158 if(!FindNextFile(h, &data)) break;
159 }
160 FindClose(h);
161 }
162 return 0;
163 }
164
processVolume(const char * volumeName)165 int processVolume(const char *volumeName)
166 { DWORD len = (DWORD)strlen(volumeName);
167 char pathNames[4096], *p;
168 if(strncmp(volumeName, "\\\\?\\", 4) != 0 ||
169 volumeName[len - 1] != '\\')
170 { fprintf(stderr, "Invalid volume name (%s) returned\n",
171 volumeName);
172 return 1;
173 }
174 /*
175 * A single volume may potentially have several paths mapped onto it, and
176 * GetVolumePathNamesForVolumeName recovers all of them, concatenated as
177 * one long string terminated with a double zero. The call here could fail
178 * if the total length of all such paths exceeded the size of the buffer
179 * I provide. I think that I would view that as a seriously unusual
180 * situation, and I am not going to worry about it.
181 */
182 if(!GetVolumePathNamesForVolumeName(volumeName, pathNames,
183 (DWORD)sizeof(pathNames), &len))
184 { printf("Unable to get volume path names\n");
185 return 1;
186 }
187
188 for(p = pathNames; *p != 0; p += strlen(p) + 1)
189 { if(processDrive(p)) return 1;
190 }
191 return 0;
192 }
193
main(int argc,char * argv[])194 int main(int argc, char *argv[])
195 { char volumeName[100];
196 // When I am ready to abandom support for systeme prior to Windows 7
197 // I ccan use SetThreadErrorMode.
198 // SetThreadErrorMode(SEM_FAILCRITICALERRORS, NULL);
199 // This call is necessary so that (eg) having an SD card reader without an
200 // SD card in it does not stall the installation and lead to failure.
201 SetErrorMode(SEM_FAILCRITICALERRORS);
202 HANDLE v;
203 #if 0
204 { int i;
205 FILE *o = fopen("c:\\symlink.log", "w");
206 fprintf(o, "make-cygwin-symlink %d\n", argc);
207 for (i=0; i<argc; i++) fprintf(o, "%d) \"%s\"\n", i, argv[i]);
208 fclose(o);
209 }
210 #endif
211 if((argc != 2 && argc != 3 && argc != 4) ||
212 strncmp(argv[1], "--h", 3) == 0)
213 { printf("[%d] Usage:\n", argc);
214 printf(" make-cygwin-symlink cygname\n");
215 printf("Delete /usr/local/bin/cygname in all cywgin installations\n");
216 printf(" make-cygwin-symlink winname cygname\n");
217 printf("or make-cygwin-symlink winname winname64 cygname\n");
218 printf("e.g. make-cygwin-symlink \"c:/Program "
219 "Files/reduce/csl-reduce/reduce.exe\" redcsl\n");
220 printf("This establishes cygwin soft links in the cygwin "
221 "/usr/local/bin\n");
222 printf("directory to point to the specified executable\n");
223 printf("Rather like\n");
224 printf("\"ln -s '/cygdrive/c/Program "
225 "Files/reduce/csl-reduce/reduce.exe' \\\n");
226 printf(" /usr/local/bin/redcsl\"\n");
227 printf("but running as a native windows task not a cygwin one. It\n");
228 printf("identifes all the cygwin installations that it can find "
229 "in\n");
230 printf("directories whose name is of the form \"x:\\cyg*\"\n");
231 return 0;
232 }
233 /*
234 * Note that the Windows name should be a fully rooted name starting with
235 * drive information and it should explicitly include the ".exe" suffix
236 * that the executable has: eg "C:\path\subpath\executable.exe" while the
237 * cygname should be a plain word with no suffix and no embedded slashes
238 * or other unusual characters.
239 */
240 switch(argc)
241 { case 2:
242 winname = NULL; /* NULL used to indicate the "delete" option */
243 winname64 = NULL;
244 cygname = argv[1];
245 break;
246 case 3:
247 winname = argv[1];
248 winname64 = argv[1];
249 cygname = argv[2];
250 break;
251 case 4:
252 winname = argv[1];
253 winname64 = argv[2];
254 cygname = argv[3];
255 break;
256 }
257
258 v = FindFirstVolume(volumeName, (DWORD)sizeof(volumeName));
259 if(v == INVALID_HANDLE_VALUE)
260 { fprintf(stderr, "No volumes found\n");
261 return 1;
262 }
263 for(;;)
264 { if(processVolume(volumeName)) return 1;
265 if(!FindNextVolume(v, volumeName, (DWORD)sizeof(volumeName)))
266 { if(GetLastError() == ERROR_NO_MORE_FILES) return 0;
267 fprintf(stderr, "FindNextVolume failed\n");
268 return 1;
269 }
270 }
271 }
272
273 /* end of make-cygwin-symlink.c */
274