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