1 // Written in the D programming language.
2
3 /**
4 * Convert Win32 error code to string.
5 *
6 * Copyright: Copyright The D Language Foundation" 2006 - 2013.
7 * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
8 * Authors: $(HTTP digitalmars.com, Walter Bright)
9 * Credits: Based on code written by Regan Heath
10 */
11 /* Copyright The D Language Foundation" 2006 - 2013.
12 * Distributed under the Boost Software License, Version 1.0.
13 * (See accompanying file LICENSE_1_0.txt or copy at
14 * http://www.boost.org/LICENSE_1_0.txt)
15 */
16 module std.windows.syserror;
17 import std.traits : isSomeString;
18
version(StdDdoc)19 version (StdDdoc)
20 {
21 private
22 {
23 alias DWORD = uint;
24 enum LANG_NEUTRAL = 0, SUBLANG_DEFAULT = 1;
25 }
26
27 /** Query the text for a Windows error code, as returned by
28 $(LINK2 http://msdn.microsoft.com/en-us/library/windows/desktop/ms679360.aspx,
29 `GetLastError`), as a D string.
30 */
31 string sysErrorString(
32 DWORD errCode,
33 // MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT) is the user's default language
34 int langId = LANG_NEUTRAL,
35 int subLangId = SUBLANG_DEFAULT) @trusted;
36
37 /*********************
38 Thrown if errors that set
39 $(LINK2 http://msdn.microsoft.com/en-us/library/windows/desktop/ms679360.aspx,
40 `GetLastError`) occur.
41 */
42 class WindowsException : Exception
43 {
44 private alias DWORD = int;
45 final @property DWORD code(); /// `GetLastError`'s return value.
46 this(DWORD code, string str=null, string file = null, size_t line = 0) nothrow @trusted;
47 }
48
49 /++
50 If `!!value` is true, `value` is returned. Otherwise,
51 $(D new WindowsException(GetLastError(), msg)) is thrown.
52 `WindowsException` assumes that the last operation set
53 `GetLastError()` appropriately.
54
55 Example:
56 --------------------
57 wenforce(DeleteFileA("junk.tmp"), "DeleteFile failed");
58 --------------------
59 +/
60 T wenforce(T, S)(T value, lazy S msg = null,
61 string file = __FILE__, size_t line = __LINE__) @safe
62 if (isSomeString!S);
63 }
64 else:
65
66 version (Windows):
67
68 import core.sys.windows.winbase, core.sys.windows.winnt;
69 import std.array : appender, Appender;
70 import std.conv : to, toTextRange, text;
71 import std.exception;
72 import std.windows.charset;
73
74 string sysErrorString(
75 DWORD errCode,
76 // MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT) is the user's default language
77 int langId = LANG_NEUTRAL,
78 int subLangId = SUBLANG_DEFAULT) @trusted
79 {
80 auto buf = appender!string();
81
82 wenforce(
83 // Ignore unlikely UTF decoding errors, always report the actual error (`errCode`)
84 putSysError(errCode, buf, MAKELANGID(langId, subLangId)).ifThrown(false),
85 text("Could not fetch error string for WinAPI code ", errCode)
86 );
87
88 return buf.data;
89 }
90
91 @safe unittest
92 {
93 import std.algorithm.searching;
94
95 assert(sysErrorString(ERROR_PATH_NOT_FOUND) !is null);
96
97 const msg = collectExceptionMsg!WindowsException(sysErrorString(DWORD.max));
98 assert(msg.startsWith(`Could not fetch error string for WinAPI code 4294967295: `));
99 }
100
putSysError(Writer)101 bool putSysError(Writer)(DWORD code, Writer w, /*WORD*/int langId = 0)
102 {
103 wchar *lpMsgBuf = null;
104 auto res = FormatMessageW(
105 FORMAT_MESSAGE_ALLOCATE_BUFFER |
106 FORMAT_MESSAGE_FROM_SYSTEM |
107 FORMAT_MESSAGE_IGNORE_INSERTS,
108 null,
109 code,
110 langId,
111 cast(LPWSTR)&lpMsgBuf,
112 0,
113 null);
114 scope(exit) if (lpMsgBuf) LocalFree(lpMsgBuf);
115
116 if (lpMsgBuf)
117 {
118 import std.string : strip;
119 w.put(lpMsgBuf[0 .. res].strip());
120 return true;
121 }
122 else
123 return false;
124 }
125
126 class WindowsException : Exception
127 {
128 import core.sys.windows.windef : DWORD;
129
code()130 final @property DWORD code() { return _code; } /// `GetLastError`'s return value.
131 private DWORD _code;
132
133 this(DWORD code, string str=null, string file = null, size_t line = 0) nothrow @trusted
134 {
135 _code = code;
136
137 auto buf = appender!(char[]);
138
139 if (str != null)
140 {
141 buf.put(str);
142 if (code)
143 buf.put(": ");
144 }
145
146 if (code && writeErrorMessage(code, buf))
147 {
148 buf.put(" (error ");
149 toTextRange(code, buf);
150 buf.put(')');
151 }
152
153 super(cast(immutable) buf.data, file, line);
154 }
155 }
156
157 /// Writes the error string associated to `code` into `buf`.
158 /// Writes `Error <code>` when the error message lookup fails
159 private bool writeErrorMessage(DWORD code, ref Appender!(char[]) buf) nothrow
160 {
161 bool success;
162 try
163 {
164 // Reset the buffer to undo partial changes
165 const len = buf[].length;
166 scope (failure) buf.shrinkTo(len);
167
168 success = putSysError(code, buf);
169 }
catch(Exception)170 catch (Exception) {}
171
172 // Write the error code instead if we couldn't find the string
173 if (!success)
174 {
175 buf.put("Error ");
176 toTextRange(code, buf);
177 }
178
179 return success;
180 }
181
182 T wenforce(T, S = string)(T value, lazy S msg = null,
183 string file = __FILE__, size_t line = __LINE__)
184 if (isSomeString!S)
185 {
186 if (!value)
187 throw new WindowsException(GetLastError(), to!string(msg), file, line);
188 return value;
189 }
190
wenforce(T)191 T wenforce(T)(T condition, const(char)[] name, const(wchar)* namez, string file = __FILE__, size_t line = __LINE__)
192 {
193 if (condition)
194 return condition;
195 string names;
196 if (!name)
197 {
198 static string trustedToString(const(wchar)* stringz) @trusted
199 {
200 import core.stdc.wchar_ : wcslen;
201 import std.conv : to;
202 auto len = wcslen(stringz);
203 return to!string(stringz[0 .. len]);
204 }
205
206 names = trustedToString(namez);
207 }
208 else
209 names = to!string(name);
210 throw new WindowsException(GetLastError(), names, file, line);
211 }
212
213 @system unittest
214 {
215 import std.algorithm.searching : startsWith, endsWith;
216 import std.string;
217
218 auto e = collectException!WindowsException(
219 DeleteFileA("unexisting.txt").wenforce("DeleteFile")
220 );
221 assert(e.code == ERROR_FILE_NOT_FOUND);
222 assert(e.msg.startsWith("DeleteFile: "));
223 // can't test the entire message, as it depends on Windows locale
224 assert(e.msg.endsWith(" (error 2)"));
225
226 // Test code zero
227 e = new WindowsException(0);
228 assert(e.msg == "");
229
230 e = new WindowsException(0, "Test");
231 assert(e.msg == "Test");
232 }
233
234 @safe nothrow unittest
235 {
236 import std.algorithm.searching : endsWith;
237
238 auto e = new WindowsException(ERROR_FILE_NOT_FOUND);
239 assert(e.msg.endsWith("(error 2)"));
240
241 e = new WindowsException(DWORD.max);
242 assert(e.msg == "Error 4294967295");
243 }
244
245 /// Tries to translate an error code from the Windows API to the corresponding
246 /// error message. Returns `Error <code>` on failure
package(std)247 package (std) string generateSysErrorMsg(DWORD errCode = GetLastError()) nothrow @trusted
248 {
249 auto buf = appender!(char[]);
250 cast(void) writeErrorMessage(errCode, buf);
251 return cast(immutable) buf[];
252 }
253
254 nothrow @safe unittest
255 {
256 assert(generateSysErrorMsg(ERROR_PATH_NOT_FOUND) !is null);
257 assert(generateSysErrorMsg(DWORD.max) == "Error 4294967295");
258 }
259