1 /*
2  *  ATTRIB.C - attrib internal command.
3  *
4  *
5  *  History:
6  *
7  *    04-Dec-1998 Eric Kohl
8  *        started
9  *
10  *    09-Dec-1998 Eric Kohl
11  *        implementation works, except recursion ("attrib /s").
12  *
13  *    05-Jan-1999 Eric Kohl
14  *        major rewrite.
15  *        fixed recursion ("attrib /s").
16  *        started directory support ("attrib /s /d").
17  *        updated help text.
18  *
19  *    14-Jan-1999 Eric Kohl
20  *        Unicode ready!
21  *
22  *    19-Jan-1999 Eric Kohl
23  *        Redirection ready!
24  *
25  *    21-Jan-1999 Eric Kohl
26  *        Added check for invalid filenames.
27  *
28  *    23-Jan-1999 Eric Kohl
29  *        Added handling of multiple filenames.
30  *
31  *    02-Apr-2005 (Magnus Olsen <magnus@greatlord.com>)
32  *        Remove all hardcoded strings in En.rc
33  */
34 
35 #include <stdio.h>
36 #include <stdlib.h>
37 
38 #include <windef.h>
39 #include <winbase.h>
40 #include <wincon.h>
41 #include <winuser.h>
42 
43 #include <conutils.h>
44 
45 #include "resource.h"
46 
47 CON_SCREEN StdOutScreen = INIT_CON_SCREEN(StdOut);
48 
49 static
50 VOID
51 ErrorMessage(
52     DWORD dwErrorCode,
53     LPWSTR szFormat,
54     ...)
55 {
56     WCHAR szMsg[RC_STRING_MAX_SIZE];
57     WCHAR  szMessage[1024];
58     LPWSTR szError;
59     va_list arg_ptr;
60 
61     if (dwErrorCode == ERROR_SUCCESS)
62         return;
63 
64     if (szFormat)
65     {
66         va_start(arg_ptr, szFormat);
67         vswprintf(szMessage, szFormat, arg_ptr);
68         va_end(arg_ptr);
69     }
70 
71     if (FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER,
72                        NULL, dwErrorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
73                        (LPWSTR)&szError, 0, NULL))
74     {
75         ConPrintf(StdErr, L"%s %s\n", szError, szMessage);
76         if (szError)
77             LocalFree(szError);
78         return;
79     }
80 
81     /* Fall back just in case the error is not defined */
82     LoadStringW(GetModuleHandle(NULL), STRING_CONSOLE_ERROR, szMsg, ARRAYSIZE(szMsg));
83     if (szFormat)
84         ConPrintf(StdErr, L"%s -- %s\n", szMsg, szMessage);
85     else
86         ConPrintf(StdErr, L"%s\n", szMsg);
87 }
88 
89 static
90 INT
91 PrintAttribute(
92     LPWSTR pszPath,
93     LPWSTR pszFile,
94     BOOL bRecurse)
95 {
96     WIN32_FIND_DATAW findData;
97     HANDLE hFind;
98     WCHAR  szFullName[MAX_PATH];
99     LPWSTR pszFileName;
100 
101     /* prepare full file name buffer */
102     wcscpy(szFullName, pszPath);
103     pszFileName = szFullName + wcslen(szFullName);
104 
105     /* display all subdirectories */
106     if (bRecurse)
107     {
108         /* append file name */
109         wcscpy(pszFileName, pszFile);
110 
111         hFind = FindFirstFileW(szFullName, &findData);
112         if (hFind == INVALID_HANDLE_VALUE)
113         {
114             ErrorMessage(GetLastError(), pszFile);
115             return 1;
116         }
117 
118         do
119         {
120             if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
121                 continue;
122 
123             if (!wcscmp(findData.cFileName, L".") ||
124                 !wcscmp(findData.cFileName, L".."))
125                 continue;
126 
127             wcscpy(pszFileName, findData.cFileName);
128             wcscat(pszFileName, L"\\");
129             PrintAttribute(szFullName, pszFile, bRecurse);
130         }
131         while(FindNextFileW(hFind, &findData));
132         FindClose(hFind);
133     }
134 
135     /* append file name */
136     wcscpy(pszFileName, pszFile);
137 
138     /* display current directory */
139     hFind = FindFirstFileW(szFullName, &findData);
140     if (hFind == INVALID_HANDLE_VALUE)
141     {
142         ErrorMessage(GetLastError(), pszFile);
143         return 1;
144     }
145 
146     do
147     {
148         if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
149             continue;
150 
151         wcscpy(pszFileName, findData.cFileName);
152 
153         ConPrintf(StdOut,
154                   L"%c  %c%c%c     %s\n",
155                   (findData.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) ? L'A' : L' ',
156                   (findData.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) ? L'S' : L' ',
157                   (findData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) ? L'H' : L' ',
158                   (findData.dwFileAttributes & FILE_ATTRIBUTE_READONLY) ? L'R' : L' ',
159                   szFullName);
160     }
161     while(FindNextFileW(hFind, &findData));
162     FindClose(hFind);
163 
164     return 0;
165 }
166 
167 
168 static
169 INT
170 ChangeAttribute(
171     LPWSTR pszPath,
172     LPWSTR pszFile,
173     DWORD dwMask,
174     DWORD dwAttrib,
175     BOOL bRecurse,
176     BOOL bDirectories)
177 {
178     WIN32_FIND_DATAW findData;
179     HANDLE hFind;
180     DWORD  dwAttribute;
181     WCHAR  szFullName[MAX_PATH];
182     LPWSTR pszFileName;
183 
184     /* prepare full file name buffer */
185     wcscpy(szFullName, pszPath);
186     pszFileName = szFullName + wcslen(szFullName);
187 
188     /* change all subdirectories */
189     if (bRecurse)
190     {
191         /* append file name */
192         wcscpy(pszFileName, L"*.*");
193 
194         hFind = FindFirstFileW(szFullName, &findData);
195         if (hFind == INVALID_HANDLE_VALUE)
196         {
197             ErrorMessage(GetLastError(), pszFile);
198             return 1;
199         }
200 
201         do
202         {
203             if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
204             {
205                 if (!wcscmp(findData.cFileName, L".") ||
206                     !wcscmp(findData.cFileName, L".."))
207                     continue;
208 
209                 wcscpy(pszFileName, findData.cFileName);
210                 wcscat(pszFileName, L"\\");
211 
212                 ChangeAttribute(szFullName, pszFile, dwMask,
213                                 dwAttrib, bRecurse, bDirectories);
214             }
215         }
216         while (FindNextFileW(hFind, &findData));
217         FindClose(hFind);
218     }
219 
220     /* append file name */
221     wcscpy(pszFileName, pszFile);
222 
223     hFind = FindFirstFileW(szFullName, &findData);
224     if (hFind == INVALID_HANDLE_VALUE)
225     {
226         ErrorMessage(GetLastError(), pszFile);
227         return 1;
228     }
229 
230     do
231     {
232         if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
233             continue;
234 
235         wcscpy(pszFileName, findData.cFileName);
236 
237         dwAttribute = GetFileAttributes (szFullName);
238 
239         if (dwAttribute != 0xFFFFFFFF)
240         {
241             dwAttribute = (dwAttribute & ~dwMask) | dwAttrib;
242             SetFileAttributes(szFullName, dwAttribute);
243         }
244     }
245     while (FindNextFileW(hFind, &findData));
246     FindClose(hFind);
247 
248     return 0;
249 }
250 
251 
252 int wmain(int argc, WCHAR *argv[])
253 {
254     INT    i;
255     WCHAR  szPath[MAX_PATH];
256     WCHAR  szFileName [MAX_PATH];
257     BOOL   bRecurse = FALSE;
258     BOOL   bDirectories = FALSE;
259     DWORD  dwAttrib = 0;
260     DWORD  dwMask = 0;
261 
262     /* Initialize the Console Standard Streams */
263     ConInitStdStreams();
264 
265     /* Print help */
266     if (argc > 1 && wcscmp(argv[1], L"/?") == 0)
267     {
268         ConResPuts(StdOut, STRING_ATTRIB_HELP);
269         return 0;
270     }
271 
272     /* check for options */
273     for (i = 1; i < argc; i++)
274     {
275         if (wcsicmp(argv[i], L"/s") == 0)
276             bRecurse = TRUE;
277         else if (wcsicmp(argv[i], L"/d") == 0)
278             bDirectories = TRUE;
279     }
280 
281     /* create attributes and mask */
282     for (i = 1; i < argc; i++)
283     {
284         if (*argv[i] == L'+')
285         {
286             if (wcslen(argv[i]) != 2)
287             {
288                 ConResPrintf(StdErr, STRING_ERROR_INVALID_PARAM_FORMAT, argv[i]);
289                 return -1;
290             }
291 
292             switch (towupper(argv[i][1]))
293             {
294                 case L'A':
295                     dwMask   |= FILE_ATTRIBUTE_ARCHIVE;
296                     dwAttrib |= FILE_ATTRIBUTE_ARCHIVE;
297                     break;
298 
299                 case L'H':
300                     dwMask   |= FILE_ATTRIBUTE_HIDDEN;
301                     dwAttrib |= FILE_ATTRIBUTE_HIDDEN;
302                     break;
303 
304                 case L'R':
305                     dwMask   |= FILE_ATTRIBUTE_READONLY;
306                     dwAttrib |= FILE_ATTRIBUTE_READONLY;
307                     break;
308 
309                 case L'S':
310                     dwMask   |= FILE_ATTRIBUTE_SYSTEM;
311                     dwAttrib |= FILE_ATTRIBUTE_SYSTEM;
312                     break;
313 
314                 default:
315                     ConResPrintf(StdErr, STRING_ERROR_INVALID_PARAM_FORMAT, argv[i]);
316                     return -1;
317             }
318         }
319         else if (*argv[i] == L'-')
320         {
321             if (wcslen(argv[i]) != 2)
322             {
323                 ConResPrintf(StdErr, STRING_ERROR_INVALID_PARAM_FORMAT, argv[i]);
324                 return -1;
325             }
326 
327             switch (towupper(argv[i][1]))
328             {
329                 case L'A':
330                     dwMask   |= FILE_ATTRIBUTE_ARCHIVE;
331                     dwAttrib &= ~FILE_ATTRIBUTE_ARCHIVE;
332                     break;
333 
334                 case L'H':
335                     dwMask   |= FILE_ATTRIBUTE_HIDDEN;
336                     dwAttrib &= ~FILE_ATTRIBUTE_HIDDEN;
337                     break;
338 
339                 case L'R':
340                     dwMask   |= FILE_ATTRIBUTE_READONLY;
341                     dwAttrib &= ~FILE_ATTRIBUTE_READONLY;
342                     break;
343 
344                 case L'S':
345                     dwMask   |= FILE_ATTRIBUTE_SYSTEM;
346                     dwAttrib &= ~FILE_ATTRIBUTE_SYSTEM;
347                     break;
348 
349                 default:
350                     ConResPrintf(StdErr, STRING_ERROR_INVALID_PARAM_FORMAT, argv[i]);
351                     return -1;
352             }
353         }
354     }
355 
356     if (argc == 1)
357     {
358         DWORD len;
359 
360         len = GetCurrentDirectory(MAX_PATH, szPath);
361         if (szPath[len-1] != L'\\')
362         {
363             szPath[len] = L'\\';
364             szPath[len + 1] = UNICODE_NULL;
365         }
366         wcscpy(szFileName, L"*.*");
367         PrintAttribute(szPath, szFileName, bRecurse);
368         return 0;
369     }
370 
371     /* get full file name */
372     for (i = 1; i < argc; i++)
373     {
374         if ((*argv[i] != L'+') && (*argv[i] != L'-') && (*argv[i] != L'/'))
375         {
376             LPWSTR p;
377 
378             GetFullPathName(argv[i], MAX_PATH, szPath, NULL);
379             p = wcsrchr(szPath, L'\\') + 1;
380             wcscpy(szFileName, p);
381             *p = L'\0';
382 
383             if (dwMask == 0)
384                 PrintAttribute(szPath, szFileName, bRecurse);
385             else
386                 ChangeAttribute(szPath, szFileName, dwMask,
387                          dwAttrib, bRecurse, bDirectories);
388         }
389     }
390 
391     return 0;
392 }
393