files.c 16.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/*
 * Implementation of the Microsoft Installer (msi.dll)
 *
 * Copyright 2005 Aric Stewart for CodeWeavers
 *
 * 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
18
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
 */


/*
 * Actions dealing with files These are
 *
 * InstallFiles
 * DuplicateFiles
 * MoveFiles (TODO)
 * PatchFiles (TODO)
 * RemoveDuplicateFiles(TODO)
 * RemoveFiles(TODO)
 */

#include <stdarg.h>

#include "windef.h"
#include "winbase.h"
#include "winerror.h"
#include "wine/debug.h"
#include "fdi.h"
40
#include "msi.h"
41 42 43
#include "msidefs.h"
#include "msipriv.h"
#include "winuser.h"
44 45
#include "winreg.h"
#include "shlwapi.h"
46 47 48 49
#include "wine/unicode.h"

WINE_DEFAULT_DEBUG_CHANNEL(msi);

50
static void msi_file_update_ui( MSIPACKAGE *package, MSIFILE *f, const WCHAR *action )
51 52 53 54 55 56 57 58 59 60 61 62 63
{
    MSIRECORD *uirow;
    LPWSTR uipath, p;

    /* the UI chunk */
    uirow = MSI_CreateRecord( 9 );
    MSI_RecordSetStringW( uirow, 1, f->FileName );
    uipath = strdupW( f->TargetPath );
    p = strrchrW(uipath,'\\');
    if (p)
        p[1]=0;
    MSI_RecordSetStringW( uirow, 9, uipath);
    MSI_RecordSetInteger( uirow, 6, f->FileSize );
64
    ui_actiondata( package, action, uirow);
65 66 67 68 69
    msiobj_release( &uirow->hdr );
    msi_free( uipath );
    ui_progress( package, 2, f->FileSize, 0, 0);
}

70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
/* compares the version of a file read from the filesystem and
 * the version specified in the File table
 */
static int msi_compare_file_version(MSIFILE *file)
{
    WCHAR version[MAX_PATH];
    DWORD size;
    UINT r;

    size = MAX_PATH;
    version[0] = '\0';
    r = MsiGetFileVersionW(file->TargetPath, version, &size, NULL, NULL);
    if (r != ERROR_SUCCESS)
        return 0;

    return lstrcmpW(version, file->Version);
}

88
static UINT get_file_target(MSIPACKAGE *package, LPCWSTR file_key, 
89
                            MSIFILE** file)
90
{
91
    LIST_FOR_EACH_ENTRY( *file, &package->files, MSIFILE, entry )
92
    {
93
        if (lstrcmpW( file_key, (*file)->File )==0)
94
        {
95
            if ((*file)->state >= msifs_overwrite)
96 97 98 99 100 101 102 103 104
                return ERROR_SUCCESS;
            else
                return ERROR_FILE_NOT_FOUND;
        }
    }

    return ERROR_FUNCTION_FAILED;
}

105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
static void schedule_install_files(MSIPACKAGE *package)
{
    MSIFILE *file;

    LIST_FOR_EACH_ENTRY(file, &package->files, MSIFILE, entry)
    {
        if (!ACTION_VerifyComponentForAction(file->Component, INSTALLSTATE_LOCAL))
        {
            TRACE("File %s is not scheduled for install\n", debugstr_w(file->File));

            ui_progress(package,2,file->FileSize,0,0);
            file->state = msifs_skipped;
        }
    }
}

121
static UINT copy_file(MSIFILE *file, LPWSTR source)
122 123 124
{
    BOOL ret;

125
    ret = CopyFileW(source, file->TargetPath, FALSE);
126 127 128 129
    if (!ret)
        return GetLastError();

    SetFileAttributesW(file->TargetPath, FILE_ATTRIBUTE_NORMAL);
130

131 132
    file->state = msifs_installed;
    return ERROR_SUCCESS;
133 134
}

