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(StdOut, 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(StdOut, L"%s -- %s\n", szMsg, szMessage);
85     else
86         ConPrintf(StdOut, 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         wcscpy(pszFileName, findData.cFileName);
149 
150         ConPrintf(StdOut,
151                   L"%c  %c%c%c     %s\n",
152                   (findData.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) ? L'A' : L' ',
153                   (findData.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) ? L'S' : L' ',
154                   (findData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) ? L'H' : L' ',
155                   (findData.dwFileAttributes & FILE_ATTRIBUTE_READONLY) ? L'R' : L' ',
156                   szFullName);
157     }
158     while(FindNextFileW(hFind, &findData));
159     FindClose(hFind);
160 
161     return 0;
162 }
163 
164 
165 static
166 BOOL
167 ChangeAttribute(
168     LPWSTR pszPath,
169     LPWSTR pszFile,
170     DWORD dwMask,
171     DWORD dwAttrib,
172     BOOL bRecurse,
173     BOOL bDirectories)
174 {
175     WIN32_FIND_DATAW findData;
176     HANDLE hFind;
177     DWORD  dwAttribute;
178     WCHAR  szFullName[MAX_PATH];
179     LPWSTR pszFileName;
180     BOOL bWildcard = (wcschr(pszFile, L'*') || wcschr(pszFile, L'?'));
181 
182     /* prepare full file name buffer */
183     wcscpy(szFullName, pszPath);
184     pszFileName = szFullName + wcslen(szFullName);
185 
186     /* append file name */
187     wcscpy(pszFileName, pszFile);
188 
189     hFind = FindFirstFileW(szFullName, &findData);
190     if (hFind == INVALID_HANDLE_VALUE)
191         return FALSE;
192 
193     dwAttribute = findData.dwFileAttributes;
194 
195     if (!bWildcard)
196     {
197         FindClose(hFind);
198         if (dwAttribute & FILE_ATTRIBUTE_DIRECTORY)
199         {
200             dwAttribute = (dwAttribute & ~dwMask) | dwAttrib;
201             SetFileAttributes(szFullName, dwAttribute);
202             if (bRecurse)
203             {
204                 if (bDirectories)
205                 {
206                     ChangeAttribute(szFullName, L"*", dwMask, dwAttrib,
207                                     bRecurse, bDirectories);
208                 }
209                 else
210                 {
211                     if (!ChangeAttribute(szFullName, L"*", dwMask, dwAttrib,
212                                          bRecurse, FALSE))
213                     {
214                         return FALSE;
215                     }
216                 }
217             }
218             else
219             {
220                 if (!bDirectories)
221                 {
222                     ChangeAttribute(szFullName, L"*", dwMask, dwAttrib,
223                                     bRecurse, FALSE);
224                 }
225             }
226             return TRUE;
227         }
228         else
229         {
230             dwAttribute = (dwAttribute & ~dwMask) | dwAttrib;
231             SetFileAttributes(szFullName, dwAttribute);
232             return TRUE;
233         }
234     }
235     else
236     {
237         if ((dwAttribute & FILE_ATTRIBUTE_DIRECTORY) && (!bRecurse || !bDirectories))
238             return FALSE;
239 
240         do
241         {
242             dwAttribute = findData.dwFileAttributes;
243             if (dwAttribute & FILE_ATTRIBUTE_DIRECTORY)
244             {
245                 if (!bDirectories)
246                     continue;
247 
248                 if (!wcscmp(findData.cFileName, L".") ||
249                     !wcscmp(findData.cFileName, L".."))
250                     continue;
251 
252                 wcscpy(pszFileName, findData.cFileName);
253                 dwAttribute = (dwAttribute & ~dwMask) | dwAttrib;
254                 SetFileAttributes(szFullName, dwAttribute);
255 
256                 if (bRecurse)
257                 {
258                     ChangeAttribute(szFullName, findData.cFileName, dwMask,
259                                     dwAttrib, bRecurse, FALSE);
260                 }
261             }
262             else
263             {
264                 wcscpy(pszFileName, findData.cFileName);
265                 dwAttribute = (dwAttribute & ~dwMask) | dwAttrib;
266                 SetFileAttributes(szFullName, dwAttribute);
267             }
268         } while (FindNextFileW(hFind, &findData));
269 
270         FindClose(hFind);
271     }
272 
273     return TRUE;
274 }
275 
276 int wmain(int argc, WCHAR *argv[])
277 {
278     INT    i;
279     WCHAR  szPath[MAX_PATH];
280     WCHAR  szFileName [MAX_PATH];
281     BOOL   bRecurse = FALSE;
282     BOOL   bDirectories = FALSE;
283     DWORD  dwAttrib = 0;
284     DWORD  dwMask = 0;
285     LPWSTR p;
286 
287     /* Initialize the Console Standard Streams */
288     ConInitStdStreams();
289 
290     /* Print help */
291     if (argc > 1 && wcscmp(argv[1], L"/?") == 0)
292     {
293         ConResPuts(StdOut, STRING_ATTRIB_HELP);
294         return 0;
295     }
296 
297     /* check for options */
298     for (i = 1; i < argc; i++)
299     {
300         if (wcsicmp(argv[i], L"/s") == 0)
301             bRecurse = TRUE;
302         else if (wcsicmp(argv[i], L"/d") == 0)
303             bDirectories = TRUE;
304     }
305 
306     /* create attributes and mask */
307     for (i = 1; i < argc; i++)
308     {
309         if (*argv[i] == L'+')
310         {
311             if (wcslen(argv[i]) != 2)
312             {
313                 ConResPrintf(StdOut, STRING_ERROR_INVALID_PARAM_FORMAT, argv[i]);
314                 return -1;
315             }
316 
317             switch (towupper(argv[i][1]))
318             {
319                 case L'A':
320                     dwMask   |= FILE_ATTRIBUTE_ARCHIVE;
321                     dwAttrib |= FILE_ATTRIBUTE_ARCHIVE;
322                     break;
323 
324                 case L'H':
325                     dwMask   |= FILE_ATTRIBUTE_HIDDEN;
326                     dwAttrib |= FILE_ATTRIBUTE_HIDDEN;
327                     break;
328 
329                 case L'R':
330                     dwMask   |= FILE_ATTRIBUTE_READONLY;
331                     dwAttrib |= FILE_ATTRIBUTE_READONLY;
332                     break;
333 
334                 case L'S':
335                     dwMask   |= FILE_ATTRIBUTE_SYSTEM;
336                     dwAttrib |= FILE_ATTRIBUTE_SYSTEM;
337                     break;
338 
339                 default:
340                     ConResPrintf(StdOut, STRING_ERROR_INVALID_PARAM_FORMAT, argv[i]);
341                     return -1;
342             }
343         }
344         else if (*argv[i] == L'-')
345         {
346             if (wcslen(argv[i]) != 2)
347             {
348                 ConResPrintf(StdOut, STRING_ERROR_INVALID_PARAM_FORMAT, argv[i]);
349                 return -1;
350             }
351 
352             switch (towupper(argv[i][1]))
353             {
354                 case L'A':
355                     dwMask   |= FILE_ATTRIBUTE_ARCHIVE;
356                     dwAttrib &= ~FILE_ATTRIBUTE_ARCHIVE;
357                     break;
358 
359                 case L'H':
360                     dwMask   |= FILE_ATTRIBUTE_HIDDEN;
361                     dwAttrib &= ~FILE_ATTRIBUTE_HIDDEN;
362                     break;
363 
364                 case L'R':
365                     dwMask   |= FILE_ATTRIBUTE_READONLY;
366                     dwAttrib &= ~FILE_ATTRIBUTE_READONLY;
367                     break;
368 
369                 case L'S':
370                     dwMask   |= FILE_ATTRIBUTE_SYSTEM;
371                     dwAttrib &= ~FILE_ATTRIBUTE_SYSTEM;
372                     break;
373 
374                 default:
375                     ConResPrintf(StdOut, STRING_ERROR_INVALID_PARAM_FORMAT, argv[i]);
376                     return -1;
377             }
378         }
379     }
380 
381     if (argc == 1)
382     {
383         DWORD len;
384 
385         len = GetCurrentDirectory(MAX_PATH, szPath);
386         if (szPath[len-1] != L'\\')
387         {
388             szPath[len] = L'\\';
389             szPath[len + 1] = UNICODE_NULL;
390         }
391         wcscpy(szFileName, L"*.*");
392         PrintAttribute(szPath, szFileName, bRecurse);
393         return 0;
394     }
395 
396     /* get full file name */
397     for (i = 1; i < argc; i++)
398     {
399         if (*argv[i] == L'+' || *argv[i] == L'-' || *argv[i] == L'/')
400             continue;
401 
402         GetFullPathNameW(argv[i], MAX_PATH, szPath, &p);
403         wcscpy(szFileName, p);
404         *p = 0;
405 
406         if (dwMask == 0)
407         {
408             PrintAttribute(szPath, szFileName, bRecurse);
409         }
410         else if (!ChangeAttribute(szPath, szFileName, dwMask,
411                                   dwAttrib, bRecurse, bDirectories))
412         {
413             ConResPrintf(StdOut, STRING_FILE_NOT_FOUND, argv[i]);
414         }
415     }
416 
417     return 0;
418 }
419