From: Daira Hopwood Date: Tue, 21 Oct 2014 20:11:56 +0000 (+0100) Subject: Make a start on the Windows installer. X-Git-Url: https://git.rkrishnan.org/about.html?a=commitdiff_plain;h=4eb052997b5c74bb0c363cf24a93309f521e77c7;p=tahoe-lafs%2Ftahoe-lafs.git Make a start on the Windows installer. Signed-off-by: Daira Hopwood --- diff --git a/misc/build_helpers/windows/installer/.gitattributes b/misc/build_helpers/windows/installer/.gitattributes new file mode 100644 index 00000000..33ffd98d --- /dev/null +++ b/misc/build_helpers/windows/installer/.gitattributes @@ -0,0 +1,2 @@ +installer.sln -text +installer/installer.vcproj -text diff --git a/misc/build_helpers/windows/installer/installer.sln b/misc/build_helpers/windows/installer/installer.sln new file mode 100644 index 00000000..95d7a25d --- /dev/null +++ b/misc/build_helpers/windows/installer/installer.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual C++ Express 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "installer", "installer\installer.vcproj", "{413A02C3-FBCC-42B4-9277-F1CCBDD274CA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {413A02C3-FBCC-42B4-9277-F1CCBDD274CA}.Debug|Win32.ActiveCfg = Debug|Win32 + {413A02C3-FBCC-42B4-9277-F1CCBDD274CA}.Debug|Win32.Build.0 = Debug|Win32 + {413A02C3-FBCC-42B4-9277-F1CCBDD274CA}.Release|Win32.ActiveCfg = Release|Win32 + {413A02C3-FBCC-42B4-9277-F1CCBDD274CA}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/misc/build_helpers/windows/installer/installer/app.rc b/misc/build_helpers/windows/installer/installer/app.rc new file mode 100644 index 00000000..f82f4b65 --- /dev/null +++ b/misc/build_helpers/windows/installer/installer/app.rc @@ -0,0 +1,4 @@ +#include +#define IDC_STATIC -1 + +100 ICON "logo.ico" diff --git a/misc/build_helpers/windows/installer/installer/installer.cpp b/misc/build_helpers/windows/installer/installer/installer.cpp new file mode 100644 index 00000000..e17bb26a --- /dev/null +++ b/misc/build_helpers/windows/installer/installer/installer.cpp @@ -0,0 +1,276 @@ +// installer.cpp : Defines the entry point for the console application. +// + +#include "stdafx.h" +#include +#include + +// Turn off the warnings nagging you to use the more complicated *_s +// "secure" functions that are actually more difficult to use securely. +#pragma warning(disable:4996) + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include + +int wmain(int argc, wchar_t *argv[]); +wchar_t * get_default_destination_dir(); +void self_extract(wchar_t *destination_dir); +void unzip_from_executable(wchar_t *executable_path, wchar_t *destination_dir); +size_t read_uint32_le(unsigned char *b); +void unzip(wchar_t *zip_path, wchar_t *destination_dir); +bool have_acceptable_python(); +void install_python(wchar_t *python_installer_dir); + +#define fail_unless(x, s) if (!(x)) { fail(s); } +void fail(char *s); + +#define MINIMUM_PYTHON_VERSION L"2.7.0" +#define INSTALL_PYTHON_VERSION L"2.7.8" +#define PYTHON_INSTALLER_32BIT (L"python-" INSTALL_PYTHON_VERSION L".msi") +#define PYTHON_INSTALLER_64BIT (L"python-" INSTALL_PYTHON_VERSION L".amd64.msi") + +void noop_handler(const wchar_t * expression, + const wchar_t * function, + const wchar_t * file, + unsigned int line, + uintptr_t pReserved) { +} + +int wmain(int argc, wchar_t *argv[]) { + _set_invalid_parameter_handler(noop_handler); + + if (argc >= 2 && wcscmp(argv[1], L"--help") == 0) { + printf("installer \n"); + } + wchar_t *destination_dir = (argc >= 2) ? argv[1] : get_default_destination_dir(); + + self_extract(destination_dir); + //unzip(L"C:\\tahoe\\allmydata-tahoe-1.10.0c1.zip", destination_dir); + + if (!have_acceptable_python()) { + install_python(destination_dir); + } + //unlink(python_installer); + return 0; +} + +wchar_t * get_default_destination_dir() { + // TODO: get Program Files directory from the registry + return L"C:\\tahoe\\windowstest"; +} + +void self_extract(wchar_t *destination_dir) { + wchar_t executable_path[MAX_PATH]; + + HMODULE hModule = GetModuleHandle(NULL); + fail_unless(hModule != NULL, "Could not get the module handle."); + GetModuleFileNameW(hModule, executable_path, MAX_PATH); + fail_unless(GetLastError() == ERROR_SUCCESS, "Could not get the path of the current executable."); + + unzip_from_executable(executable_path, destination_dir); +} + +void unzip_from_executable(wchar_t *executable_path, wchar_t *destination_dir) { + // shell32's zipped folder implementation is strict about the zip format and + // does not support unzipping a self-extracting exe directly. So we copy the + // original zip file that was appended to the exe to a temporary directory, + // and use shell32 to unzip it from there. To get the length of the zip file, + // we look at its "end of central directory record", which is documented at + // . + // For simplicity we only handle the case of a zip file that has no archive + // comment, that does not use disk spanning, and that does not have a + // "Zip64 end of central directory record". + + // APPNOTE.TXT section 4.3.16. + const size_t sizeof_eocd = 22; + unsigned char end_data[sizeof_eocd]; + unsigned char eocd_signature[] = {0x50, 0x4B, 0x05, 0x06}; + unsigned char comment_length[] = {0x00, 0x00}; + unsigned char disk_num[] = {0x00, 0x00}; + + errno = 0; + FILE *f = _wfopen(L"C:\\tahoe\\foo.zip", L"rb"); + fail_unless(f != NULL && errno == 0 && ferror(f) == 0, + "Could not open executable file."); + + fseek(f, -(off_t) sizeof_eocd, SEEK_END); + fail_unless(errno == 0 && ferror(f) == 0, + "Could not seek to end-of-central-directory record."); + + __int64 eocd_offset = _ftelli64(f); + fail_unless(errno == 0 && ferror(f) == 0 && eocd_offset >= 0, + "Could not read position of end-of-central-directory record."); + fail_unless(eocd_offset + sizeof_eocd <= 0xFFFFFFFFi64, + "Cannot read an executable file >= 4 GiB."); + + size_t n = fread(end_data, sizeof(end_data), 1, f); + fail_unless(n == 1 && errno == 0 && ferror(f) == 0, + "Could not read end records."); + + fail_unless(memcmp(end_data + sizeof(end_data) - sizeof(comment_length), + comment_length, sizeof(comment_length)) == 0, + "Cannot read a zip file that has an archive comment."); + + fail_unless(memcmp(end_data, eocd_signature, sizeof(eocd_signature)) == 0, + "Could not find the end-of-central-directory signature."); + + fail_unless(memcmp(end_data + 4, disk_num, sizeof(disk_num)) == 0 && + memcmp(end_data + 6, disk_num, sizeof(disk_num)) == 0, + "Cannot read a zipfile that spans disks."); + + size_t cd_length = read_uint32_le(end_data + 12); + size_t cd_offset = read_uint32_le(end_data + 16); + __int64 zip_length = cd_offset + cd_length + sizeof_eocd; + fail_unless(zip_length <= 0x7FFFFFFFi64, + "Cannot copy a zip file >= 2 GiB."); + fseek(f, -(off_t) zip_length, SEEK_END); + fail_unless(errno == 0 && ferror(f) == 0, + "Could not seek to start of embedded zip file."); + + const wchar_t tmp_filename[] = L"tahoe-lafs.zip"; // FIXME make this more unique. + wchar_t tmp_path[MAX_PATH]; + DWORD len = GetTempPathW(MAX_PATH, tmp_path); + fail_unless(len > 0 && len < MAX_PATH - wcslen(tmp_filename), + "Could not obtain temporary directory path."); + wcscpy(tmp_path + len, tmp_filename); + + FILE *tmp_file = _wfopen(tmp_path, L"wb"); + unsigned char buf[16384]; + size_t remaining_length = (size_t) zip_length; + while (remaining_length > 0) { + size_t chunk_length = min(remaining_length, sizeof(buf)); + n = fread(buf, chunk_length, 1, f); + fail_unless(n == 1 && errno == 0 && ferror(f) == 0, + "Could not read from executable file."); + fwrite(buf, chunk_length, 1, tmp_file); + fail_unless(n == 1 && errno == 0 && ferror(f) == 0, + "Could not write to temporary file."); + remaining_length -= chunk_length; + } + fclose(tmp_file); + + unzip(tmp_path, destination_dir); +} + +// read unsigned little-endian 32-bit integer +size_t read_uint32_le(unsigned char *b) { + return ((size_t) b[0] ) | + ((size_t) b[1] << 8) | + ((size_t) b[2] << 16) | + ((size_t) b[3] << 24); +} + +void unzip(wchar_t *zip_path, wchar_t *destination_dir) { + // Based on + + wprintf(L"Extracting %ls\nto %ls\n", zip_path, destination_dir); + + // SysAllocString: + // BSTR: + + VARIANT zip_path_var; + zip_path_var.vt = VT_BSTR; + zip_path_var.bstrVal = SysAllocString(zip_path); + fail_unless(zip_path_var.bstrVal != NULL, "Could not allocate string for zip file path."); + + VARIANT destination_dir_var; + destination_dir_var.vt = VT_BSTR; + destination_dir_var.bstrVal = SysAllocString(destination_dir); + fail_unless(destination_dir_var.bstrVal != NULL, "Could not allocate string for destination directory path."); + + // CoInitializeEx: + HRESULT res = CoInitializeEx(NULL, 0); + fail_unless(res == S_OK || res == S_FALSE, "Could not initialize COM."); + + // CoCreateInstance: + IShellDispatch *shell; + res = CoCreateInstance(CLSID_Shell, NULL, CLSCTX_INPROC_SERVER, IID_IShellDispatch, (void **) &shell); + fail_unless(res == S_OK, "Could not create Shell instance."); + + // Folder.NameSpace: + Folder *zip_folder = NULL; + res = shell->NameSpace(zip_path_var, &zip_folder); + fail_unless(res == S_OK && zip_folder != NULL, "Could not create zip Folder object."); + + Folder *destination_folder = NULL; + res = shell->NameSpace(destination_dir_var, &destination_folder); + fail_unless(res == S_OK && destination_folder != NULL, "Could not create destination Folder object."); + + FolderItems *zip_folderitems = NULL; + zip_folder->Items(&zip_folderitems); + fail_unless(zip_folderitems != NULL, "Could not create zip FolderItems object."); + + long files_count = 0; + zip_folderitems->get_Count(&files_count); + printf("count %d\n", files_count); + + VARIANT zip_idispatch_var; + zip_idispatch_var.vt = VT_DISPATCH; + zip_idispatch_var.pdispVal = NULL; + zip_folderitems->QueryInterface(IID_IDispatch, (void **) &zip_idispatch_var.pdispVal); + fail_unless(zip_idispatch_var.pdispVal != NULL, "Could not create IDispatch for zip FolderItems object."); + + // Folder.CopyHere: + // (4) Do not display a progress dialog box. + // (16) Respond with "Yes to All" for any dialog box that is displayed. + // (256) Display a progress dialog box but do not show the file names. + // (512) Do not confirm the creation of a new directory if the operation requires one to be created. + // (1024) Do not display a user interface if an error occurs. + VARIANT options_var; + options_var.vt = VT_I4; + options_var.lVal = 16 | 256 | 512 | 1024; + + res = destination_folder->CopyHere(zip_idispatch_var, options_var); + fail_unless(res == S_OK, "Could not extract zip file contents to destination directory."); + + // We don't bother to free/release stuff unless we succeed, since we exit on failure. + + // SysFreeString: + SysFreeString(zip_path_var.bstrVal); + SysFreeString(destination_dir_var.bstrVal); + zip_idispatch_var.pdispVal->Release(); + zip_folderitems->Release(); + destination_folder->Release(); + zip_folder->Release(); + shell->Release(); + + // CoUninitialize: + CoUninitialize(); +} + +bool have_acceptable_python() { + printf("Checking for Python 2.7..."); + //key = OpenKey(HKEY_CURRENT_USER, L"Environment", 0, KEY_QUERY_VALUE) + //key = OpenKey(HKEY_CURRENT_USER, L"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment", 0, KEY_QUERY_VALUE) + //... + return false; +} + +void install_python(wchar_t *python_installer_dir) { + wchar_t installer_wildcard[] = L"\\*.msi"; + wchar_t installer_pattern[MAX_PATH]; + fail_unless(wcslen(python_installer_dir) < MAX_PATH - wcslen(installer_wildcard), + "Could not construct pattern for Python installer.") + wcscpy(installer_pattern, python_installer_dir); + wcscat(installer_pattern, installer_wildcard); + + WIN32_FIND_DATA find_data; + HANDLE search_handle = FindFirstFileW(installer_pattern, &find_data); + fail_unless(search_handle != INVALID_HANDLE_VALUE, + "Could not find the Python installer.") + + fail_unless(wcslen(python_installer_dir) < MAX_PATH - wcslen(find_data.cFileName), + "Could not construct path to Python installer.") + wcscpy(installer_pattern, python_installer_dir); + wcscat(installer_pattern, find_data.cFileName); + //CreateProcessW(".msi"); +} + +void fail(char *s) { + // TODO: show dialog box + puts(s); + exit(1); +} diff --git a/misc/build_helpers/windows/installer/installer/installer.vcproj b/misc/build_helpers/windows/installer/installer/installer.vcproj new file mode 100644 index 00000000..d19a40b9 --- /dev/null +++ b/misc/build_helpers/windows/installer/installer/installer.vcproj @@ -0,0 +1,233 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/misc/build_helpers/windows/installer/installer/logo.ico b/misc/build_helpers/windows/installer/installer/logo.ico new file mode 100644 index 00000000..84656706 Binary files /dev/null and b/misc/build_helpers/windows/installer/installer/logo.ico differ diff --git a/misc/build_helpers/windows/installer/installer/logo.svg b/misc/build_helpers/windows/installer/installer/logo.svg new file mode 100644 index 00000000..6de798e3 --- /dev/null +++ b/misc/build_helpers/windows/installer/installer/logo.svg @@ -0,0 +1,157 @@ + + + + + + image/svg+xml + + Tahoe-LAFS logo + + + + + + Tahoe-LAFS logo + + A proposed logo for the Tahoe-LAFS Project. + + + + +Tahoe-LAFS Logo + by Kevin Reid + is licensed under a Creative Commons Attribution 3.0 Unported License +. + + + + + + + + + + + + + + + + diff --git a/misc/build_helpers/windows/installer/installer/make-icon.sh b/misc/build_helpers/windows/installer/installer/make-icon.sh new file mode 100755 index 00000000..987e0ecb --- /dev/null +++ b/misc/build_helpers/windows/installer/installer/make-icon.sh @@ -0,0 +1,23 @@ +#! /bin/bash +# Based on +# converts the passed-in svgs to ico + +if [[ $# -eq 0 ]]; then + echo "Usage: $0 svg1 [svg2 [...]]" + exit 0 +fi + +temp=$(mktemp -d) +declare -a res=(16 24 32 48 64 256) +for f in $*; do + mkdir -p $temp/$(dirname $f) + for r in "${res[@]}"; do + inkscape -z -e $temp/${f}${r}.png -w $r -h $r $f + done + resm=( "${res[@]/#/$temp/$f}" ) + resm=( "${resm[@]/%/.png}" ) + for filetype in ico; do + convert "${resm[@]}" ${f%%.*}.$filetype + done +done +rm -rf $temp diff --git a/misc/build_helpers/windows/installer/installer/stdafx.cpp b/misc/build_helpers/windows/installer/installer/stdafx.cpp new file mode 100644 index 00000000..9718bdc6 --- /dev/null +++ b/misc/build_helpers/windows/installer/installer/stdafx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// installer.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff --git a/misc/build_helpers/windows/installer/installer/stdafx.h b/misc/build_helpers/windows/installer/installer/stdafx.h new file mode 100644 index 00000000..b005a839 --- /dev/null +++ b/misc/build_helpers/windows/installer/installer/stdafx.h @@ -0,0 +1,15 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#pragma once + +#include "targetver.h" + +#include +#include + + + +// TODO: reference additional headers your program requires here diff --git a/misc/build_helpers/windows/installer/installer/targetver.h b/misc/build_helpers/windows/installer/installer/targetver.h new file mode 100644 index 00000000..6fe8eb79 --- /dev/null +++ b/misc/build_helpers/windows/installer/installer/targetver.h @@ -0,0 +1,13 @@ +#pragma once + +// The following macros define the minimum required platform. The minimum required platform +// is the earliest version of Windows, Internet Explorer etc. that has the necessary features to run +// your application. The macros work by enabling all features available on platform versions up to and +// including the version specified. + +// Modify the following defines if you have to target a platform prior to the ones specified below. +// Refer to MSDN for the latest info on corresponding values for different platforms. +#ifndef _WIN32_WINNT // Specifies that the minimum required platform is Windows Vista. +#define _WIN32_WINNT 0x0600 // Change this to the appropriate value to target other versions of Windows. +#endif +