135
static UINT copy_install_file(MSIPACKAGE *package, MSIFILE *file, LPWSTR source)
136 137 138
{
    UINT gle;

139
    TRACE("Copying %s to %s\n", debugstr_w(source),
140 141
          debugstr_w(file->TargetPath));

142
    gle = copy_file(file, source);
143 144 145
    if (gle == ERROR_SUCCESS)
        return gle;

146 147 148
    if (gle == ERROR_ALREADY_EXISTS && file->state == msifs_overwrite)
    {
        TRACE("overwriting existing file\n");
149
        return ERROR_SUCCESS;
150
    }
151 152 153 154
    else if (gle == ERROR_ACCESS_DENIED)
    {
        SetFileAttributesW(file->TargetPath, FILE_ATTRIBUTE_NORMAL);

155
        gle = copy_file(file, source);
156 157
        TRACE("Overwriting existing file: %d\n", gle);
    }
158
    if (gle == ERROR_SHARING_VIOLATION || gle == ERROR_USER_MAPPED_FILE)
159 160 161 162 163 164
    {
        WCHAR tmpfileW[MAX_PATH], *pathW, *p;
        DWORD len;

        TRACE("file in use, scheduling rename operation\n");

165
        GetTempFileNameW(szBackSlash, szMsi, 0, tmpfileW);
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
        len = strlenW(file->TargetPath) + strlenW(tmpfileW) + 1;
        if (!(pathW = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR))))
            return ERROR_OUTOFMEMORY;

        strcpyW(pathW, file->TargetPath);
        if ((p = strrchrW(pathW, '\\'))) *p = 0;
        strcatW(pathW, tmpfileW);

        if (CopyFileW(source, pathW, FALSE) &&
            MoveFileExW(file->TargetPath, NULL, MOVEFILE_DELAY_UNTIL_REBOOT) &&
            MoveFileExW(pathW, file->TargetPath, MOVEFILE_DELAY_UNTIL_REBOOT))
        {
            file->state = msifs_installed;
            package->need_reboot = 1;
            gle = ERROR_SUCCESS;
        }
        else
        {
            gle = GetLastError();
            WARN("failed to schedule rename operation: %d)\n", gle);
        }
        HeapFree(GetProcessHeap(), 0, pathW);
    }
189 190 191 192

    return gle;
}

193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
static BOOL check_dest_hash_matches(MSIFILE *file)
{
    MSIFILEHASHINFO hash;
    UINT r;

    if (!file->hash.dwFileHashInfoSize)
        return FALSE;

    hash.dwFileHashInfoSize = sizeof(MSIFILEHASHINFO);
    r = MsiGetFileHashW(file->TargetPath, 0, &hash);
    if (r != ERROR_SUCCESS)
        return FALSE;

    return !memcmp(&hash, &file->hash, sizeof(MSIFILEHASHINFO));
}

209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242
static BOOL installfiles_cb(MSIPACKAGE *package, LPCWSTR file, DWORD action,
                            LPWSTR *path, DWORD *attrs, PVOID user)
{
    static MSIFILE *f = NULL;

    if (action == MSICABEXTRACT_BEGINEXTRACT)
    {
        f = get_loaded_file(package, file);
        if (!f)
        {
            WARN("unknown file in cabinet (%s)\n", debugstr_w(file));
            return FALSE;
        }

        if (f->state != msifs_missing && f->state != msifs_overwrite)
        {
            TRACE("Skipping extraction of %s\n", debugstr_w(file));
            return FALSE;
        }

        msi_file_update_ui(package, f, szInstallFiles);

        *path = strdupW(f->TargetPath);
        *attrs = f->Attributes;
    }
    else if (action == MSICABEXTRACT_FILEEXTRACTED)
    {
        f->state = msifs_installed;
        f = NULL;
    }

    return TRUE;
}

243
/*
244 245 246 247 248
 * ACTION_InstallFiles()
 * 
 * For efficiency, this is done in two passes:
 * 1) Correct all the TargetPaths and determine what files are to be installed.
 * 2) Extract Cabinets and copy files.
249
 */
