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 
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 
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 
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     }
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 
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
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 }