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 
73 string sysErrorString(
74     DWORD errCode,
75     // MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT) is the user's default language
76     int langId = LANG_NEUTRAL,
77     int subLangId = SUBLANG_DEFAULT) @trusted
78 {
79     auto buf = appender!string();
80 
81     wenforce(
82         // Ignore unlikely UTF decoding errors, always report the actual error (`errCode`)
83         putSysError(errCode, buf, MAKELANGID(langId, subLangId)).ifThrown(false),
84         text("Could not fetch error string for WinAPI code ", errCode)
85     );
86 
87     return buf.data;
88 }
89 
90 @safe unittest
91 {
92     import std.algorithm.searching;
93 
94     assert(sysErrorString(ERROR_PATH_NOT_FOUND) !is null);
95 
96     const msg = collectExceptionMsg!WindowsException(sysErrorString(DWORD.max));
97     assert(msg.startsWith(`Could not fetch error string for WinAPI code 4294967295: `));
98 }
99 
100 bool putSysError(Writer)(DWORD code, Writer w, /*WORD*/int langId = 0)
101 {
102     wchar *lpMsgBuf = null;
103     auto res = FormatMessageW(
104         FORMAT_MESSAGE_ALLOCATE_BUFFER |
105         FORMAT_MESSAGE_FROM_SYSTEM |
106         FORMAT_MESSAGE_IGNORE_INSERTS,
107         null,
108         code,
109         langId,
110         cast(LPWSTR)&lpMsgBuf,
111         0,
112         null);
113     scope(exit) if (lpMsgBuf) LocalFree(lpMsgBuf);
114 
115     if (lpMsgBuf)
116     {
117         import std.string : strip;
118         w.put(lpMsgBuf[0 .. res].strip());
119         return true;
120     }
121     else
122         return false;
123 }
124 
125 class WindowsException : Exception
126 {
127     import core.sys.windows.windef : DWORD;
128 
129     final @property DWORD code() { return _code; } /// `GetLastError`'s return value.
130     private DWORD _code;
131 
132     this(DWORD code, string str=null, string file = null, size_t line = 0) nothrow @trusted
133     {
134         _code = code;
135 
136         auto buf = appender!(char[]);
137 
138         if (str != null)
139         {
140             buf.put(str);
141             if (code)
142                 buf.put(": ");
143         }
144 
145         if (code && writeErrorMessage(code, buf))
146         {
147             buf.put(" (error ");
148             toTextRange(code, buf);
149             buf.put(')');
150         }
151 
152         super(cast(immutable) buf.data, file, line);
153     }
154 }
155 
156 /// Writes the error string associated to `code` into `buf`.
157 /// Writes `Error <code>` when the error message lookup fails
158 private bool writeErrorMessage(DWORD code, ref Appender!(char[]) buf) nothrow
159 {
160     bool success;
161     try
162     {
163         // Reset the buffer to undo partial changes
164         const len = buf[].length;
165         scope (failure) buf.shrinkTo(len);
166 
167         success = putSysError(code, buf);
168     }
169     catch (Exception) {}
170 
171     // Write the error code instead if we couldn't find the string
172     if (!success)
173     {
174         buf.put("Error ");
175         toTextRange(code, buf);
176     }
177 
178     return success;
179 }
180 
181 T wenforce(T, S = string)(T value, lazy S msg = null,
182 string file = __FILE__, size_t line = __LINE__)
183 if (isSomeString!S)
184 {
185     if (!value)
186         throw new WindowsException(GetLastError(), to!string(msg), file, line);
187     return value;
188 }
189 
190 T wenforce(T)(T condition, const(char)[] name, const(wchar)* namez, string file = __FILE__, size_t line = __LINE__)
191 {
192     if (condition)
193         return condition;
194     string names;
195     if (!name)
196     {
197         static string trustedToString(const(wchar)* stringz) @trusted
198         {
199             import core.stdc.wchar_ : wcslen;
200             import std.conv : to;
201             auto len = wcslen(stringz);
202             return to!string(stringz[0 .. len]);
203         }
204 
205         names = trustedToString(namez);
206     }
207     else
208         names = to!string(name);
209     throw new WindowsException(GetLastError(), names, file, line);
210 }
211 
212 @system unittest
213 {
214     import std.algorithm.searching : startsWith, endsWith;
215     import std.string;
216 
217     auto e = collectException!WindowsException(
218         DeleteFileA("unexisting.txt").wenforce("DeleteFile")
219     );
220     assert(e.code == ERROR_FILE_NOT_FOUND);
221     assert(e.msg.startsWith("DeleteFile: "));
222     // can't test the entire message, as it depends on Windows locale
223     assert(e.msg.endsWith(" (error 2)"));
224 
225     // Test code zero
226     e = new WindowsException(0);
227     assert(e.msg == "");
228 
229     e = new WindowsException(0, "Test");
230     assert(e.msg == "Test");
231 }
232 
233 @safe nothrow unittest
234 {
235     import std.algorithm.searching : endsWith;
236 
237     auto e = new WindowsException(ERROR_FILE_NOT_FOUND);
238     assert(e.msg.endsWith("(error 2)"));
239 
240     e = new WindowsException(DWORD.max);
241     assert(e.msg == "Error 4294967295");
242 }
243 
244 /// Tries to translate an error code from the Windows API to the corresponding
245 /// error message. Returns `Error <code>` on failure
246 package (std) string generateSysErrorMsg(DWORD errCode = GetLastError()) nothrow @trusted
247 {
248     auto buf = appender!(char[]);
249     cast(void) writeErrorMessage(errCode, buf);
250     return cast(immutable) buf[];
251 }
252 
253 nothrow @safe unittest
254 {
255     assert(generateSysErrorMsg(ERROR_PATH_NOT_FOUND) !is null);
256     assert(generateSysErrorMsg(DWORD.max) == "Error 4294967295");
257 }