Imported Upstream version 9.20
[platform/upstream/7zip.git] / C / Util / SfxSetup / SfxSetup.c
1 /* SfxSetup.c - 7z SFX Setup\r
2 2010-11-11 : Igor Pavlov : Public domain */\r
3 \r
4 #ifndef UNICODE\r
5 #define UNICODE\r
6 #endif\r
7 \r
8 #ifndef _UNICODE\r
9 #define _UNICODE\r
10 #endif\r
11 \r
12 #ifdef _CONSOLE\r
13 #include <stdio.h>\r
14 #endif\r
15 \r
16 #include "../../7z.h"\r
17 #include "../../7zAlloc.h"\r
18 #include "../../7zCrc.h"\r
19 #include "../../7zFile.h"\r
20 #include "../../CpuArch.h"\r
21 \r
22 #define k_EXE_ExtIndex 1\r
23 \r
24 static const char *kExts[] =\r
25 {\r
26   "bat",\r
27   "exe",\r
28   "inf",\r
29   "msi",\r
30   #ifdef UNDER_CE\r
31   "cab",\r
32   #endif\r
33   "html",\r
34   "htm"\r
35 };\r
36 \r
37 static const char *kNames[] =\r
38 {\r
39   "setup",\r
40   "install",\r
41   "run",\r
42   "start"\r
43 };\r
44 \r
45 static unsigned FindExt(const wchar_t *s, unsigned *extLen)\r
46 {\r
47   unsigned len = (unsigned)wcslen(s);\r
48   unsigned i;\r
49   for (i = len; i > 0; i--)\r
50   {\r
51     if (s[i - 1] == '.')\r
52     {\r
53       *extLen = len - i;\r
54       return i - 1;\r
55     }\r
56   }\r
57   *extLen = 0;\r
58   return len;\r
59 }\r
60 \r
61 #define MAKE_CHAR_UPPER(c) ((((c) >= 'a' && (c) <= 'z') ? (c) -= 0x20 : (c)))\r
62 \r
63 static unsigned FindItem(const char **items, unsigned num, const wchar_t *s, unsigned len)\r
64 {\r
65   unsigned i;\r
66   for (i = 0; i < num; i++)\r
67   {\r
68     const char *item = items[i];\r
69     unsigned itemLen = (unsigned)strlen(item);\r
70     unsigned j;\r
71     if (len != itemLen)\r
72       continue;\r
73     for (j = 0; j < len; j++)\r
74     {\r
75       unsigned c = item[j];\r
76       if (c != s[j] && MAKE_CHAR_UPPER(c) != s[j])\r
77         break;\r
78     }\r
79     if (j == len)\r
80       return i;\r
81   }\r
82   return i;\r
83 }\r
84 \r
85 #ifdef _CONSOLE\r
86 static BOOL WINAPI HandlerRoutine(DWORD ctrlType)\r
87 {\r
88   ctrlType = ctrlType;\r
89   return TRUE;\r
90 }\r
91 #endif\r
92 \r
93 static void PrintErrorMessage(const char *message)\r
94 {\r
95   #ifdef _CONSOLE\r
96   printf("\n7-Zip Error: %s\n", message);\r
97   #else\r
98   #ifdef UNDER_CE\r
99   WCHAR messageW[256 + 4];\r
100   unsigned i;\r
101   for (i = 0; i < 256 && message[i] != 0; i++)\r
102     messageW[i] = message[i];\r
103   messageW[i] = 0;\r
104   MessageBoxW(0, messageW, L"7-Zip Error", MB_ICONERROR);\r
105   #else\r
106   MessageBoxA(0, message, "7-Zip Error", MB_ICONERROR);\r
107   #endif\r
108   #endif\r
109 }\r
110 \r
111 static WRes MyCreateDir(const WCHAR *name)\r
112 {\r
113   return CreateDirectoryW(name, NULL) ? 0 : GetLastError();\r
114 }\r
115 \r
116 #ifdef UNDER_CE\r
117 #define kBufferSize (1 << 13)\r
118 #else\r
119 #define kBufferSize (1 << 15)\r
120 #endif\r
121 \r
122 #define kSignatureSearchLimit (1 << 22)\r
123 \r
124 static Bool FindSignature(CSzFile *stream, UInt64 *resPos)\r
125 {\r
126   Byte buf[kBufferSize];\r
127   size_t numPrevBytes = 0;\r
128   *resPos = 0;\r
129   for (;;)\r
130   {\r
131     size_t numTests, pos;\r
132     if (*resPos > kSignatureSearchLimit)\r
133       return False;\r
134     \r
135     do\r
136     {\r
137       size_t processed = kBufferSize - numPrevBytes;\r
138       if (File_Read(stream, buf + numPrevBytes, &processed) != 0)\r
139         return False;\r
140       if (processed == 0)\r
141         return False;\r
142       numPrevBytes += processed;\r
143     }\r
144     while (numPrevBytes <= k7zStartHeaderSize);\r
145     \r
146     numTests = numPrevBytes - k7zStartHeaderSize;\r
147     for (pos = 0; pos < numTests; pos++)\r
148     {\r
149       for (; buf[pos] != '7' && pos < numTests; pos++);\r
150       if (pos == numTests)\r
151         break;\r
152       if (memcmp(buf + pos, k7zSignature, k7zSignatureSize) == 0)\r
153         if (CrcCalc(buf + pos + 12, 20) == GetUi32(buf + pos + 8))\r
154         {\r
155           *resPos += pos;\r
156           return True;\r
157         }\r
158     }\r
159     *resPos += numTests;\r
160     numPrevBytes -= numTests;\r
161     memmove(buf, buf + numTests, numPrevBytes);\r
162   }\r
163 }\r
164 \r
165 static Bool DoesFileOrDirExist(const WCHAR *path)\r
166 {\r
167   WIN32_FIND_DATAW fd;\r
168   HANDLE handle;\r
169   handle = FindFirstFileW(path, &fd);\r
170   if (handle == INVALID_HANDLE_VALUE)\r
171     return False;\r
172   FindClose(handle);\r
173   return True;\r
174 }\r
175 \r
176 static WRes RemoveDirWithSubItems(WCHAR *path)\r
177 {\r
178   WIN32_FIND_DATAW fd;\r
179   HANDLE handle;\r
180   WRes res = 0;\r
181   size_t len = wcslen(path);\r
182   wcscpy(path + len, L"*");\r
183   handle = FindFirstFileW(path, &fd);\r
184   path[len] = L'\0';\r
185   if (handle == INVALID_HANDLE_VALUE)\r
186     return GetLastError();\r
187   for (;;)\r
188   {\r
189     if (wcscmp(fd.cFileName, L".") != 0 &&\r
190         wcscmp(fd.cFileName, L"..") != 0)\r
191     {\r
192       wcscpy(path + len, fd.cFileName);\r
193       if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0)\r
194       {\r
195         wcscat(path, L"\\");\r
196         res = RemoveDirWithSubItems(path);\r
197       }\r
198       else\r
199       {\r
200         SetFileAttributesW(path, 0);\r
201         if (DeleteFileW(path) == 0)\r
202           res = GetLastError();\r
203       }\r
204       if (res != 0)\r
205         break;\r
206     }\r
207     if (!FindNextFileW(handle, &fd))\r
208     {\r
209       res = GetLastError();\r
210       if (res == ERROR_NO_MORE_FILES)\r
211         res = 0;\r
212       break;\r
213     }\r
214   }\r
215   path[len] = L'\0';\r
216   FindClose(handle);\r
217   if (res == 0)\r
218   {\r
219     if (!RemoveDirectoryW(path))\r
220       res = GetLastError();\r
221   }\r
222   return res;\r
223 }\r
224 \r
225 #ifdef _CONSOLE\r
226 int MY_CDECL main()\r
227 #else\r
228 int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,\r
229   #ifdef UNDER_CE\r
230   LPWSTR\r
231   #else\r
232   LPSTR\r
233   #endif\r
234   lpCmdLine, int nCmdShow)\r
235 #endif\r
236 {\r
237   CFileInStream archiveStream;\r
238   CLookToRead lookStream;\r
239   CSzArEx db;\r
240   SRes res = SZ_OK;\r
241   ISzAlloc allocImp;\r
242   ISzAlloc allocTempImp;\r
243   WCHAR sfxPath[MAX_PATH + 2];\r
244   WCHAR path[MAX_PATH * 3 + 2];\r
245   size_t pathLen;\r
246   DWORD winRes;\r
247   const wchar_t *cmdLineParams;\r
248   const char *errorMessage = NULL;\r
249   Bool useShellExecute = True;\r
250 \r
251   #ifdef _CONSOLE\r
252   SetConsoleCtrlHandler(HandlerRoutine, TRUE);\r
253   #else\r
254   hInstance = hInstance;\r
255   hPrevInstance = hPrevInstance;\r
256   lpCmdLine = lpCmdLine;\r
257   nCmdShow = nCmdShow;\r
258   #endif\r
259 \r
260   CrcGenerateTable();\r
261 \r
262   allocImp.Alloc = SzAlloc;\r
263   allocImp.Free = SzFree;\r
264 \r
265   allocTempImp.Alloc = SzAllocTemp;\r
266   allocTempImp.Free = SzFreeTemp;\r
267 \r
268   FileInStream_CreateVTable(&archiveStream);\r
269   LookToRead_CreateVTable(&lookStream, False);\r
270  \r
271   winRes = GetModuleFileNameW(NULL, sfxPath, MAX_PATH);\r
272   if (winRes == 0 || winRes > MAX_PATH)\r
273     return 1;\r
274   {\r
275     cmdLineParams = GetCommandLineW();\r
276     #ifndef UNDER_CE\r
277     {\r
278       Bool quoteMode = False;\r
279       for (;; cmdLineParams++)\r
280       {\r
281         wchar_t c = *cmdLineParams;\r
282         if (c == L'\"')\r
283           quoteMode = !quoteMode;\r
284         else if (c == 0 || (c == L' ' && !quoteMode))\r
285           break;\r
286       }\r
287     }\r
288     #endif\r
289   }\r
290 \r
291   {\r
292     unsigned i;\r
293     DWORD d;\r
294     winRes = GetTempPathW(MAX_PATH, path);\r
295     if (winRes == 0 || winRes > MAX_PATH)\r
296       return 1;\r
297     pathLen = wcslen(path);\r
298     d = (GetTickCount() << 12) ^ (GetCurrentThreadId() << 14) ^ GetCurrentProcessId();\r
299     for (i = 0;; i++, d += GetTickCount())\r
300     {\r
301       if (i >= 100)\r
302       {\r
303         res = SZ_ERROR_FAIL;\r
304         break;\r
305       }\r
306       wcscpy(path + pathLen, L"7z");\r
307 \r
308       {\r
309         wchar_t *s = path + wcslen(path);\r
310         UInt32 value = d;\r
311         unsigned k;\r
312         for (k = 0; k < 8; k++)\r
313         {\r
314           unsigned t = value & 0xF;\r
315           value >>= 4;\r
316           s[7 - k] = (char)((t < 10) ? ('0' + t) : ('A' + (t - 10)));\r
317         }\r
318         s[k] = '\0';\r
319       }\r
320 \r
321       if (DoesFileOrDirExist(path))\r
322         continue;\r
323       if (CreateDirectoryW(path, NULL))\r
324       {\r
325         wcscat(path, L"\\");\r
326         pathLen = wcslen(path);\r
327         break;\r
328       }\r
329       if (GetLastError() != ERROR_ALREADY_EXISTS)\r
330       {\r
331         res = SZ_ERROR_FAIL;\r
332         break;\r
333       }\r
334     }\r
335     if (res != SZ_OK)\r
336       errorMessage = "Can't create temp folder";\r
337   }\r
338 \r
339   if (res != SZ_OK)\r
340   {\r
341     if (!errorMessage)\r
342       errorMessage = "Error";\r
343     PrintErrorMessage(errorMessage);\r
344     return 1;\r
345   }\r
346 \r
347   if (InFile_OpenW(&archiveStream.file, sfxPath) != 0)\r
348   {\r
349     errorMessage = "can not open input file";\r
350     res = SZ_ERROR_FAIL;\r
351   }\r
352   else\r
353   {\r
354     UInt64 pos = 0;\r
355     if (!FindSignature(&archiveStream.file, &pos))\r
356       res = SZ_ERROR_FAIL;\r
357     else if (File_Seek(&archiveStream.file, (Int64 *)&pos, SZ_SEEK_SET) != 0)\r
358       res = SZ_ERROR_FAIL;\r
359     if (res != 0)\r
360       errorMessage = "Can't find 7z archive";\r
361   }\r
362 \r
363   if (res == SZ_OK)\r
364   {\r
365     lookStream.realStream = &archiveStream.s;\r
366     LookToRead_Init(&lookStream);\r
367   }\r
368 \r
369   SzArEx_Init(&db);\r
370   if (res == SZ_OK)\r
371   {\r
372     res = SzArEx_Open(&db, &lookStream.s, &allocImp, &allocTempImp);\r
373   }\r
374   if (res == SZ_OK)\r
375   {\r
376     UInt32 executeFileIndex = (UInt32)(Int32)-1;\r
377     UInt32 minPrice = 1 << 30;\r
378     UInt32 i;\r
379     UInt32 blockIndex = 0xFFFFFFFF; /* it can have any value before first call (if outBuffer = 0) */\r
380     Byte *outBuffer = 0; /* it must be 0 before first call for each new archive. */\r
381     size_t outBufferSize = 0;  /* it can have any value before first call (if outBuffer = 0) */\r
382     \r
383     for (i = 0; i < db.db.NumFiles; i++)\r
384     {\r
385       size_t offset = 0;\r
386       size_t outSizeProcessed = 0;\r
387       const CSzFileItem *f = db.db.Files + i;\r
388       size_t len;\r
389       WCHAR *temp;\r
390       len = SzArEx_GetFileNameUtf16(&db, i, NULL);\r
391       \r
392       if (len >= MAX_PATH)\r
393       {\r
394         res = SZ_ERROR_FAIL;\r
395         break;\r
396       }\r
397       \r
398       temp = path + pathLen;\r
399       \r
400       SzArEx_GetFileNameUtf16(&db, i, temp);\r
401       {\r
402         res = SzArEx_Extract(&db, &lookStream.s, i,\r
403           &blockIndex, &outBuffer, &outBufferSize,\r
404           &offset, &outSizeProcessed,\r
405           &allocImp, &allocTempImp);\r
406         if (res != SZ_OK)\r
407           break;\r
408       }\r
409       {\r
410         CSzFile outFile;\r
411         size_t processedSize;\r
412         size_t j;\r
413         size_t nameStartPos = 0;\r
414         for (j = 0; temp[j] != 0; j++)\r
415         {\r
416           if (temp[j] == '/')\r
417           {\r
418             temp[j] = 0;\r
419             MyCreateDir(path);\r
420             temp[j] = CHAR_PATH_SEPARATOR;\r
421             nameStartPos = j + 1;\r
422           }\r
423         }\r
424 \r
425         if (f->IsDir)\r
426         {\r
427           MyCreateDir(path);\r
428           continue;\r
429         }\r
430         else\r
431         {\r
432           unsigned extLen;\r
433           const WCHAR *name = temp + nameStartPos;\r
434           unsigned len = (unsigned)wcslen(name);\r
435           unsigned nameLen = FindExt(temp + nameStartPos, &extLen);\r
436           unsigned extPrice = FindItem(kExts, sizeof(kExts) / sizeof(kExts[0]), name + len - extLen, extLen);\r
437           unsigned namePrice = FindItem(kNames, sizeof(kNames) / sizeof(kNames[0]), name, nameLen);\r
438 \r
439           unsigned price = namePrice + extPrice * 64 + (nameStartPos == 0 ? 0 : (1 << 12));\r
440           if (minPrice > price)\r
441           {\r
442             minPrice = price;\r
443             executeFileIndex = i;\r
444             useShellExecute = (extPrice != k_EXE_ExtIndex);\r
445           }\r
446          \r
447           if (DoesFileOrDirExist(path))\r
448           {\r
449             errorMessage = "Duplicate file";\r
450             res = SZ_ERROR_FAIL;\r
451             break;\r
452           }\r
453           if (OutFile_OpenW(&outFile, path))\r
454           {\r
455             errorMessage = "Can't open output file";\r
456             res = SZ_ERROR_FAIL;\r
457             break;\r
458           }\r
459         }\r
460         processedSize = outSizeProcessed;\r
461         if (File_Write(&outFile, outBuffer + offset, &processedSize) != 0 || processedSize != outSizeProcessed)\r
462         {\r
463           errorMessage = "Can't write output file";\r
464           res = SZ_ERROR_FAIL;\r
465         }\r
466         \r
467         #ifdef USE_WINDOWS_FILE\r
468         if (f->MTimeDefined)\r
469         {\r
470           FILETIME mTime;\r
471           mTime.dwLowDateTime = f->MTime.Low;\r
472           mTime.dwHighDateTime = f->MTime.High;\r
473           SetFileTime(outFile.handle, NULL, NULL, &mTime);\r
474         }\r
475         #endif\r
476         \r
477         {\r
478           SRes res2 = File_Close(&outFile);\r
479           if (res != SZ_OK)\r
480             break;\r
481           if (res2 != SZ_OK)\r
482           {\r
483             res = res2;\r
484             break;\r
485           }\r
486         }\r
487         #ifdef USE_WINDOWS_FILE\r
488         if (f->AttribDefined)\r
489           SetFileAttributesW(path, f->Attrib);\r
490         #endif\r
491       }\r
492     }\r
493 \r
494     if (res == SZ_OK)\r
495     {\r
496       if (executeFileIndex == (UInt32)(Int32)-1)\r
497       {\r
498         errorMessage = "There is no file to execute";\r
499         res = SZ_ERROR_FAIL;\r
500       }\r
501       else\r
502       {\r
503         WCHAR *temp = path + pathLen;\r
504         UInt32 j;\r
505         SzArEx_GetFileNameUtf16(&db, executeFileIndex, temp);\r
506         for (j = 0; temp[j] != 0; j++)\r
507           if (temp[j] == '/')\r
508             temp[j] = CHAR_PATH_SEPARATOR;\r
509       }\r
510     }\r
511     IAlloc_Free(&allocImp, outBuffer);\r
512   }\r
513   SzArEx_Free(&db, &allocImp);\r
514 \r
515   File_Close(&archiveStream.file);\r
516 \r
517   if (res == SZ_OK)\r
518   {\r
519     HANDLE hProcess = 0;\r
520     if (useShellExecute)\r
521     {\r
522       SHELLEXECUTEINFO ei;\r
523       UINT32 executeRes;\r
524       BOOL success;\r
525       \r
526       memset(&ei, 0, sizeof(ei));\r
527       ei.cbSize = sizeof(ei);\r
528       ei.lpFile = path;\r
529       ei.fMask = SEE_MASK_NOCLOSEPROCESS\r
530           #ifndef UNDER_CE\r
531           | SEE_MASK_FLAG_DDEWAIT\r
532           #endif\r
533           /* | SEE_MASK_NO_CONSOLE */\r
534           ;\r
535       if (wcslen(cmdLineParams) != 0)\r
536         ei.lpParameters = cmdLineParams;\r
537       ei.nShow = SW_SHOWNORMAL; /* SW_HIDE; */\r
538       success = ShellExecuteEx(&ei);\r
539       executeRes = (UINT32)(UINT_PTR)ei.hInstApp;\r
540       if (!success || (executeRes <= 32 && executeRes != 0))  /* executeRes = 0 in Windows CE */\r
541         res = SZ_ERROR_FAIL;\r
542       else\r
543         hProcess = ei.hProcess;\r
544     }\r
545     else\r
546     {\r
547       STARTUPINFOW si;\r
548       PROCESS_INFORMATION pi;\r
549       WCHAR cmdLine[MAX_PATH * 3];\r
550 \r
551       wcscpy(cmdLine, path);\r
552       wcscat(cmdLine, cmdLineParams);\r
553       memset(&si, 0, sizeof(si));\r
554       si.cb = sizeof(si);\r
555       if (CreateProcessW(NULL, cmdLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi) == 0)\r
556         res = SZ_ERROR_FAIL;\r
557       else\r
558       {\r
559         CloseHandle(pi.hThread);\r
560         hProcess = pi.hProcess;\r
561       }\r
562     }\r
563     if (hProcess != 0)\r
564     {\r
565       WaitForSingleObject(hProcess, INFINITE);\r
566       CloseHandle(hProcess);\r
567     }\r
568   }\r
569 \r
570   path[pathLen] = L'\0';\r
571   RemoveDirWithSubItems(path);\r
572 \r
573   if (res == SZ_OK)\r
574     return 0;\r
575   \r
576   {\r
577     if (res == SZ_ERROR_UNSUPPORTED)\r
578       errorMessage = "Decoder doesn't support this archive";\r
579     else if (res == SZ_ERROR_MEM)\r
580       errorMessage = "Can't allocate required memory";\r
581     else if (res == SZ_ERROR_CRC)\r
582       errorMessage = "CRC error";\r
583     else\r
584     {\r
585       if (!errorMessage)\r
586         errorMessage = "ERROR";\r
587     }\r
588     if (errorMessage)\r
589       PrintErrorMessage(errorMessage);\r
590   }\r
591   return 1;\r
592 }\r