250 251
UINT ACTION_InstallFiles(MSIPACKAGE *package)
{
252
    MSIMEDIAINFO *mi;
253
    UINT rc = ERROR_SUCCESS;
254
    MSIFILE *file;
255 256 257 258

    /* increment progress bar each time action data is sent */
    ui_progress(package,1,1,0,0);

259
    schedule_install_files(package);
260

261 262 263 264 265 266 267 268
    /*
     * Despite MSDN specifying that the CreateFolders action
     * should be called before InstallFiles, some installers don't
     * do that, and they seem to work correctly.  We need to create
     * directories here to make sure that the files can be copied.
     */
    msi_create_component_directories( package );

269
    mi = msi_alloc_zero( sizeof(MSIMEDIAINFO) );
270

271
    LIST_FOR_EACH_ENTRY( file, &package->files, MSIFILE, entry )
272
    {
273
        if (file->state != msifs_missing && !mi->is_continuous && file->state != msifs_overwrite)
274 275
            continue;

276 277 278 279 280 281
        if (check_dest_hash_matches(file))
        {
            TRACE("File hashes match, not overwriting\n");
            continue;
        }

282 283 284 285 286 287 288
        if (MsiGetFileVersionW(file->TargetPath, NULL, NULL, NULL, NULL) == ERROR_SUCCESS &&
            msi_compare_file_version(file) >= 0)
        {
            TRACE("Destination file version greater, not overwriting\n");
            continue;
        }

289 290
        if (file->Sequence > mi->last_sequence || mi->is_continuous ||
            (file->IsCompressed && !mi->is_extracted))
291
        {
292
            MSICABDATA data;
293

294
            rc = ready_media(package, file, mi);
295 296 297 298 299
            if (rc != ERROR_SUCCESS)
            {
                ERR("Failed to ready media\n");
                break;
            }
300

301 302
            data.mi = mi;
            data.package = package;
303 304
            data.cb = installfiles_cb;
            data.user = NULL;
305 306

            if (file->IsCompressed &&
307
                !msi_cabextract(package, mi, &data))
308 309 310 311 312
            {
                ERR("Failed to extract cabinet: %s\n", debugstr_w(mi->cabinet));
                rc = ERROR_FUNCTION_FAILED;
                break;
            }
313
        }
314

315
        if (!file->IsCompressed)
316
        {
317 318 319
            LPWSTR source = resolve_file_source(package, file);

            TRACE("file paths %s to %s\n", debugstr_w(source),
320 321
                  debugstr_w(file->TargetPath));

322
            msi_file_update_ui(package, file, szInstallFiles);
323
            rc = copy_install_file(package, file, source);
324
            if (rc != ERROR_SUCCESS)
325
            {
326
                ERR("Failed to copy %s to %s (%d)\n", debugstr_w(source),
327
                    debugstr_w(file->TargetPath), rc);
328
                rc = ERROR_INSTALL_FAILURE;
329
                msi_free(source);
330 331
                break;
            }
332 333

            msi_free(source);
334
        }
335
        else if (file->state != msifs_installed)
336
        {
337 338
            ERR("compressed file wasn't extracted (%s)\n",
                debugstr_w(file->TargetPath));
339 340
            rc = ERROR_INSTALL_FAILURE;
            break;
341
        }
342 343
    }

344
    msi_free_media_info(mi);
345 346 347
    return rc;
}

