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 plover__uri_handler_init();
98 prefix=plover_pre_install_prefix();
99 s=g_strconcat(prefix,"/var/log/pre-install",NULL);
102 s=g_strconcat(prefix,"/var/lib/razor",NULL);
103 uri=razor_path_to_uri(s);
105 razor_set_database_uri(uri);
107 if (verify_and_fix(prefix))
113 retval=!plover_install(path,prefix,install,&error);
115 retval=!plover_update(path,prefix,NULL,&error);
118 fprintf(stderr,"%s\n",error->message);
123 PostQuitMessage(retval);
124 PostThreadMessage(main_thread_id,WM_QUIT,retval,0);
125 _endthreadex(retval);
131 * The idea is that if pre_install() fails, update/setup should fall back
132 * to console interfaces.
139 pre_install(const char *path)
146 razor_set_lua_loader("posix",(void (*)())luaopen_posix);
147 razor_set_lua_loader("whelk",(void (*)())luaopen_whelk);
149 retval=(HANDLE)_beginthreadex(NULL,0,pre_install_thread,(void *)path,0,
152 if (pre_install_thread(path))
155 retval=(void *)1; /* Non-NULL to indicate success */
161 int remove_ignore(const char *fpath,const struct stat *sb,int typeflag,
169 gboolean deltree(const char *path)
172 /* Based on g_local_file_trash() */
173 SHFILEOPSTRUCTW op={0};
177 wfilename=g_utf8_to_utf16(path,-1,NULL,&len,NULL);
178 /* SHFILEOPSTRUCT.pFrom is double-zero-terminated */
179 wfilename=g_renew(wchar_t,wfilename,len+2);
184 success=!SHFileOperationW(&op);
185 if (success && op.fAnyOperationsAborted)
190 return nftw(path,remove_ignore,64,FTW_DEPTH|FTW_PHYS);
194 gboolean pre_uninstall(void)
199 razor_set_lua_loader("posix",(void (*)())luaopen_posix);
200 razor_set_lua_loader("whelk",(void (*)())luaopen_whelk);
201 prefix=plover_pre_install_prefix();
202 s=g_strconcat(prefix,"/var/lib/razor",NULL);
203 uri=razor_path_to_uri(s);
205 razor_set_database_uri(uri);
207 success=plover_remove(NULL,&error);
210 fprintf(stderr,"%s\n",error->message);
217 #if defined(WIN32) && !defined(USE_G_SPAWN)
218 /* Based on glib's g_spawn_win32.c */
221 win32_cmdline_quote(const char *string)
223 const gchar *p=string;
226 gboolean need_dblquotes=FALSE;
229 if (*p==' ' || *p=='\t')
236 while (*pp && *pp=='\\')
244 q=retval=g_malloc(len+need_dblquotes*2+1);
255 while (*pp && *pp=='\\')
269 /* Create a win32-style wide-character argv with suitable quoting */
270 wchar_t **win32_argv_import(char **argv,GError **error)
275 GError *tmp_error=NULL;
276 n=g_strv_length(argv);
277 wargv=g_new(wchar_t *,n+1);
280 s=win32_cmdline_quote(argv[i]);
281 wargv[i]=g_utf8_to_utf16(s,-1,NULL,NULL,&tmp_error);
285 g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
286 "Invalid argument #%d: %s",i,tmp_error->message);
287 g_error_free(tmp_error);
298 gboolean spawn_sync(char **argv,GError **error)
300 wchar_t *wargv0,**wargv;
302 GError *tmp_error=NULL;
303 wargv0=g_utf8_to_utf16(argv[0],-1,NULL,NULL,&tmp_error);
306 fprintf(stderr,"Conversion error in post\n");
307 g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
308 "Invalid program name: %s",tmp_error->message);
309 g_error_free(tmp_error);
312 wargv=win32_argv_import(argv,error);
315 fprintf(stderr,"Conversion error in post\n");
320 rc=_wspawnvp(P_WAIT,wargv0,(const wchar_t **)wargv);
322 g_strfreev((gchar **)wargv);
323 if (rc==-1 && errno!=0)
325 fprintf(stderr,"Failed to start post command (%s)\n",g_strerror(errno));
326 g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
327 "Failed to execute post command: %s",g_strerror(errno));
330 if (rc!=EXIT_SUCCESS)
332 fprintf(stderr,"post command failed (%ld)\n",(long)rc);
333 g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
334 "Post command exited with code %ld",(long)rc);
340 #endif /* defined(WIN32) && !defined(USE_G_SPAWN) */
343 * Run a command after completing request.
345 * Command may refer to %INSTALL_PREFIX% which will be replaced by the
346 * (first) install prefix used and/or %TEST_RESULT% which will be replaced
347 * by either "pass" or "fail" depending as to whether the request succeeded
348 * or not. Command may also include double quotes which will be used to
349 * affect how the command is split into arguments much like a shell does.
351 gboolean run_post(int argc,char **argv,gboolean test_result,GError **error)
358 gchar *standard_output,*standard_error;
361 GError *tmp_error=NULL;
364 g_set_error_literal(error,G_FILE_ERROR,G_FILE_ERROR_NOENT,
365 "--post: No command given");
368 printf("Running post command: %s\n",argv[1]);
369 if (!g_shell_parse_argv(argv[1],&post_argc,&post_argv,&tmp_error))
371 g_propagate_prefixed_error(error,tmp_error,"%s: ",argv[1]);
374 for(i=0;i<post_argc;i++)
376 s=strstr(post_argv[i],"%INSTALL_PREFIX%");
380 s+=strlen("%INSTALL_PREFIX%");
381 expanded=g_strconcat(post_argv[i],prefix,s,NULL);
382 g_free(post_argv[i]);
383 post_argv[i]=expanded;
385 s=strstr(post_argv[i],"%TEST_RESULT%");
389 s+=strlen("%TEST_RESULT%");
390 expanded=g_strconcat(post_argv[i],test_result?"pass":"fail",s,NULL);
391 g_free(post_argv[i]);
392 post_argv[i]=expanded;
396 if (!g_spawn_sync(NULL,post_argv,NULL,G_SPAWN_SEARCH_PATH,NULL,NULL,
397 &standard_output,&standard_error,&exit_status,&tmp_error))
399 fprintf(stderr,"Failed to start post command\n");
400 g_propagate_prefixed_error(error,tmp_error,"%s: ",post_argv[0]);
403 if (standard_output && *standard_output)
405 printf("Output from post command %s:\n",post_argv[0]);
406 fputs(standard_output,stdout);
408 g_free(standard_output);
409 if (standard_error && *standard_error)
411 printf("Error output from post command %s:\n",post_argv[0]);
412 fputs(standard_error,stdout);
414 g_free(standard_error);
415 if (!g_spawn_check_exit_status(exit_status,&tmp_error))
417 fprintf(stderr,"post command failed\n");
418 g_propagate_prefixed_error(error,tmp_error,"%s: ",post_argv[0]);
422 if (!spawn_sync(post_argv,&tmp_error))
424 g_propagate_prefixed_error(error,tmp_error,"%s: ",post_argv[0]);
432 DWORD win32_pre_install_gui(char *path)
435 INITCOMMONCONTROLSEX icc={0,};
438 main_thread_id=GetCurrentThreadId();
439 thread=(HANDLE)pre_install(path);
442 icc.dwSize=sizeof(icc);
443 icc.dwICC=ICC_WIN95_CLASSES;
444 InitCommonControlsEx(&icc);
445 DialogBox(GetModuleHandle(NULL),MAKEINTRESOURCE(IDD_PROGRESSDIALOG),NULL,
446 &ProgressDialogProc);
447 while(GetMessage(&msg,NULL,0,0)>0)
449 TranslateMessage(&msg);
450 DispatchMessage(&msg);
452 WaitForSingleObject(thread,INFINITE);
453 GetExitCodeThread(thread,&retval);
459 gchar *pre_install_default_path(const char *argv0)
465 struct razor_error *tmp_error=NULL;
467 * The default path is the executable itself if it's an archive
468 * or otherwise the directory in which the executable is stored.
470 path=plover_get_program(argv0);
471 uri=razor_path_to_uri(path);
472 s=g_strconcat(uri,"/repodata/comps.xml",NULL);
474 contents=razor_uri_get_contents(s,&length,FALSE,&tmp_error);
477 razor_uri_free_contents(contents,length);
480 if (razor_error_matches(tmp_error,RAZOR_GENERAL_ERROR,
481 RAZOR_GENERAL_ERROR_UNSUPPORTED_ARCHIVE))
484 path=plover_get_program_directory(argv0);
486 razor_error_free(tmp_error);
491 int main(int argc,char **argv)
493 gboolean success,uninstall=FALSE,enable_post=FALSE;
496 GOptionContext *context;
497 GOptionEntry options[]={
498 {"path",0,0,G_OPTION_ARG_FILENAME,&path,
499 "Repository path","path"},
500 {"uninstall",'u',0,G_OPTION_ARG_NONE,&uninstall,
501 "Uninstall all packages",NULL},
502 {"post",0,0,G_OPTION_ARG_NONE,&enable_post,
503 "Run command after request is processed",NULL},
508 * pre-inst is normally a GUI application, but rpm scripts may well
509 * call console applications and it looks ugly if console windows keep
510 * popping up. Avoid this by allocating our own console and hiding it.
512 * - If pre-inst is a console application (typically for debugging),
513 * then skip this step.
514 * - Call ShowWindow twice to negate special handling on first call.
516 if (!GetConsoleWindow())
519 ShowWindow(GetConsoleWindow(),SW_HIDE);
520 ShowWindow(GetConsoleWindow(),SW_HIDE);
523 plover_exception_handler_init();
524 context=g_option_context_new("[command] - install the main installer");
525 g_option_context_add_main_entries(context,options,NULL);
526 g_option_context_set_description(context,
527 "If --post is specified, then the command to run and its arguments\n"
528 "should be listed at the end of the command line.\n"
529 "Command may refer to %INSTALL_PREFIX% which will be replaced by the\n"
530 "(first) install prefix used and/or %TEST_RESULT% which will be\n"
531 "replaced by either \"pass\" or \"fail\" depending as to whether the\n"
532 "request succeeded or not. Command may also include double quotes which\n"
533 "will be used to affect how the command is split into arguments much\n"
534 "like a shell does.");
535 g_option_context_set_strict_posix(context,TRUE);
536 g_option_context_set_ignore_unknown_options(context,TRUE);
537 if (!g_option_context_parse(context,&argc,&argv,&error))
539 g_printerr("pre-install: %s\n",error->message);
540 g_printerr("Use \"%s --help\" for help\n",(*argv)[0]);
544 success=pre_uninstall();
548 path=pre_install_default_path(argv[0]);
550 success=win32_pre_install_gui(path)==EXIT_SUCCESS;
552 success=!!pre_install(path);
555 if (enable_post && !run_post(argc,argv,success,&error))
558 fprintf(stderr,"Error in post: %s\n",error->message);
560 MessageBox(NULL,error->message,"Error in post",MB_ICONERROR|MB_OK);
566 return success?EXIT_SUCCESS:EXIT_FAILURE;