attrib.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. /*
  2. * ATTRIB - Wine-compatible attrib program
  3. *
  4. * Copyright 2010-2012 Christian Costa
  5. *
  6. * This library is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 2.1 of the License, or (at your option) any later version.
  10. *
  11. * This library is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public
  17. * License along with this library; if not, write to the Free Software
  18. * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
  19. */
  20. #include <windows.h>
  21. #include <wine/debug.h>
  22. #include "attrib.h"
  23. WINE_DEFAULT_DEBUG_CHANNEL(attrib);
  24. /* =========================================================================
  25. * Load a string from the resource file, handling any error
  26. * Returns string retrieved from resource file
  27. * ========================================================================= */
  28. static WCHAR *ATTRIB_LoadMessage(UINT id)
  29. {
  30. static WCHAR msg[MAXSTRING];
  31. if (!LoadStringW(GetModuleHandleW(NULL), id, msg, ARRAY_SIZE(msg))) {
  32. WINE_FIXME("LoadString failed with %ld\n", GetLastError());
  33. lstrcpyW(msg, L"Failed!");
  34. }
  35. return msg;
  36. }
  37. /* =========================================================================
  38. * Output a formatted unicode string. Ideally this will go to the console
  39. * and hence required WriteConsoleW to output it, however if file i/o is
  40. * redirected, it needs to be WriteFile'd using OEM (not ANSI) format
  41. * ========================================================================= */
  42. static int WINAPIV ATTRIB_wprintf(const WCHAR *format, ...)
  43. {
  44. static WCHAR *output_bufW = NULL;
  45. static char *output_bufA = NULL;
  46. static BOOL toConsole = TRUE;
  47. static BOOL traceOutput = FALSE;
  48. #define MAX_WRITECONSOLE_SIZE 65535
  49. va_list parms;
  50. DWORD nOut;
  51. int len;
  52. DWORD res = 0;
  53. /*
  54. * Allocate buffer to use when writing to console
  55. * Note: Not freed - memory will be allocated once and released when
  56. * attrib ends
  57. */
  58. if (!output_bufW) output_bufW = HeapAlloc(GetProcessHeap(), 0,
  59. MAX_WRITECONSOLE_SIZE*sizeof(WCHAR));
  60. if (!output_bufW) {
  61. WINE_FIXME("Out of memory - could not allocate 2 x 64 KB buffers\n");
  62. return 0;
  63. }
  64. va_start(parms, format);
  65. len = FormatMessageW(FORMAT_MESSAGE_FROM_STRING, format, 0, 0, output_bufW,
  66. MAX_WRITECONSOLE_SIZE/sizeof(*output_bufW), &parms);
  67. va_end(parms);
  68. if (len == 0 && GetLastError() != ERROR_NO_WORK_DONE) {
  69. WINE_FIXME("Could not format string: le=%lu, fmt=%s\n", GetLastError(), wine_dbgstr_w(format));
  70. return 0;
  71. }
  72. /* Try to write as unicode all the time we think it's a console */
  73. if (toConsole) {
  74. res = WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE),
  75. output_bufW, len, &nOut, NULL);
  76. }
  77. /* If writing to console has failed (ever) we assume it's file
  78. i/o so convert to OEM codepage and output */
  79. if (!res) {
  80. BOOL usedDefaultChar = FALSE;
  81. DWORD convertedChars;
  82. toConsole = FALSE;
  83. /*
  84. * Allocate buffer to use when writing to file. Not freed, as above
  85. */
  86. if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
  87. MAX_WRITECONSOLE_SIZE);
  88. if (!output_bufA) {
  89. WINE_FIXME("Out of memory - could not allocate 2 x 64 KB buffers\n");
  90. return 0;
  91. }
  92. /* Convert to OEM, then output */
  93. convertedChars = WideCharToMultiByte(GetOEMCP(), 0, output_bufW,
  94. len, output_bufA, MAX_WRITECONSOLE_SIZE,
  95. "?", &usedDefaultChar);
  96. WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), output_bufA, convertedChars,
  97. &nOut, FALSE);
  98. }
  99. /* Trace whether screen or console */
  100. if (!traceOutput) {
  101. WINE_TRACE("Writing to console? (%d)\n", toConsole);
  102. traceOutput = TRUE;
  103. }
  104. return nOut;
  105. }
  106. /* =========================================================================
  107. * Handle the processing for a single directory, optionally recursing into
  108. * subdirectories if needed.
  109. * Parameters:
  110. * rootdir [I] The directory to search in
  111. * filespec [I] The filespec to search for
  112. * recurse [I] Whether to recurse (search subdirectories before
  113. * current directory)
  114. * includedirs [I] Whether to set directory attributes as well
  115. * attrib_set [I] Attributes to set
  116. * attrib_clear [I] Attributes to clear
  117. *
  118. * Returns TRUE if at least one file displayed / modified
  119. * ========================================================================= */
  120. static BOOL ATTRIB_processdirectory(const WCHAR *rootdir, const WCHAR *filespec,
  121. BOOL recurse, BOOL includedirs,
  122. DWORD attrib_set, DWORD attrib_clear)
  123. {
  124. BOOL found = FALSE;
  125. WCHAR buffer[MAX_PATH];
  126. HANDLE hff;
  127. WIN32_FIND_DATAW fd;
  128. WCHAR flags[] = L" ";
  129. WINE_TRACE("Processing dir '%s', spec '%s', %d,%lx,%lx\n",
  130. wine_dbgstr_w(rootdir), wine_dbgstr_w(filespec),
  131. recurse, attrib_set, attrib_clear);
  132. if (recurse) {
  133. /* Build spec to search for */
  134. lstrcpyW(buffer, rootdir);
  135. lstrcatW(buffer, L"*");
  136. /* Search for directories in the location and recurse if necessary */
  137. WINE_TRACE("Searching for directories with '%s'\n", wine_dbgstr_w(buffer));
  138. hff = FindFirstFileW(buffer, &fd);
  139. if (hff != INVALID_HANDLE_VALUE) {
  140. do {
  141. /* Only interested in directories, and not . nor .. */
  142. if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ||
  143. !lstrcmpW(fd.cFileName, L".") || !lstrcmpW(fd.cFileName, L".."))
  144. continue;
  145. /* Build new root dir to go searching in */
  146. lstrcpyW(buffer, rootdir);
  147. lstrcatW(buffer, fd.cFileName);
  148. lstrcatW(buffer, L"\\");
  149. ATTRIB_processdirectory(buffer, filespec, recurse, includedirs,
  150. attrib_set, attrib_clear);
  151. } while (FindNextFileW(hff, &fd) != 0);
  152. }
  153. FindClose (hff);
  154. }
  155. /* Build spec to search for */
  156. lstrcpyW(buffer, rootdir);
  157. lstrcatW(buffer, filespec);
  158. WINE_TRACE("Searching for files as '%s'\n", wine_dbgstr_w(buffer));
  159. /* Search for files in the location with the filespec supplied */
  160. hff = FindFirstFileW(buffer, &fd);
  161. if (hff != INVALID_HANDLE_VALUE) {
  162. do {
  163. DWORD count;
  164. WINE_TRACE("Found '%s'\n", wine_dbgstr_w(fd.cFileName));
  165. if (!lstrcmpW(fd.cFileName, L".") || !lstrcmpW(fd.cFileName, L".."))
  166. continue;
  167. if (!includedirs && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
  168. continue;
  169. if (attrib_set || attrib_clear) {
  170. fd.dwFileAttributes &= ~attrib_clear;
  171. fd.dwFileAttributes |= attrib_set;
  172. if (!fd.dwFileAttributes)
  173. fd.dwFileAttributes |= FILE_ATTRIBUTE_NORMAL;
  174. lstrcpyW(buffer, rootdir);
  175. lstrcatW(buffer, fd.cFileName);
  176. SetFileAttributesW(buffer, fd.dwFileAttributes);
  177. found = TRUE;
  178. } else {
  179. if (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {
  180. flags[4] = 'H';
  181. }
  182. if (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) {
  183. flags[1] = 'S';
  184. }
  185. if (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) {
  186. flags[0] = 'A';
  187. }
  188. if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
  189. flags[5] = 'R';
  190. }
  191. if (fd.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) {
  192. flags[6] = 'T';
  193. }
  194. if (fd.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) {
  195. flags[7] = 'C';
  196. }
  197. lstrcpyW(buffer, rootdir);
  198. lstrcatW(buffer, fd.cFileName);
  199. ATTRIB_wprintf(L"%1 %2\n", flags, buffer);
  200. for (count = 0; count < (ARRAY_SIZE(flags) - 1); count++) flags[count] = ' ';
  201. found = TRUE;
  202. }
  203. } while (FindNextFileW(hff, &fd) != 0);
  204. }
  205. FindClose (hff);
  206. return found;
  207. }
  208. int __cdecl wmain(int argc, WCHAR *argv[])
  209. {
  210. WCHAR name[MAX_PATH];
  211. WCHAR *namepart;
  212. WCHAR curdir[MAX_PATH];
  213. WCHAR originalname[MAX_PATH];
  214. DWORD attrib_set = 0;
  215. DWORD attrib_clear = 0;
  216. BOOL attrib_recurse = FALSE;
  217. BOOL attrib_includedirs = FALSE;
  218. int i = 1;
  219. BOOL found = FALSE;
  220. if ((argc >= 2) && !lstrcmpW(argv[1], L"/?")) {
  221. ATTRIB_wprintf(ATTRIB_LoadMessage(STRING_HELP));
  222. return 0;
  223. }
  224. /* By default all files from current directory are taken into account */
  225. lstrcpyW(originalname, L".\\*");
  226. while (i < argc) {
  227. WCHAR *param = argv[i++];
  228. WINE_TRACE("Processing arg: '%s'\n", wine_dbgstr_w(param));
  229. if ((param[0] == '+') || (param[0] == '-')) {
  230. DWORD attrib = 0;
  231. switch (param[1]) {
  232. case 'H': case 'h': attrib |= FILE_ATTRIBUTE_HIDDEN; break;
  233. case 'S': case 's': attrib |= FILE_ATTRIBUTE_SYSTEM; break;
  234. case 'R': case 'r': attrib |= FILE_ATTRIBUTE_READONLY; break;
  235. case 'A': case 'a': attrib |= FILE_ATTRIBUTE_ARCHIVE; break;
  236. default:
  237. ATTRIB_wprintf(ATTRIB_LoadMessage(STRING_NYI));
  238. return 0;
  239. }
  240. switch (param[0]) {
  241. case '+': attrib_set = attrib; break;
  242. case '-': attrib_clear = attrib; break;
  243. }
  244. } else if (param[0] == '/') {
  245. if (((param[1] == 'D') || (param[1] == 'd')) && !param[2]) {
  246. attrib_includedirs = TRUE;
  247. } else if (((param[1] == 'S') || (param[1] == 's')) && !param[2]) {
  248. attrib_recurse = TRUE;
  249. } else {
  250. WINE_FIXME("Unknown option %s\n", debugstr_w(param));
  251. }
  252. } else if (param[0]) {
  253. lstrcpyW(originalname, param);
  254. }
  255. }
  256. /* Name may be a relative or explicit path, so calculate curdir based on
  257. current locations, stripping off the filename */
  258. WINE_TRACE("Supplied name: '%s'\n", wine_dbgstr_w(originalname));
  259. GetFullPathNameW(originalname, ARRAY_SIZE(curdir), curdir, &namepart);
  260. WINE_TRACE("Result: '%s'\n", wine_dbgstr_w(curdir));
  261. if (namepart) {
  262. lstrcpyW(name, namepart);
  263. *namepart = 0;
  264. } else {
  265. name[0] = 0;
  266. }
  267. /* If a directory is explicitly supplied on the command line, and no
  268. wildcards are in the name, then allow it to be changed/displayed */
  269. if (wcspbrk(originalname, L"*?") == NULL) attrib_includedirs = TRUE;
  270. /* Do all the processing based on the filename arg */
  271. found = ATTRIB_processdirectory(curdir, name, attrib_recurse,
  272. attrib_includedirs, attrib_set, attrib_clear);
  273. if (!found) {
  274. ATTRIB_wprintf(ATTRIB_LoadMessage(STRING_FILENOTFOUND), originalname);
  275. }
  276. return 0;
  277. }