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 /* Returns TRUE if anything is printed, FALSE otherwise */
90 static
91 BOOL
92 PrintAttribute(
93     LPWSTR pszPath,
94     LPWSTR pszFile,
95     BOOL   bRecurse,
96     BOOL   bDirectories)
97 {
98     WIN32_FIND_DATAW findData;
99     HANDLE hFind;
100     WCHAR  szFullName[MAX_PATH];
101     LPWSTR pszFileName;
102     BOOL   bFound = FALSE;
103     BOOL   bIsDir;
104     BOOL   bExactMatch;
105     DWORD  Error;
106 
107     /* prepare full file name buffer */
108     wcscpy(szFullName, pszPath);
109     pszFileName = szFullName + wcslen(szFullName);
110 
111     /* display all subdirectories */
112     if (bRecurse)
113     {
114         /* append *.* */
115         wcscpy(pszFileName, L"*.*");
116 
117         hFind = FindFirstFileW(szFullName, &findData);
118         if (hFind == INVALID_HANDLE_VALUE)
119         {
120             Error = GetLastError();
121             if ((Error != ERROR_DIRECTORY) && (Error != ERROR_SHARING_VIOLATION)
122                   && (Error != ERROR_FILE_NOT_FOUND))
123             {
124                 ErrorMessage(Error, pszFile);
125             }
126             return FALSE;
127         }
128 
129         do
130         {
131             if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
132                 continue;
133 
134             if (!wcscmp(findData.cFileName, L".") ||
135                 !wcscmp(findData.cFileName, L".."))
136             {
137                 continue;
138             }
139 
140             wcscpy(pszFileName, findData.cFileName);
141             wcscat(pszFileName, L"\\");
142             bFound |= PrintAttribute(szFullName, pszFile, bRecurse, bDirectories);
143         }
144         while (FindNextFileW(hFind, &findData));
145         FindClose(hFind);
146     }
147 
148     /* append file name */
149     wcscpy(pszFileName, pszFile);
150 
151     /* search current directory */
152     hFind = FindFirstFileW(szFullName, &findData);
153     if (hFind == INVALID_HANDLE_VALUE)
154     {
155         return bFound;
156     }
157 
158     do
159     {
160         bIsDir = findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
161         bExactMatch = wcsicmp(findData.cFileName, pszFile) == 0;
162 
163         if (bIsDir && !bDirectories && !bExactMatch)
164             continue;
165 
166         if (!wcscmp(findData.cFileName, L".") ||
167             !wcscmp(findData.cFileName, L".."))
168         {
169             continue;
170         }
171 
172         wcscpy(pszFileName, findData.cFileName);
173 
174         ConPrintf(StdOut,
175                   L"%c  %c%c%c     %s\n",
176                   (findData.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) ? L'A' : L' ',
177                   (findData.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) ? L'S' : L' ',
178                   (findData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) ? L'H' : L' ',
179                   (findData.dwFileAttributes & FILE_ATTRIBUTE_READONLY) ? L'R' : L' ',
180                   szFullName);
181         bFound = TRUE;
182     }
183     while (FindNextFileW(hFind, &findData));
184     FindClose(hFind);
185 
186     return bFound;
187 }
188 
189 
190 /* Returns TRUE if anything changed, FALSE otherwise */
191 static
192 BOOL
193 ChangeAttribute(
194     LPWSTR pszPath,
195     LPWSTR pszFile,
196     BOOL  bRecurse,
197     BOOL  bDirectories,
198     DWORD dwMask,
199     DWORD dwAttrib)
200 {
201     WIN32_FIND_DATAW findData;
202     HANDLE hFind;
203     WCHAR  szFullName[MAX_PATH];
204     LPWSTR pszFileName;
205     BOOL   bFound = FALSE;
206     BOOL   bIsDir;
207     BOOL   bExactMatch;
208     DWORD  dwAttribute;
209     DWORD  Error;
210 
211     /* prepare full file name buffer */
212     wcscpy(szFullName, pszPath);
213     pszFileName = szFullName + wcslen(szFullName);
214 
215     /* display all subdirectories */
216     if (bRecurse)
217     {
218         /* append *.* */
219         wcscpy(pszFileName, L"*.*");
220 
221         hFind = FindFirstFileW(szFullName, &findData);
222         if (hFind == INVALID_HANDLE_VALUE)
223         {
224             Error = GetLastError();
225             if ((Error != ERROR_DIRECTORY) && (Error != ERROR_SHARING_VIOLATION)
226                   && (Error != ERROR_FILE_NOT_FOUND))
227             {
228                 ErrorMessage(Error, pszFile);
229             }
230             return FALSE;
231         }
232 
233         do
234         {
235             if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
236                 continue;
237 
238             if (!wcscmp(findData.cFileName, L".") ||
239                 !wcscmp(findData.cFileName, L".."))
240             {
241                 continue;
242             }
243 
244             wcscpy(pszFileName, findData.cFileName);
245             wcscat(pszFileName, L"\\");
246             bFound |= ChangeAttribute(szFullName, pszFile, bRecurse, bDirectories,
247                                       dwMask, dwAttrib);
248         }
249         while (FindNextFileW(hFind, &findData));
250         FindClose(hFind);
251     }
252 
253     /* append file name */
254     wcscpy(pszFileName, pszFile);
255 
256     /* search current directory */
257     hFind = FindFirstFileW(szFullName, &findData);
258     if (hFind == INVALID_HANDLE_VALUE)
259     {
260         return bFound;
261     }
262 
263     do
264     {
265         bIsDir = findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
266         bExactMatch = wcsicmp(findData.cFileName, pszFile) == 0;
267 
268         if (bIsDir && !bDirectories && !bExactMatch)
269             continue;
270 
271         if (!wcscmp(findData.cFileName, L".") ||
272             !wcscmp(findData.cFileName, L".."))
273         {
274             continue;
275         }
276 
277         if (bRecurse && bIsDir && !bDirectories)
278             continue;
279 
280         wcscpy(pszFileName, findData.cFileName);
281 
282         dwAttribute = (findData.dwFileAttributes & ~dwMask) | dwAttrib;
283 
284         SetFileAttributes(szFullName, dwAttribute);
285         bFound = TRUE;
286     }
287     while (FindNextFileW(hFind, &findData));
288     FindClose(hFind);
289 
290     return bFound;
291 }
292 
293 int wmain(int argc, WCHAR *argv[])
294 {
295     INT    i;
296     WCHAR  szPath[MAX_PATH] = L""; // For case we only use 'attrib +h /s' there is no szPath
297     WCHAR  szFileName [MAX_PATH];
298     BOOL   bRecurse = FALSE;
299     BOOL   bDirectories = FALSE;
300     DWORD  dwAttrib = 0;
301     DWORD  dwMask = 0;
302     LPWSTR p;
303 
304     /* Initialize the Console Standard Streams */
305     ConInitStdStreams();
306 
307     /* Print help */
308     if (argc > 1 && wcscmp(argv[1], L"/?") == 0)
309     {
310         ConResPuts(StdOut, STRING_ATTRIB_HELP);
311         return 0;
312     }
313 
314     /* check for options */
315     for (i = 1; i < argc; i++)
316     {
317         if (wcsicmp(argv[i], L"/s") == 0)
318             bRecurse = TRUE;
319         else if (wcsicmp(argv[i], L"/d") == 0)
320             bDirectories = TRUE;
321     }
322 
323     /* create attributes and mask */
324     for (i = 1; i < argc; i++)
325     {
326         if (*argv[i] == L'+')
327         {
328             if (wcslen(argv[i]) != 2)
329             {
330                 ConResPrintf(StdOut, STRING_ERROR_INVALID_PARAM_FORMAT, argv[i]);
331                 return -1;
332             }
333 
334             switch (towupper(argv[i][1]))
335             {
336                 case L'A':
337                     dwMask   |= FILE_ATTRIBUTE_ARCHIVE;
338                     dwAttrib |= FILE_ATTRIBUTE_ARCHIVE;
339                     break;
340 
341                 case L'H':
342                     dwMask   |= FILE_ATTRIBUTE_HIDDEN;
343                     dwAttrib |= FILE_ATTRIBUTE_HIDDEN;
344                     break;
345 
346                 case L'R':
347                     dwMask   |= FILE_ATTRIBUTE_READONLY;
348                     dwAttrib |= FILE_ATTRIBUTE_READONLY;
349                     break;
350 
351                 case L'S':
352                     dwMask   |= FILE_ATTRIBUTE_SYSTEM;
353                     dwAttrib |= FILE_ATTRIBUTE_SYSTEM;
354                     break;
355 
356                 default:
357                     ConResPrintf(StdOut, STRING_ERROR_INVALID_PARAM_FORMAT, argv[i]);
358                     return -1;
359             }
360         }
361         else if (*argv[i] == L'-')
362         {
363             if (wcslen(argv[i]) != 2)
364             {
365                 ConResPrintf(StdOut, STRING_ERROR_INVALID_PARAM_FORMAT, argv[i]);
366                 return -1;
367             }
368 
369             switch (towupper(argv[i][1]))
370             {
371                 case L'A':
372                     dwMask   |= FILE_ATTRIBUTE_ARCHIVE;
373                     dwAttrib &= ~FILE_ATTRIBUTE_ARCHIVE;
374                     break;
375 
376                 case L'H':
377                     dwMask   |= FILE_ATTRIBUTE_HIDDEN;
378                     dwAttrib &= ~FILE_ATTRIBUTE_HIDDEN;
379                     break;
380 
381                 case L'R':
382                     dwMask   |= FILE_ATTRIBUTE_READONLY;
383                     dwAttrib &= ~FILE_ATTRIBUTE_READONLY;
384                     break;
385 
386                 case L'S':
387                     dwMask   |= FILE_ATTRIBUTE_SYSTEM;
388                     dwAttrib &= ~FILE_ATTRIBUTE_SYSTEM;
389                     break;
390 
391                 default:
392                     ConResPrintf(StdOut, STRING_ERROR_INVALID_PARAM_FORMAT, argv[i]);
393                     return -1;
394             }
395         }
396     }
397 
398     if (argc == 1)
399     {
400         DWORD len;
401 
402         len = GetCurrentDirectory(MAX_PATH, szPath);
403         if (szPath[len-1] != L'\\')
404         {
405             szPath[len] = L'\\';
406             szPath[len + 1] = UNICODE_NULL;
407         }
408         wcscpy(szFileName, L"*.*");
409         PrintAttribute(szPath, szFileName, bRecurse, bDirectories);
410         return 0;
411     }
412 
413     /* get full file name */
414     for (i = 1; i < argc; i++)
415     {
416         if (*argv[i] == L'+' || *argv[i] == L'-' || *argv[i] == L'/')
417             continue;
418 
419         GetFullPathNameW(argv[i], MAX_PATH, szPath, &p);
420         wcscpy(szFileName, p);
421         *p = 0;
422 
423         if (dwMask == 0)
424         {
425             if (!PrintAttribute(szPath, szFileName, bRecurse, bDirectories))
426             {
427                 ConResPrintf(StdOut, STRING_FILE_NOT_FOUND, argv[i]);
428             }
429         }
430         else if (!ChangeAttribute(szPath, szFileName, bRecurse, bDirectories, dwMask, dwAttrib))
431         {
432             ConResPrintf(StdOut, STRING_FILE_NOT_FOUND, argv[i]);
433         }
434     }
435 
436 // Code below handles the special case of 'attrib +h /s' and similar
437 
438     if (bRecurse && dwMask && (wcscmp(szPath, L"") == 0))
439     {
440         DWORD len;
441 
442         len = GetCurrentDirectory(MAX_PATH, szPath);
443         if (szPath[len-1] != L'\\')
444         {
445             szPath[len] = L'\\';
446             szPath[len + 1] = UNICODE_NULL;
447         }
448         wcscpy(szFileName, L"*.*");
449         if (!ChangeAttribute(szPath, szFileName, bRecurse, bDirectories, dwMask, dwAttrib))
450             ConResPrintf(StdOut, STRING_FILE_NOT_FOUND, szFileName);
451     }
452 
453     return 0;
454 }
455