348
static UINT ITERATE_DuplicateFiles(MSIRECORD *row, LPVOID param)
349
{
350
    MSIPACKAGE *package = param;
351 352 353 354 355
    WCHAR dest_name[0x100];
    LPWSTR dest_path, dest;
    LPCWSTR file_key, component;
    DWORD sz;
    DWORD rc;
356
    MSICOMPONENT *comp;
357
    MSIFILE *file;
358

359
    component = MSI_RecordGetString(row,2);
360
    comp = get_loaded_component(package,component);
361

362
    if (!ACTION_VerifyComponentForAction( comp, INSTALLSTATE_LOCAL ))
363 364 365 366 367
    {
        TRACE("Skipping copy due to disabled component %s\n",
                        debugstr_w(component));

        /* the action taken was the same as the current install state */        
368
        comp->Action = comp->Installed;
369 370

        return ERROR_SUCCESS;
371
    }
372

373
    comp->Action = INSTALLSTATE_LOCAL;
374 375 376

    file_key = MSI_RecordGetString(row,3);
    if (!file_key)
377
    {
378 379
        ERR("Unable to get file key\n");
        return ERROR_FUNCTION_FAILED;
380 381
    }

382
    rc = get_file_target(package,file_key,&file);
383 384

    if (rc != ERROR_SUCCESS)
385
    {
386 387 388
        ERR("Original file unknown %s\n",debugstr_w(file_key));
        return ERROR_SUCCESS;
    }
389

390
    if (MSI_RecordIsNull(row,4))
391
        strcpyW(dest_name,strrchrW(file->TargetPath,'\\')+1);
392 393 394 395 396
    else
    {
        sz=0x100;
        MSI_RecordGetStringW(row,4,dest_name,&sz);
        reduce_to_longfilename(dest_name);
397
    }
398

399 400 401
    if (MSI_RecordIsNull(row,5))
    {
        LPWSTR p;
402
        dest_path = strdupW(file->TargetPath);
403 404 405 406 407 408 409 410
        p = strrchrW(dest_path,'\\');
        if (p)
            *p=0;
    }
    else
    {
        LPCWSTR destkey;
        destkey = MSI_RecordGetString(row,5);
411
        dest_path = resolve_folder(package, destkey, FALSE, FALSE, TRUE, NULL);
412
        if (!dest_path)
413
        {
414
            /* try a Property */
415
            dest_path = msi_dup_property( package, destkey );
416 417 418 419 420
            if (!dest_path)
            {
                FIXME("Unable to get destination folder, try AppSearch properties\n");
                return ERROR_SUCCESS;
            }
421
        }
422
    }
423

424
    dest = build_directory_name(2, dest_path, dest_name);
425
    create_full_pathW(dest_path);
426

427
    TRACE("Duplicating file %s to %s\n",debugstr_w(file->TargetPath),
428 429
                    debugstr_w(dest)); 

430 431
    if (strcmpW(file->TargetPath,dest))
        rc = !CopyFileW(file->TargetPath,dest,TRUE);
432 433
    else
        rc = ERROR_SUCCESS;
434

435
    if (rc != ERROR_SUCCESS)
436
        ERR("Failed to copy file %s -> %s, last error %d\n",
437
            debugstr_w(file->TargetPath), debugstr_w(dest_path), GetLastError());
438

439
    FIXME("We should track these duplicate files as well\n");   
440

441 442
    msi_free(dest_path);
    msi_free(dest);
443 444

    msi_file_update_ui(package, file, szDuplicateFiles);
445

446 447
    return ERROR_SUCCESS;
}
448

449 450 451 452 453 454 455
UINT ACTION_DuplicateFiles(MSIPACKAGE *package)
{
    UINT rc;
    MSIQUERY * view;
    static const WCHAR ExecSeqQuery[] =
        {'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
         '`','D','u','p','l','i','c','a','t','e','F','i','l','e','`',0};
456

457 458 459
    rc = MSI_DatabaseOpenViewW(package->db, ExecSeqQuery, &view);
    if (rc != ERROR_SUCCESS)
        return ERROR_SUCCESS;
460

461
    rc = MSI_IterateRecords(view, NULL, ITERATE_DuplicateFiles, package);
462
    msiobj_release(&view->hdr);
463

464 465
    return rc;
}
466

467 468 469 470
static BOOL verify_comp_for_removal(MSICOMPONENT *comp, UINT install_mode)
{
    INSTALLSTATE request = comp->ActionRequest;

471 472 473
    if (request == INSTALLSTATE_UNKNOWN)
        return FALSE;

474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494
    if (install_mode == msidbRemoveFileInstallModeOnInstall &&
        (request == INSTALLSTATE_LOCAL || request == INSTALLSTATE_SOURCE))
        return TRUE;

    if (request == INSTALLSTATE_ABSENT)
    {
        if (!comp->ComponentId)
            return FALSE;

        if (install_mode == msidbRemoveFileInstallModeOnRemove)
            return TRUE;
    }

    if (install_mode == msidbRemoveFileInstallModeOnBoth)
        return TRUE;

    return FALSE;
}

