2 * Copyright (C) 2014 J. Ali Harlow <ali@juiblex.co.uk>
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 * http://www.transmissionzero.co.uk/computing/win32-apps-with-mingw/
24 #define _XOPEN_SOURCE 500
31 #include <plover/plover.h>
32 #include <whelk/whelk.h>
40 #define FOF_NO_UI (FOF_SILENT|FOF_NOCONFIRMATION|FOF_NOERRORUI|\
49 /* Under WIN32, g_spawn requires a helper program which we'd rather avoid */
55 LUALIB_API int luaopen_posix(lua_State *L);
63 int verify_and_fix(const char *root)
69 INT_PTR CALLBACK ProgressDialogProc(HWND dialog,UINT msg,WPARAM w_param,
77 progress=GetDlgItem(dialog,IDC_PROGRESS);
78 style=GetWindowLong(progress,GWL_STYLE);
79 SetWindowLong(progress,GWL_STYLE,style|PBS_MARQUEE);
80 SendMessage(progress,PBM_SETMARQUEE,(WPARAM)TRUE,(LPARAM)30);
83 return (INT_PTR)FALSE;
90 unsigned pre_install_thread(void *data)
95 char *install[]={"plover-gtkui",NULL};
97 prefix=plover_pre_install_prefix();
98 s=g_strconcat(prefix,"/var/log/pre-install",NULL);
101 s=g_strconcat(prefix,"/var/lib/razor",NULL);
102 uri=razor_path_to_uri(s);
104 razor_set_database_uri(uri);
106 if (verify_and_fix(prefix))
112 retval=!plover_install(path,prefix,install,&error);
114 retval=!plover_update(path,prefix,NULL,&error);
117 fprintf(stderr,"%s\n",error->message);
122 PostQuitMessage(retval);
123 PostThreadMessage(main_thread_id,WM_QUIT,retval,0);
124 _endthreadex(retval);
130 * The idea is that if pre_install() fails, update/setup should fall back
131 * to console interfaces.
138 pre_install(const char *path)
145 razor_set_lua_loader("posix",(void (*)())luaopen_posix);
146 razor_set_lua_loader("whelk",(void (*)())luaopen_whelk);
148 retval=(HANDLE)_beginthreadex(NULL,0,pre_install_thread,(void *)path,0,
151 if (pre_install_thread(path))
154 retval=(void *)1; /* Non-NULL to indicate success */
160 int remove_ignore(const char *fpath,const struct stat *sb,int typeflag,
168 gboolean deltree(const char *path)
171 /* Based on g_local_file_trash() */
172 SHFILEOPSTRUCTW op={0};
176 wfilename=g_utf8_to_utf16(path,-1,NULL,&len,NULL);
177 /* SHFILEOPSTRUCT.pFrom is double-zero-terminated */
178 wfilename=g_renew(wchar_t,wfilename,len+2);
183 success=!SHFileOperationW(&op);
184 if (success && op.fAnyOperationsAborted)
189 return nftw(path,remove_ignore,64,FTW_DEPTH|FTW_PHYS);
193 gboolean pre_uninstall(void)
198 razor_set_lua_loader("posix",(void (*)())luaopen_posix);
199 razor_set_lua_loader("whelk",(void (*)())luaopen_whelk);
200 prefix=plover_pre_install_prefix();
201 s=g_strconcat(prefix,"/var/lib/razor",NULL);
202 uri=razor_path_to_uri(s);
204 razor_set_database_uri(uri);
206 success=plover_remove(NULL,&error);
209 fprintf(stderr,"%s\n",error->message);
216 #if defined(WIN32) && !defined(USE_G_SPAWN)
217 /* Based on glib's g_spawn_win32.c */
220 win32_cmdline_quote(const char *string)
222 const gchar *p=string;
225 gboolean need_dblquotes=FALSE;
228 if (*p==' ' || *p=='\t')
235 while (*pp && *pp=='\\')
243 q=retval=g_malloc(len+need_dblquotes*2+1);
254 while (*pp && *pp=='\\')
268 /* Create a win32-style wide-character argv with suitable quoting */
269 wchar_t **win32_argv_import(char **argv,GError **error)
274 GError *tmp_error=NULL;
275 n=g_strv_length(argv);
276 wargv=g_new(wchar_t *,n+1);
279 s=win32_cmdline_quote(argv[i]);
280 wargv[i]=g_utf8_to_utf16(s,-1,NULL,NULL,&tmp_error);
284 g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
285 "Invalid argument #%d: %s",i,tmp_error->message);
286 g_error_free(tmp_error);
297 gboolean spawn_sync(char **argv,GError **error)
299 wchar_t *wargv0,**wargv;
301 GError *tmp_error=NULL;
302 wargv0=g_utf8_to_utf16(argv[0],-1,NULL,NULL,&tmp_error);
305 fprintf(stderr,"Conversion error in post\n");
306 g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
307 "Invalid program name: %s",tmp_error->message);
308 g_error_free(tmp_error);
311 wargv=win32_argv_import(argv,error);
314 fprintf(stderr,"Conversion error in post\n");
319 rc=_wspawnvp(P_WAIT,wargv0,(const wchar_t **)wargv);
321 g_strfreev((gchar **)wargv);
322 if (rc==-1 && errno!=0)
324 fprintf(stderr,"Failed to start post command (%s)\n",g_strerror(errno));
325 g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
326 "Failed to execute post command: %s",g_strerror(errno));
329 if (rc!=EXIT_SUCCESS)
331 fprintf(stderr,"post command failed (%ld)\n",(long)rc);
332 g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
333 "Post command exited with code %ld",(long)rc);
339 #endif /* defined(WIN32) && !defined(USE_G_SPAWN) */
342 * Run a command after completing request.
344 * Command may refer to %INSTALL_PREFIX% which will be replaced by the
345 * (first) install prefix used and/or %TEST_RESULT% which will be replaced
346 * by either "pass" or "fail" depending as to whether the request succeeded
347 * or not. Command may also include double quotes which will be used to
348 * affect how the command is split into arguments much like a shell does.
350 gboolean run_post(int argc,char **argv,gboolean test_result,GError **error)
357 gchar *standard_output,*standard_error;
360 GError *tmp_error=NULL;
363 g_set_error_literal(error,G_FILE_ERROR,G_FILE_ERROR_NOENT,
364 "--post: No command given");
367 printf("Running post command: %s\n",argv[1]);
368 if (!g_shell_parse_argv(argv[1],&post_argc,&post_argv,&tmp_error))
370 g_propagate_prefixed_error(error,tmp_error,"%s: ",argv[1]);
373 for(i=0;i<post_argc;i++)
375 s=strstr(post_argv[i],"%INSTALL_PREFIX%");
379 s+=strlen("%INSTALL_PREFIX%");
380 expanded=g_strconcat(post_argv[i],prefix,s,NULL);
381 g_free(post_argv[i]);
382 post_argv[i]=expanded;
384 s=strstr(post_argv[i],"%TEST_RESULT%");
388 s+=strlen("%TEST_RESULT%");
389 expanded=g_strconcat(post_argv[i],test_result?"pass":"fail",s,NULL);
390 g_free(post_argv[i]);
391 post_argv[i]=expanded;
395 if (!g_spawn_sync(NULL,post_argv,NULL,G_SPAWN_SEARCH_PATH,NULL,NULL,
396 &standard_output,&standard_error,&exit_status,&tmp_error))
398 fprintf(stderr,"Failed to start post command\n");
399 g_propagate_prefixed_error(error,tmp_error,"%s: ",post_argv[0]);
402 if (standard_output && *standard_output)
404 printf("Output from post command %s:\n",post_argv[0]);
405 fputs(standard_output,stdout);
407 g_free(standard_output);
408 if (standard_error && *standard_error)
410 printf("Error output from post command %s:\n",post_argv[0]);
411 fputs(standard_error,stdout);
413 g_free(standard_error);
414 if (!g_spawn_check_exit_status(exit_status,&tmp_error))
416 fprintf(stderr,"post command failed\n");
417 g_propagate_prefixed_error(error,tmp_error,"%s: ",post_argv[0]);
421 if (!spawn_sync(post_argv,&tmp_error))
423 g_propagate_prefixed_error(error,tmp_error,"%s: ",post_argv[0]);
431 DWORD win32_pre_install_gui(char *path)
434 INITCOMMONCONTROLSEX icc={0,};
437 main_thread_id=GetCurrentThreadId();
438 thread=(HANDLE)pre_install(path);
441 icc.dwSize=sizeof(icc);
442 icc.dwICC=ICC_WIN95_CLASSES;
443 InitCommonControlsEx(&icc);
444 DialogBox(GetModuleHandle(NULL),MAKEINTRESOURCE(IDD_PROGRESSDIALOG),NULL,
445 &ProgressDialogProc);
446 while(GetMessage(&msg,NULL,0,0)>0)
448 TranslateMessage(&msg);
449 DispatchMessage(&msg);
451 WaitForSingleObject(thread,INFINITE);
452 GetExitCodeThread(thread,&retval);
458 int main(int argc,char **argv)
460 gboolean success,uninstall=FALSE,enable_post=FALSE;
463 GOptionContext *context;
464 GOptionEntry options[]={
465 {"path",0,0,G_OPTION_ARG_FILENAME,&path,
466 "Repository path","path"},
467 {"uninstall",'u',0,G_OPTION_ARG_NONE,&uninstall,
468 "Uninstall all packages",NULL},
469 {"post",0,0,G_OPTION_ARG_NONE,&enable_post,
470 "Run command after request is processed",NULL},
475 * pre-inst is normally a GUI application, but rpm scripts may well
476 * call console applications and it looks ugly if console windows keep
477 * popping up. Avoid this by allocating our own console and hiding it.
479 * - If pre-inst is a console application (typically for debugging),
480 * then skip this step.
481 * - Call ShowWindow twice to negate special handling on first call.
483 if (!GetConsoleWindow())
486 ShowWindow(GetConsoleWindow(),SW_HIDE);
487 ShowWindow(GetConsoleWindow(),SW_HIDE);
490 plover_exception_handler_init();
491 context=g_option_context_new("[command] - install the main installer");
492 g_option_context_add_main_entries(context,options,NULL);
493 g_option_context_set_description(context,
494 "If --post is specified, then the command to run and its arguments\n"
495 "should be listed at the end of the command line.\n"
496 "Command may refer to %INSTALL_PREFIX% which will be replaced by the\n"
497 "(first) install prefix used and/or %TEST_RESULT% which will be\n"
498 "replaced by either \"pass\" or \"fail\" depending as to whether the\n"
499 "request succeeded or not. Command may also include double quotes which\n"
500 "will be used to affect how the command is split into arguments much\n"
501 "like a shell does.");
502 g_option_context_set_strict_posix(context,TRUE);
503 g_option_context_set_ignore_unknown_options(context,TRUE);
504 if (!g_option_context_parse(context,&argc,&argv,&error))
506 g_printerr("pre-install: %s\n",error->message);
507 g_printerr("Use \"%s --help\" for help\n",(*argv)[0]);
511 success=pre_uninstall();
515 path=plover_get_program_directory(argv[0]);
517 success=win32_pre_install_gui(path)==EXIT_SUCCESS;
519 success=!!pre_install(path);
522 if (enable_post && !run_post(argc-1,argv+1,success,&error))
525 fprintf(stderr,"Error in post: %s\n",error->message);
527 MessageBox(NULL,error->message,"Error in post",MB_ICONERROR|MB_OK);
533 return success?EXIT_SUCCESS:EXIT_FAILURE;