| /* |
| * Task Scheduler Service |
| * |
| * Copyright 2014 Dmitry Timoshkov |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
| */ |
| |
| #include <stdarg.h> |
| |
| #include "windef.h" |
| #include "schrpc.h" |
| #include "taskschd.h" |
| #include "wine/debug.h" |
| |
| #include "schedsvc_private.h" |
| |
| WINE_DEFAULT_DEBUG_CHANNEL(schedsvc); |
| |
| static const char bom_utf8[] = { 0xef,0xbb,0xbf }; |
| |
| HRESULT __cdecl SchRpcHighestVersion(DWORD *version) |
| { |
| TRACE("%p\n", version); |
| |
| *version = MAKELONG(3, 1); |
| return S_OK; |
| } |
| |
| static WCHAR *get_full_name(const WCHAR *path, WCHAR **relative_path) |
| { |
| static const WCHAR tasksW[] = { '\\','t','a','s','k','s','\\',0 }; |
| WCHAR *target; |
| int len; |
| |
| len = GetSystemDirectoryW(NULL, 0); |
| len += strlenW(tasksW) + strlenW(path); |
| |
| target = heap_alloc(len * sizeof(WCHAR)); |
| if (target) |
| { |
| GetSystemDirectoryW(target, len); |
| strcatW(target, tasksW); |
| if (relative_path) |
| *relative_path = target + strlenW(target) - 1; |
| while (*path == '\\') path++; |
| strcatW(target, path); |
| } |
| return target; |
| } |
| |
| /* |
| * Recursively create all directories in the path. |
| */ |
| static HRESULT create_directory(const WCHAR *path) |
| { |
| HRESULT hr = S_OK; |
| WCHAR *new_path; |
| int len; |
| |
| new_path = heap_alloc((strlenW(path) + 1) * sizeof(WCHAR)); |
| if (!new_path) return E_OUTOFMEMORY; |
| |
| strcpyW(new_path, path); |
| |
| len = strlenW(new_path); |
| while (len && new_path[len - 1] == '\\') |
| { |
| new_path[len - 1] = 0; |
| len--; |
| } |
| |
| while (!CreateDirectoryW(new_path, NULL)) |
| { |
| WCHAR *slash; |
| DWORD last_error = GetLastError(); |
| |
| if (last_error != ERROR_PATH_NOT_FOUND || !(slash = strrchrW(new_path, '\\'))) |
| { |
| hr = HRESULT_FROM_WIN32(last_error); |
| break; |
| } |
| |
| len = slash - new_path; |
| new_path[len] = 0; |
| hr = create_directory(new_path); |
| if (hr != S_OK) break; |
| new_path[len] = '\\'; |
| } |
| |
| heap_free(new_path); |
| return hr; |
| } |
| |
| static HRESULT write_xml_utf8(const WCHAR *name, DWORD disposition, const WCHAR *xmlW) |
| { |
| static const char comment[] = "<!-- Task definition created by Wine -->\n"; |
| HANDLE hfile; |
| DWORD size; |
| char *xml; |
| HRESULT hr = S_OK; |
| |
| hfile = CreateFileW(name, GENERIC_WRITE, 0, NULL, disposition, 0, 0); |
| if (hfile == INVALID_HANDLE_VALUE) |
| { |
| if (GetLastError() == ERROR_FILE_EXISTS) |
| return HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS); |
| |
| return HRESULT_FROM_WIN32(GetLastError()); |
| } |
| |
| size = WideCharToMultiByte(CP_UTF8, 0, xmlW, -1, NULL, 0, NULL, NULL); |
| xml = heap_alloc(size); |
| if (!xml) |
| { |
| CloseHandle(hfile); |
| return E_OUTOFMEMORY; |
| } |
| WideCharToMultiByte(CP_UTF8, 0, xmlW, -1, xml, size, NULL, NULL); |
| |
| if (!WriteFile(hfile, bom_utf8, sizeof(bom_utf8), &size, NULL)) |
| { |
| hr = HRESULT_FROM_WIN32(GetLastError()); |
| goto failed; |
| } |
| if (!WriteFile(hfile, comment, strlen(comment), &size, NULL)) |
| { |
| hr = HRESULT_FROM_WIN32(GetLastError()); |
| goto failed; |
| } |
| |
| /* skip XML declaration with UTF-16 specifier */ |
| if (!memcmp(xml, "<?xml", 5)) |
| { |
| const char *p = strchr(xml, '>'); |
| if (p++) while (isspace(*p)) p++; |
| else p = xml; |
| if (!WriteFile(hfile, p, strlen(p), &size, NULL)) |
| hr = HRESULT_FROM_WIN32(GetLastError()); |
| } |
| else |
| { |
| if (!WriteFile(hfile, xml, strlen(xml), &size, NULL)) |
| hr = HRESULT_FROM_WIN32(GetLastError()); |
| } |
| |
| failed: |
| heap_free(xml); |
| CloseHandle(hfile); |
| return hr; |
| } |
| |
| HRESULT __cdecl SchRpcRegisterTask(const WCHAR *path, const WCHAR *xml, DWORD flags, const WCHAR *sddl, |
| DWORD task_logon_type, DWORD n_creds, const TASK_USER_CRED *creds, |
| WCHAR **actual_path, TASK_XML_ERROR_INFO **xml_error_info) |
| { |
| WCHAR *full_name, *relative_path; |
| DWORD disposition; |
| HRESULT hr; |
| |
| TRACE("%s,%s,%#x,%s,%u,%u,%p,%p,%p\n", debugstr_w(path), debugstr_w(xml), flags, |
| debugstr_w(sddl), task_logon_type, n_creds, creds, actual_path, xml_error_info); |
| |
| *actual_path = NULL; |
| *xml_error_info = NULL; |
| |
| /* FIXME: assume that validation is performed on the client side */ |
| if (flags & TASK_VALIDATE_ONLY) return S_OK; |
| |
| if (path) |
| { |
| full_name = get_full_name(path, &relative_path); |
| if (!full_name) return E_OUTOFMEMORY; |
| |
| if (strchrW(path, '\\') || strchrW(path, '/')) |
| { |
| WCHAR *p = strrchrW(full_name, '/'); |
| if (!p) p = strrchrW(full_name, '\\'); |
| *p = 0; |
| hr = create_directory(full_name); |
| if (hr != S_OK && hr != HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS)) |
| { |
| heap_free(full_name); |
| return hr; |
| } |
| *p = '\\'; |
| } |
| } |
| else |
| { |
| IID iid; |
| WCHAR uuid_str[39]; |
| |
| UuidCreate(&iid); |
| StringFromGUID2(&iid, uuid_str, 39); |
| |
| full_name = get_full_name(uuid_str, &relative_path); |
| if (!full_name) return E_OUTOFMEMORY; |
| /* skip leading '\' */ |
| relative_path++; |
| } |
| |
| switch (flags & (TASK_CREATE | TASK_UPDATE)) |
| { |
| default: |
| case TASK_CREATE: |
| disposition = CREATE_NEW; |
| break; |
| |
| case TASK_UPDATE: |
| disposition = OPEN_EXISTING; |
| break; |
| |
| case (TASK_CREATE | TASK_UPDATE): |
| disposition = OPEN_ALWAYS; |
| break; |
| } |
| |
| hr = write_xml_utf8(full_name, disposition, xml); |
| if (hr == S_OK) |
| { |
| *actual_path = heap_strdupW(relative_path); |
| schedsvc_auto_start(); |
| } |
| |
| heap_free(full_name); |
| return hr; |
| } |
| |
| static int detect_encoding(const void *buffer, DWORD size) |
| { |
| if (size >= sizeof(bom_utf8) && !memcmp(buffer, bom_utf8, sizeof(bom_utf8))) |
| return CP_UTF8; |
| else |
| { |
| int flags = IS_TEXT_UNICODE_SIGNATURE | |
| IS_TEXT_UNICODE_REVERSE_SIGNATURE | |
| IS_TEXT_UNICODE_ODD_LENGTH; |
| IsTextUnicode(buffer, size, &flags); |
| if (flags & IS_TEXT_UNICODE_SIGNATURE) |
| return -1; |
| if (flags & IS_TEXT_UNICODE_REVERSE_SIGNATURE) |
| return -2; |
| return CP_ACP; |
| } |
| } |
| |
| static HRESULT read_xml(const WCHAR *name, WCHAR **xml) |
| { |
| HANDLE hfile; |
| DWORD size, attrs; |
| char *src; |
| int cp; |
| |
| attrs = GetFileAttributesW(name); |
| if (attrs == INVALID_FILE_ATTRIBUTES) |
| return HRESULT_FROM_WIN32(GetLastError()); |
| if (attrs & FILE_ATTRIBUTE_DIRECTORY) |
| return HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND); |
| |
| hfile = CreateFileW(name, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0); |
| if (hfile == INVALID_HANDLE_VALUE) |
| return HRESULT_FROM_WIN32(GetLastError()); |
| |
| size = GetFileSize(hfile, NULL); |
| src = heap_alloc(size + 2); |
| if (!src) |
| { |
| CloseHandle(hfile); |
| return E_OUTOFMEMORY; |
| } |
| |
| src[size] = 0; |
| src[size + 1] = 0; |
| |
| ReadFile(hfile, src, size, &size, NULL); |
| CloseHandle(hfile); |
| |
| cp = detect_encoding(src, size); |
| if (cp < 0) |
| { |
| *xml = (WCHAR *)src; |
| return S_OK; |
| } |
| |
| if (cp == CP_UTF8 && size >= sizeof(bom_utf8) && !memcmp(src, bom_utf8, sizeof(bom_utf8))) |
| src += sizeof(bom_utf8); |
| |
| size = MultiByteToWideChar(cp, 0, src, -1, NULL, 0); |
| *xml = heap_alloc(size * sizeof(WCHAR)); |
| if (!*xml) return E_OUTOFMEMORY; |
| MultiByteToWideChar(cp, 0, src, -1, *xml, size); |
| return S_OK; |
| } |
| |
| HRESULT __cdecl SchRpcRetrieveTask(const WCHAR *path, const WCHAR *languages, ULONG *n_languages, WCHAR **xml) |
| { |
| WCHAR *full_name; |
| HRESULT hr; |
| |
| TRACE("%s,%s,%p,%p\n", debugstr_w(path), debugstr_w(languages), n_languages, xml); |
| |
| full_name = get_full_name(path, NULL); |
| if (!full_name) return E_OUTOFMEMORY; |
| |
| hr = read_xml(full_name, xml); |
| if (hr != S_OK) *xml = NULL; |
| |
| heap_free(full_name); |
| return hr; |
| } |
| |
| HRESULT __cdecl SchRpcCreateFolder(const WCHAR *path, const WCHAR *sddl, DWORD flags) |
| { |
| WCHAR *full_name; |
| HRESULT hr; |
| |
| TRACE("%s,%s,%#x\n", debugstr_w(path), debugstr_w(sddl), flags); |
| |
| if (flags) return E_INVALIDARG; |
| |
| full_name = get_full_name(path, NULL); |
| if (!full_name) return E_OUTOFMEMORY; |
| |
| hr = create_directory(full_name); |
| |
| heap_free(full_name); |
| return hr; |
| } |
| |
| HRESULT __cdecl SchRpcSetSecurity(const WCHAR *path, const WCHAR *sddl, DWORD flags) |
| { |
| FIXME("%s,%s,%#x: stub\n", debugstr_w(path), debugstr_w(sddl), flags); |
| return E_NOTIMPL; |
| } |
| |
| HRESULT __cdecl SchRpcGetSecurity(const WCHAR *path, DWORD flags, WCHAR **sddl) |
| { |
| FIXME("%s,%#x,%p: stub\n", debugstr_w(path), flags, sddl); |
| return E_NOTIMPL; |
| } |
| |
| static void free_list(TASK_NAMES list, LONG count) |
| { |
| LONG i; |
| |
| for (i = 0; i < count; i++) |
| heap_free(list[i]); |
| |
| heap_free(list); |
| } |
| |
| static inline BOOL is_directory(const WIN32_FIND_DATAW *data) |
| { |
| if (data->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) |
| { |
| if (data->cFileName[0] == '.') |
| { |
| if (!data->cFileName[1] || (data->cFileName[1] == '.' && !data->cFileName[2])) |
| return FALSE; |
| } |
| return TRUE; |
| } |
| return FALSE; |
| } |
| |
| static inline BOOL is_file(const WIN32_FIND_DATAW *data) |
| { |
| return !(data->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); |
| } |
| |
| HRESULT __cdecl SchRpcEnumFolders(const WCHAR *path, DWORD flags, DWORD *start_index, DWORD n_requested, |
| DWORD *n_names, TASK_NAMES *names) |
| { |
| static const WCHAR allW[] = {'\\','*',0}; |
| HRESULT hr = S_OK; |
| WCHAR *full_name; |
| WCHAR pathW[MAX_PATH]; |
| WIN32_FIND_DATAW data; |
| HANDLE handle; |
| DWORD allocated, count, index; |
| TASK_NAMES list; |
| |
| TRACE("%s,%#x,%u,%u,%p,%p\n", debugstr_w(path), flags, *start_index, n_requested, n_names, names); |
| |
| *n_names = 0; |
| *names = NULL; |
| |
| if (flags & ~TASK_ENUM_HIDDEN) return E_INVALIDARG; |
| |
| if (!n_requested) n_requested = ~0u; |
| |
| full_name = get_full_name(path, NULL); |
| if (!full_name) return E_OUTOFMEMORY; |
| |
| if (strlenW(full_name) + 2 > MAX_PATH) |
| { |
| heap_free(full_name); |
| return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE); |
| } |
| |
| strcpyW(pathW, full_name); |
| strcatW(pathW, allW); |
| |
| heap_free(full_name); |
| |
| allocated = 64; |
| list = heap_alloc(allocated * sizeof(list[0])); |
| if (!list) return E_OUTOFMEMORY; |
| |
| index = count = 0; |
| |
| handle = FindFirstFileW(pathW, &data); |
| if (handle == INVALID_HANDLE_VALUE) |
| { |
| heap_free(list); |
| if (GetLastError() == ERROR_PATH_NOT_FOUND) |
| return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); |
| return HRESULT_FROM_WIN32(GetLastError()); |
| } |
| |
| do |
| { |
| if (is_directory(&data) && index++ >= *start_index) |
| { |
| if (count >= allocated) |
| { |
| TASK_NAMES new_list; |
| allocated *= 2; |
| new_list = heap_realloc(list, allocated * sizeof(list[0])); |
| if (!new_list) |
| { |
| hr = E_OUTOFMEMORY; |
| break; |
| } |
| list = new_list; |
| } |
| |
| TRACE("adding %s\n", debugstr_w(data.cFileName)); |
| |
| list[count] = heap_strdupW(data.cFileName); |
| if (!list[count]) |
| { |
| hr = E_OUTOFMEMORY; |
| break; |
| } |
| |
| count++; |
| |
| if (count >= n_requested) |
| { |
| hr = S_FALSE; |
| break; |
| } |
| } |
| } while (FindNextFileW(handle, &data)); |
| |
| FindClose(handle); |
| |
| if (FAILED(hr)) |
| { |
| free_list(list, count); |
| return hr; |
| } |
| |
| *n_names = count; |
| |
| if (count) |
| { |
| *names = list; |
| *start_index = index; |
| return hr; |
| } |
| |
| heap_free(list); |
| *names = NULL; |
| return *start_index ? S_FALSE : S_OK; |
| } |
| |
| HRESULT __cdecl SchRpcEnumTasks(const WCHAR *path, DWORD flags, DWORD *start_index, DWORD n_requested, |
| DWORD *n_names, TASK_NAMES *names) |
| { |
| static const WCHAR allW[] = {'\\','*',0}; |
| HRESULT hr = S_OK; |
| WCHAR *full_name; |
| WCHAR pathW[MAX_PATH]; |
| WIN32_FIND_DATAW data; |
| HANDLE handle; |
| DWORD allocated, count, index; |
| TASK_NAMES list; |
| |
| TRACE("%s,%#x,%u,%u,%p,%p\n", debugstr_w(path), flags, *start_index, n_requested, n_names, names); |
| |
| *n_names = 0; |
| *names = NULL; |
| |
| if (flags & ~TASK_ENUM_HIDDEN) return E_INVALIDARG; |
| |
| if (!n_requested) n_requested = ~0u; |
| |
| full_name = get_full_name(path, NULL); |
| if (!full_name) return E_OUTOFMEMORY; |
| |
| if (strlenW(full_name) + 2 > MAX_PATH) |
| { |
| heap_free(full_name); |
| return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE); |
| } |
| |
| strcpyW(pathW, full_name); |
| strcatW(pathW, allW); |
| |
| heap_free(full_name); |
| |
| allocated = 64; |
| list = heap_alloc(allocated * sizeof(list[0])); |
| if (!list) return E_OUTOFMEMORY; |
| |
| index = count = 0; |
| |
| handle = FindFirstFileW(pathW, &data); |
| if (handle == INVALID_HANDLE_VALUE) |
| { |
| heap_free(list); |
| if (GetLastError() == ERROR_PATH_NOT_FOUND) |
| return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); |
| return HRESULT_FROM_WIN32(GetLastError()); |
| } |
| |
| do |
| { |
| if (is_file(&data) && index++ >= *start_index) |
| { |
| if (count >= allocated) |
| { |
| TASK_NAMES new_list; |
| allocated *= 2; |
| new_list = heap_realloc(list, allocated * sizeof(list[0])); |
| if (!new_list) |
| { |
| hr = E_OUTOFMEMORY; |
| break; |
| } |
| list = new_list; |
| } |
| |
| TRACE("adding %s\n", debugstr_w(data.cFileName)); |
| |
| list[count] = heap_strdupW(data.cFileName); |
| if (!list[count]) |
| { |
| hr = E_OUTOFMEMORY; |
| break; |
| } |
| |
| count++; |
| |
| if (count >= n_requested) |
| { |
| hr = S_FALSE; |
| break; |
| } |
| } |
| } while (FindNextFileW(handle, &data)); |
| |
| FindClose(handle); |
| |
| if (FAILED(hr)) |
| { |
| free_list(list, count); |
| return hr; |
| } |
| |
| *n_names = count; |
| |
| if (count) |
| { |
| *names = list; |
| *start_index = index; |
| return hr; |
| } |
| |
| heap_free(list); |
| *names = NULL; |
| return *start_index ? S_FALSE : S_OK; |
| } |
| |
| HRESULT __cdecl SchRpcEnumInstances(const WCHAR *path, DWORD flags, DWORD *n_guids, GUID **guids) |
| { |
| FIXME("%s,%#x,%p,%p: stub\n", debugstr_w(path), flags, n_guids, guids); |
| return E_NOTIMPL; |
| } |
| |
| HRESULT __cdecl SchRpcGetInstanceInfo(GUID guid, WCHAR **path, DWORD *task_state, WCHAR **action, |
| WCHAR **info, DWORD *n_instances, GUID **instances, DWORD *pid) |
| { |
| FIXME("%s,%p,%p,%p,%p,%p,%p,%p: stub\n", wine_dbgstr_guid(&guid), path, task_state, action, |
| info, n_instances, instances, pid); |
| return E_NOTIMPL; |
| } |
| |
| HRESULT __cdecl SchRpcStopInstance(GUID guid, DWORD flags) |
| { |
| FIXME("%s,%#x: stub\n", wine_dbgstr_guid(&guid), flags); |
| return E_NOTIMPL; |
| } |
| |
| HRESULT __cdecl SchRpcStop(const WCHAR *path, DWORD flags) |
| { |
| FIXME("%s,%#x: stub\n", debugstr_w(path), flags); |
| return E_NOTIMPL; |
| } |
| |
| HRESULT __cdecl SchRpcRun(const WCHAR *path, DWORD n_args, const WCHAR **args, DWORD flags, |
| DWORD session_id, const WCHAR *user, GUID *guid) |
| { |
| FIXME("%s,%u,%p,%#x,%#x,%s,%p: stub\n", debugstr_w(path), n_args, args, flags, |
| session_id, debugstr_w(user), guid); |
| return E_NOTIMPL; |
| } |
| |
| HRESULT __cdecl SchRpcDelete(const WCHAR *path, DWORD flags) |
| { |
| WCHAR *full_name; |
| HRESULT hr = S_OK; |
| |
| TRACE("%s,%#x\n", debugstr_w(path), flags); |
| |
| if (flags) return E_INVALIDARG; |
| |
| while (*path == '\\' || *path == '/') path++; |
| if (!*path) return E_ACCESSDENIED; |
| |
| full_name = get_full_name(path, NULL); |
| if (!full_name) return E_OUTOFMEMORY; |
| |
| if (!RemoveDirectoryW(full_name)) |
| { |
| hr = HRESULT_FROM_WIN32(GetLastError()); |
| if (hr == HRESULT_FROM_WIN32(ERROR_DIRECTORY)) |
| hr = DeleteFileW(full_name) ? S_OK : HRESULT_FROM_WIN32(GetLastError()); |
| } |
| |
| heap_free(full_name); |
| return hr; |
| } |
| |
| HRESULT __cdecl SchRpcRename(const WCHAR *path, const WCHAR *name, DWORD flags) |
| { |
| FIXME("%s,%s,%#x: stub\n", debugstr_w(path), debugstr_w(name), flags); |
| return E_NOTIMPL; |
| } |
| |
| HRESULT __cdecl SchRpcScheduledRuntimes(const WCHAR *path, SYSTEMTIME *start, SYSTEMTIME *end, DWORD flags, |
| DWORD n_requested, DWORD *n_runtimes, SYSTEMTIME **runtimes) |
| { |
| FIXME("%s,%p,%p,%#x,%u,%p,%p: stub\n", debugstr_w(path), start, end, flags, |
| n_requested, n_runtimes, runtimes); |
| return E_NOTIMPL; |
| } |
| |
| HRESULT __cdecl SchRpcGetLastRunInfo(const WCHAR *path, SYSTEMTIME *last_runtime, DWORD *last_return_code) |
| { |
| FIXME("%s,%p,%p: stub\n", debugstr_w(path), last_runtime, last_return_code); |
| return E_NOTIMPL; |
| } |
| |
| HRESULT __cdecl SchRpcGetTaskInfo(const WCHAR *path, DWORD flags, DWORD *enabled, DWORD *task_state) |
| { |
| WCHAR *full_name, *xml; |
| HRESULT hr; |
| |
| FIXME("%s,%#x,%p,%p: stub\n", debugstr_w(path), flags, enabled, task_state); |
| |
| full_name = get_full_name(path, NULL); |
| if (!full_name) return E_OUTOFMEMORY; |
| |
| hr = read_xml(full_name, &xml); |
| heap_free(full_name); |
| if (hr != S_OK) return hr; |
| heap_free(xml); |
| |
| *enabled = 0; |
| *task_state = (flags & SCH_FLAG_STATE) ? TASK_STATE_DISABLED : TASK_STATE_UNKNOWN; |
| return S_OK; |
| } |
| |
| HRESULT __cdecl SchRpcGetNumberOfMissedRuns(const WCHAR *path, DWORD *runs) |
| { |
| FIXME("%s,%p: stub\n", debugstr_w(path), runs); |
| return E_NOTIMPL; |
| } |
| |
| HRESULT __cdecl SchRpcEnableTask(const WCHAR *path, DWORD enabled) |
| { |
| FIXME("%s,%u: stub\n", debugstr_w(path), enabled); |
| return E_NOTIMPL; |
| } |