static UINT ITERATE_RemoveFiles(MSIRECORD *row, LPVOID param)
{
495
    MSIPACKAGE *package = param;
496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525
    MSICOMPONENT *comp;
    LPCWSTR component, filename, dirprop;
    UINT install_mode;
    LPWSTR dir = NULL, path = NULL;
    DWORD size;
    UINT r;

    component = MSI_RecordGetString(row, 2);
    filename = MSI_RecordGetString(row, 3);
    dirprop = MSI_RecordGetString(row, 4);
    install_mode = MSI_RecordGetInteger(row, 5);

    comp = get_loaded_component(package, component);
    if (!comp)
    {
        ERR("Invalid component: %s\n", debugstr_w(component));
        return ERROR_FUNCTION_FAILED;
    }

    if (!verify_comp_for_removal(comp, install_mode))
    {
        TRACE("Skipping removal due to missing conditions\n");
        comp->Action = comp->Installed;
        return ERROR_SUCCESS;
    }

    dir = msi_dup_property(package, dirprop);
    if (!dir)
        return ERROR_OUTOFMEMORY;

526 527
    size = (filename != NULL) ? lstrlenW(filename) : 0;
    size += lstrlenW(dir) + 2;
528 529 530 531 532 533 534 535
    path = msi_alloc(size * sizeof(WCHAR));
    if (!path)
    {
        r = ERROR_OUTOFMEMORY;
        goto done;
    }

    lstrcpyW(path, dir);
536
    PathAddBackslashW(path);
537

538 539 540 541 542 543 544 545 546 547 548 549
    if (filename)
    {
        lstrcatW(path, filename);

        TRACE("Deleting misc file: %s\n", debugstr_w(path));
        DeleteFileW(path);
    }
    else
    {
        TRACE("Removing misc directory: %s\n", debugstr_w(path));
        RemoveDirectoryW(path);
    }
550 551 552 553 554 555 556

done:
    msi_free(path);
    msi_free(dir);
    return ERROR_SUCCESS;
}

557 558
UINT ACTION_RemoveFiles( MSIPACKAGE *package )
{
559
    MSIQUERY *view;
560
    MSIFILE *file;
561 562 563 564 565 566 567 568 569 570 571 572
    UINT r;

    static const WCHAR query[] = {
        'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
        '`','R','e','m','o','v','e','F','i','l','e','`',0};

    r = MSI_DatabaseOpenViewW(package->db, query, &view);
    if (r == ERROR_SUCCESS)
    {
        MSI_IterateRecords(view, NULL, ITERATE_RemoveFiles, package);
        msiobj_release(&view->hdr);
    }
573 574 575

    LIST_FOR_EACH_ENTRY( file, &package->files, MSIFILE, entry )
    {
576 577 578
        MSIRECORD *uirow;
        LPWSTR uipath, p;

579 580 581
        if ( file->state == msifs_installed )
            ERR("removing installed file %s\n", debugstr_w(file->TargetPath));

582 583
        if ( file->Component->ActionRequest != INSTALLSTATE_ABSENT ||
             file->Component->Installed == INSTALLSTATE_SOURCE )
584 585
            continue;

586 587
        /* don't remove a file if the old file
         * is strictly newer than the version to be installed
588
         */
589
        if ( msi_compare_file_version( file ) < 0 )
590 591
            continue;

592 593
        TRACE("removing %s\n", debugstr_w(file->File) );
        if ( !DeleteFileW( file->TargetPath ) )
594
            TRACE("failed to delete %s\n",  debugstr_w(file->TargetPath));
595
        file->state = msifs_missing;
596 597 598 599 600 601 602 603 604 605 606 607 608

        /* the UI chunk */
        uirow = MSI_CreateRecord( 9 );
        MSI_RecordSetStringW( uirow, 1, file->FileName );
        uipath = strdupW( file->TargetPath );
        p = strrchrW(uipath,'\\');
        if (p)
            p[1]=0;
        MSI_RecordSetStringW( uirow, 9, uipath);
        ui_actiondata( package, szRemoveFiles, uirow);
        msiobj_release( &uirow->hdr );
        msi_free( uipath );
        /* FIXME: call ui_progress here? */
609 610 611 612
    }

    return ERROR_SUCCESS;
}