xref: /third_party/python/PC/_msi.c (revision 7db96d56)
1/* Helper library for MSI creation with Python.
2 * Copyright (C) 2005 Martin v. Löwis
3 * Licensed to PSF under a contributor agreement.
4 */
5
6#include <Python.h>
7#include <fci.h>
8#include <fcntl.h>
9#include <windows.h>
10#include <msi.h>
11#include <msiquery.h>
12#include <msidefs.h>
13#include <rpc.h>
14
15/*[clinic input]
16module _msi
17class _msi.Record "msiobj *" "&record_Type"
18class _msi.SummaryInformation "msiobj *" "&summary_Type"
19class _msi.View "msiobj *" "&msiview_Type"
20class _msi.Database "msiobj *" "&msidb_Type"
21[clinic start generated code]*/
22/*[clinic end generated code: output=da39a3ee5e6b4b0d input=89a3605762cf4bdc]*/
23
24static PyObject *MSIError;
25
26/*[clinic input]
27_msi.UuidCreate
28
29Return the string representation of a new unique identifier.
30[clinic start generated code]*/
31
32static PyObject *
33_msi_UuidCreate_impl(PyObject *module)
34/*[clinic end generated code: output=534ecf36f10af98e input=168024ab4b3e832b]*/
35{
36    UUID result;
37    wchar_t *cresult;
38    PyObject *oresult;
39
40    /* May return ok, local only, and no address.
41       For local only, the documentation says we still get a uuid.
42       For RPC_S_UUID_NO_ADDRESS, it's not clear whether we can
43       use the result. */
44    if (UuidCreate(&result) == RPC_S_UUID_NO_ADDRESS) {
45        PyErr_SetString(PyExc_NotImplementedError, "processing 'no address' result");
46        return NULL;
47    }
48
49    if (UuidToStringW(&result, &cresult) == RPC_S_OUT_OF_MEMORY) {
50        PyErr_SetString(PyExc_MemoryError, "out of memory in uuidgen");
51        return NULL;
52    }
53
54    oresult = PyUnicode_FromWideChar(cresult, wcslen(cresult));
55    RpcStringFreeW(&cresult);
56    return oresult;
57
58}
59
60/* Helper for converting file names from UTF-8 to wchat_t*.  */
61static wchar_t *
62utf8_to_wchar(const char *s, int *err)
63{
64    PyObject *obj = PyUnicode_FromString(s);
65    if (obj == NULL) {
66        if (PyErr_ExceptionMatches(PyExc_MemoryError)) {
67            *err = ENOMEM;
68        }
69        else {
70            *err = EINVAL;
71        }
72        PyErr_Clear();
73        return NULL;
74    }
75    wchar_t *ws = PyUnicode_AsWideCharString(obj, NULL);
76    if (ws == NULL) {
77        *err = ENOMEM;
78        PyErr_Clear();
79    }
80    Py_DECREF(obj);
81    return ws;
82}
83
84/* FCI callback functions */
85
86static FNFCIALLOC(cb_alloc)
87{
88    return PyMem_RawMalloc(cb);
89}
90
91static FNFCIFREE(cb_free)
92{
93    PyMem_RawFree(memory);
94}
95
96static FNFCIOPEN(cb_open)
97{
98    wchar_t *ws = utf8_to_wchar(pszFile, err);
99    if (ws == NULL) {
100        return -1;
101    }
102    int result = _wopen(ws, oflag | O_NOINHERIT, pmode);
103    PyMem_Free(ws);
104    if (result == -1)
105        *err = errno;
106    return result;
107}
108
109static FNFCIREAD(cb_read)
110{
111    UINT result = (UINT)_read((int)hf, memory, cb);
112    if (result != cb)
113        *err = errno;
114    return result;
115}
116
117static FNFCIWRITE(cb_write)
118{
119    UINT result = (UINT)_write((int)hf, memory, cb);
120    if (result != cb)
121        *err = errno;
122    return result;
123}
124
125static FNFCICLOSE(cb_close)
126{
127    int result = _close((int)hf);
128    if (result != 0)
129        *err = errno;
130    return result;
131}
132
133static FNFCISEEK(cb_seek)
134{
135    long result = (long)_lseek((int)hf, dist, seektype);
136    if (result == -1)
137        *err = errno;
138    return result;
139}
140
141static FNFCIDELETE(cb_delete)
142{
143    wchar_t *ws = utf8_to_wchar(pszFile, err);
144    if (ws == NULL) {
145        return -1;
146    }
147    int result = _wremove(ws);
148    PyMem_Free(ws);
149    if (result != 0)
150        *err = errno;
151    return result;
152}
153
154static FNFCIFILEPLACED(cb_fileplaced)
155{
156    return 0;
157}
158
159static FNFCIGETTEMPFILE(cb_gettempfile)
160{
161    char *name = _tempnam("", "tmp");
162    if ((name != NULL) && ((int)strlen(name) < cbTempName)) {
163        strcpy(pszTempName, name);
164        free(name);
165        return TRUE;
166    }
167
168    if (name) free(name);
169    return FALSE;
170}
171
172static FNFCISTATUS(cb_status)
173{
174    if (pv) {
175        _Py_IDENTIFIER(status);
176
177        PyObject *result = _PyObject_CallMethodId(pv, &PyId_status, "iii", typeStatus, cb1, cb2);
178        if (result == NULL)
179            return -1;
180        Py_DECREF(result);
181    }
182    return 0;
183}
184
185static FNFCIGETNEXTCABINET(cb_getnextcabinet)
186{
187    if (pv) {
188        _Py_IDENTIFIER(getnextcabinet);
189
190        PyObject *result = _PyObject_CallMethodId(pv, &PyId_getnextcabinet, "i", pccab->iCab);
191        if (result == NULL)
192            return -1;
193        if (!PyBytes_Check(result)) {
194            PyErr_Format(PyExc_TypeError,
195                "Incorrect return type %s from getnextcabinet",
196                Py_TYPE(result)->tp_name);
197            Py_DECREF(result);
198            return FALSE;
199        }
200        strncpy(pccab->szCab, PyBytes_AsString(result), sizeof(pccab->szCab));
201        return TRUE;
202    }
203    return FALSE;
204}
205
206static FNFCIGETOPENINFO(cb_getopeninfo)
207{
208    BY_HANDLE_FILE_INFORMATION bhfi;
209    FILETIME filetime;
210    HANDLE handle;
211
212    wchar_t *ws = utf8_to_wchar(pszName, err);
213    if (ws == NULL) {
214        return -1;
215    }
216
217    /* Need Win32 handle to get time stamps */
218    handle = CreateFileW(ws, GENERIC_READ, FILE_SHARE_READ, NULL,
219        OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
220    if (handle == INVALID_HANDLE_VALUE) {
221        PyMem_Free(ws);
222        return -1;
223    }
224
225    if (GetFileInformationByHandle(handle, &bhfi) == FALSE) {
226        CloseHandle(handle);
227        PyMem_Free(ws);
228        return -1;
229    }
230
231    FileTimeToLocalFileTime(&bhfi.ftLastWriteTime, &filetime);
232    FileTimeToDosDateTime(&filetime, pdate, ptime);
233
234    *pattribs = (int)(bhfi.dwFileAttributes &
235        (_A_RDONLY | _A_SYSTEM | _A_HIDDEN | _A_ARCH));
236
237    CloseHandle(handle);
238
239    int result = _wopen(ws, _O_RDONLY | _O_BINARY | O_NOINHERIT);
240    PyMem_Free(ws);
241    return result;
242}
243
244/*[clinic input]
245_msi.FCICreate
246    cabname: str
247        the name of the CAB file
248    files: object
249        a list of tuples, each containing the name of the file on disk,
250        and the name of the file inside the CAB file
251    /
252
253Create a new CAB file.
254[clinic start generated code]*/
255
256static PyObject *
257_msi_FCICreate_impl(PyObject *module, const char *cabname, PyObject *files)
258/*[clinic end generated code: output=55dc05728361b799 input=1d2d75fdc8b44b71]*/
259{
260    const char *p;
261    CCAB ccab;
262    HFCI hfci;
263    ERF erf;
264    Py_ssize_t i;
265
266    if (!PyList_Check(files)) {
267        PyErr_SetString(PyExc_TypeError, "FCICreate expects a list");
268        return NULL;
269    }
270
271    ccab.cb = INT_MAX; /* no need to split CAB into multiple media */
272    ccab.cbFolderThresh = 1000000; /* flush directory after this many bytes */
273    ccab.cbReserveCFData = 0;
274    ccab.cbReserveCFFolder = 0;
275    ccab.cbReserveCFHeader = 0;
276
277    ccab.iCab = 1;
278    ccab.iDisk = 1;
279
280    ccab.setID = 0;
281    ccab.szDisk[0] = '\0';
282
283    for (i = 0, p = cabname; *p; p++)
284        if (*p == '\\' || *p == '/')
285            i = p - cabname + 1;
286
287    if (i >= sizeof(ccab.szCabPath) ||
288        strlen(cabname+i) >= sizeof(ccab.szCab)) {
289        PyErr_SetString(PyExc_ValueError, "path name too long");
290        return 0;
291    }
292
293    if (i > 0) {
294        memcpy(ccab.szCabPath, cabname, i);
295        ccab.szCabPath[i] = '\0';
296        strcpy(ccab.szCab, cabname+i);
297    } else {
298        strcpy(ccab.szCabPath, ".\\");
299        strcpy(ccab.szCab, cabname);
300    }
301
302    hfci = FCICreate(&erf, cb_fileplaced, cb_alloc, cb_free,
303        cb_open, cb_read, cb_write, cb_close, cb_seek, cb_delete,
304        cb_gettempfile, &ccab, NULL);
305
306    if (hfci == NULL) {
307        PyErr_Format(PyExc_ValueError, "FCI error %d", erf.erfOper);
308        return NULL;
309    }
310
311    for (i=0; i < PyList_GET_SIZE(files); i++) {
312        PyObject *item = PyList_GET_ITEM(files, i);
313        char *filename, *cabname;
314
315        if (!PyArg_ParseTuple(item, "ss", &filename, &cabname)) {
316            PyErr_SetString(PyExc_TypeError, "FCICreate expects a list of tuples containing two strings");
317            FCIDestroy(hfci);
318            return NULL;
319        }
320
321        if (!FCIAddFile(hfci, filename, cabname, FALSE,
322            cb_getnextcabinet, cb_status, cb_getopeninfo,
323            tcompTYPE_MSZIP))
324            goto err;
325    }
326
327    if (!FCIFlushCabinet(hfci, FALSE, cb_getnextcabinet, cb_status))
328        goto err;
329
330    if (!FCIDestroy(hfci))
331        goto err;
332
333    Py_RETURN_NONE;
334err:
335    if(erf.fError)
336        PyErr_Format(PyExc_ValueError, "FCI error %d", erf.erfOper); /* XXX better error type */
337    else
338        PyErr_SetString(PyExc_ValueError, "FCI general error");
339
340    FCIDestroy(hfci);
341    return NULL;
342}
343
344typedef struct msiobj{
345    PyObject_HEAD
346    MSIHANDLE h;
347}msiobj;
348
349static void
350msiobj_dealloc(msiobj* msidb)
351{
352    MsiCloseHandle(msidb->h);
353    msidb->h = 0;
354    PyObject_Free(msidb);
355}
356
357static PyObject*
358msierror(int status)
359{
360    int code;
361    char buf[2000];
362    char *res = buf;
363    DWORD size = Py_ARRAY_LENGTH(buf);
364    MSIHANDLE err = MsiGetLastErrorRecord();
365
366    if (err == 0) {
367        switch(status) {
368        case ERROR_ACCESS_DENIED:
369            PyErr_SetString(MSIError, "access denied");
370            return NULL;
371        case ERROR_FUNCTION_FAILED:
372            PyErr_SetString(MSIError, "function failed");
373            return NULL;
374        case ERROR_INVALID_DATA:
375            PyErr_SetString(MSIError, "invalid data");
376            return NULL;
377        case ERROR_INVALID_HANDLE:
378            PyErr_SetString(MSIError, "invalid handle");
379            return NULL;
380        case ERROR_INVALID_STATE:
381            PyErr_SetString(MSIError, "invalid state");
382            return NULL;
383        case ERROR_INVALID_PARAMETER:
384            PyErr_SetString(MSIError, "invalid parameter");
385            return NULL;
386        case ERROR_OPEN_FAILED:
387            PyErr_SetString(MSIError, "open failed");
388            return NULL;
389        case ERROR_CREATE_FAILED:
390            PyErr_SetString(MSIError, "create failed");
391            return NULL;
392        default:
393            PyErr_Format(MSIError, "unknown error %x", status);
394            return NULL;
395        }
396    }
397
398    code = MsiRecordGetInteger(err, 1); /* XXX code */
399    if (MsiFormatRecord(0, err, res, &size) == ERROR_MORE_DATA) {
400        res = malloc(size+1);
401        if (res == NULL) {
402            MsiCloseHandle(err);
403            return PyErr_NoMemory();
404        }
405        MsiFormatRecord(0, err, res, &size);
406        res[size]='\0';
407    }
408    MsiCloseHandle(err);
409    PyErr_SetString(MSIError, res);
410    if (res != buf)
411        free(res);
412    return NULL;
413}
414
415#include "clinic/_msi.c.h"
416
417/*[clinic input]
418_msi.Database.Close
419
420Close the database object.
421[clinic start generated code]*/
422
423static PyObject *
424_msi_Database_Close_impl(msiobj *self)
425/*[clinic end generated code: output=ddf2d7712ea804f1 input=104330ce4a486187]*/
426{
427    int status;
428    if ((status = MsiCloseHandle(self->h)) != ERROR_SUCCESS) {
429        return msierror(status);
430    }
431    self->h = 0;
432    Py_RETURN_NONE;
433}
434
435/*************************** Record objects **********************/
436
437/*[clinic input]
438_msi.Record.GetFieldCount
439
440Return the number of fields of the record.
441[clinic start generated code]*/
442
443static PyObject *
444_msi_Record_GetFieldCount_impl(msiobj *self)
445/*[clinic end generated code: output=112795079c904398 input=5fb9d4071b28897b]*/
446{
447    return PyLong_FromLong(MsiRecordGetFieldCount(self->h));
448}
449
450/*[clinic input]
451_msi.Record.GetInteger
452    field: unsigned_int(bitwise=True)
453    /
454
455Return the value of field as an integer where possible.
456[clinic start generated code]*/
457
458static PyObject *
459_msi_Record_GetInteger_impl(msiobj *self, unsigned int field)
460/*[clinic end generated code: output=7174ebb6e8ed1c79 input=d19209947e2bfe61]*/
461{
462    int status;
463
464    status = MsiRecordGetInteger(self->h, field);
465    if (status == MSI_NULL_INTEGER){
466        PyErr_SetString(MSIError, "could not convert record field to integer");
467        return NULL;
468    }
469    return PyLong_FromLong((long) status);
470}
471
472/*[clinic input]
473_msi.Record.GetString
474    field: unsigned_int(bitwise=True)
475    /
476
477Return the value of field as a string where possible.
478[clinic start generated code]*/
479
480static PyObject *
481_msi_Record_GetString_impl(msiobj *self, unsigned int field)
482/*[clinic end generated code: output=f670d1b484cfa47c input=ffa11f21450b77d8]*/
483{
484    unsigned int status;
485    WCHAR buf[2000];
486    WCHAR *res = buf;
487    DWORD size = Py_ARRAY_LENGTH(buf);
488    PyObject* string;
489
490    status = MsiRecordGetStringW(self->h, field, res, &size);
491    if (status == ERROR_MORE_DATA) {
492        res = (WCHAR*) malloc((size + 1)*sizeof(WCHAR));
493        if (res == NULL)
494            return PyErr_NoMemory();
495        status = MsiRecordGetStringW(self->h, field, res, &size);
496    }
497    if (status != ERROR_SUCCESS)
498        return msierror((int) status);
499    string = PyUnicode_FromWideChar(res, size);
500    if (buf != res)
501        free(res);
502    return string;
503}
504
505/*[clinic input]
506_msi.Record.ClearData
507
508Set all fields of the record to 0.
509[clinic start generated code]*/
510
511static PyObject *
512_msi_Record_ClearData_impl(msiobj *self)
513/*[clinic end generated code: output=1891467214b977f4 input=2a911c95aaded102]*/
514{
515    int status = MsiRecordClearData(self->h);
516    if (status != ERROR_SUCCESS)
517        return msierror(status);
518
519    Py_RETURN_NONE;
520}
521
522/*[clinic input]
523_msi.Record.SetString
524    field: int
525    value: Py_UNICODE
526    /
527
528Set field to a string value.
529[clinic start generated code]*/
530
531static PyObject *
532_msi_Record_SetString_impl(msiobj *self, int field, const Py_UNICODE *value)
533/*[clinic end generated code: output=2e37505b0f11f985 input=fb8ec70a2a6148e0]*/
534{
535    int status;
536
537    if ((status = MsiRecordSetStringW(self->h, field, value)) != ERROR_SUCCESS)
538        return msierror(status);
539
540    Py_RETURN_NONE;
541}
542
543/*[clinic input]
544_msi.Record.SetStream
545    field: int
546    value: Py_UNICODE
547    /
548
549Set field to the contents of the file named value.
550[clinic start generated code]*/
551
552static PyObject *
553_msi_Record_SetStream_impl(msiobj *self, int field, const Py_UNICODE *value)
554/*[clinic end generated code: output=442facac16913b48 input=a07aa19b865e8292]*/
555{
556    int status;
557
558    if ((status = MsiRecordSetStreamW(self->h, field, value)) != ERROR_SUCCESS)
559        return msierror(status);
560
561    Py_RETURN_NONE;
562}
563
564/*[clinic input]
565_msi.Record.SetInteger
566    field: int
567    value: int
568    /
569
570Set field to an integer value.
571[clinic start generated code]*/
572
573static PyObject *
574_msi_Record_SetInteger_impl(msiobj *self, int field, int value)
575/*[clinic end generated code: output=669e8647775d0ce7 input=c571aa775e7e451b]*/
576{
577    int status;
578
579    if ((status = MsiRecordSetInteger(self->h, field, value)) != ERROR_SUCCESS)
580        return msierror(status);
581
582    Py_RETURN_NONE;
583}
584
585
586
587static PyMethodDef record_methods[] = {
588    _MSI_RECORD_GETFIELDCOUNT_METHODDEF
589    _MSI_RECORD_GETINTEGER_METHODDEF
590    _MSI_RECORD_GETSTRING_METHODDEF
591    _MSI_RECORD_SETSTRING_METHODDEF
592    _MSI_RECORD_SETSTREAM_METHODDEF
593    _MSI_RECORD_SETINTEGER_METHODDEF
594    _MSI_RECORD_CLEARDATA_METHODDEF
595    { NULL, NULL }
596};
597
598static PyTypeObject record_Type = {
599        PyVarObject_HEAD_INIT(NULL, 0)
600        "_msi.Record",          /*tp_name*/
601        sizeof(msiobj), /*tp_basicsize*/
602        0,                      /*tp_itemsize*/
603        /* methods */
604        (destructor)msiobj_dealloc, /*tp_dealloc*/
605        0,                      /*tp_vectorcall_offset*/
606        0,                      /*tp_getattr*/
607        0,                      /*tp_setattr*/
608        0,                      /*tp_as_async*/
609        0,                      /*tp_repr*/
610        0,                      /*tp_as_number*/
611        0,                      /*tp_as_sequence*/
612        0,                      /*tp_as_mapping*/
613        0,                      /*tp_hash*/
614        0,                      /*tp_call*/
615        0,                      /*tp_str*/
616        PyObject_GenericGetAttr,/*tp_getattro*/
617        PyObject_GenericSetAttr,/*tp_setattro*/
618        0,                      /*tp_as_buffer*/
619        Py_TPFLAGS_DEFAULT,     /*tp_flags*/
620        0,                      /*tp_doc*/
621        0,                      /*tp_traverse*/
622        0,                      /*tp_clear*/
623        0,                      /*tp_richcompare*/
624        0,                      /*tp_weaklistoffset*/
625        0,                      /*tp_iter*/
626        0,                      /*tp_iternext*/
627        record_methods,           /*tp_methods*/
628        0,                      /*tp_members*/
629        0,                      /*tp_getset*/
630        0,                      /*tp_base*/
631        0,                      /*tp_dict*/
632        0,                      /*tp_descr_get*/
633        0,                      /*tp_descr_set*/
634        0,                      /*tp_dictoffset*/
635        0,                      /*tp_init*/
636        0,                      /*tp_alloc*/
637        0,                      /*tp_new*/
638        0,                      /*tp_free*/
639        0,                      /*tp_is_gc*/
640};
641
642static PyObject*
643record_new(MSIHANDLE h)
644{
645    msiobj *result = PyObject_New(struct msiobj, &record_Type);
646
647    if (!result) {
648        MsiCloseHandle(h);
649        return NULL;
650    }
651
652    result->h = h;
653    return (PyObject*)result;
654}
655
656/*************************** SummaryInformation objects **************/
657
658/*[clinic input]
659_msi.SummaryInformation.GetProperty
660    field: int
661        the name of the property, one of the PID_* constants
662    /
663
664Return a property of the summary.
665[clinic start generated code]*/
666
667static PyObject *
668_msi_SummaryInformation_GetProperty_impl(msiobj *self, int field)
669/*[clinic end generated code: output=f8946a33ee14f6ef input=f8dfe2c890d6cb8b]*/
670{
671    int status;
672    PyObject *result;
673    UINT type;
674    INT ival;
675    FILETIME fval;
676    char sbuf[1000];
677    char *sval = sbuf;
678    DWORD ssize = sizeof(sbuf);
679
680    status = MsiSummaryInfoGetProperty(self->h, field, &type, &ival,
681        &fval, sval, &ssize);
682    if (status == ERROR_MORE_DATA) {
683        ssize++;
684        sval = malloc(ssize);
685        if (sval == NULL) {
686            return PyErr_NoMemory();
687        }
688        status = MsiSummaryInfoGetProperty(self->h, field, &type, &ival,
689            &fval, sval, &ssize);
690    }
691    if (status != ERROR_SUCCESS) {
692        return msierror(status);
693    }
694
695    switch(type) {
696        case VT_I2:
697        case VT_I4:
698            result = PyLong_FromLong(ival);
699            break;
700        case VT_FILETIME:
701            PyErr_SetString(PyExc_NotImplementedError, "FILETIME result");
702            result = NULL;
703            break;
704        case VT_LPSTR:
705            result = PyBytes_FromStringAndSize(sval, ssize);
706            break;
707        case VT_EMPTY:
708            Py_INCREF(Py_None);
709            result = Py_None;
710            break;
711        default:
712            PyErr_Format(PyExc_NotImplementedError, "result of type %d", type);
713            result = NULL;
714            break;
715    }
716    if (sval != sbuf)
717        free(sval);
718    return result;
719}
720
721/*[clinic input]
722_msi.SummaryInformation.GetPropertyCount
723
724Return the number of summary properties.
725[clinic start generated code]*/
726
727static PyObject *
728_msi_SummaryInformation_GetPropertyCount_impl(msiobj *self)
729/*[clinic end generated code: output=68e94b2aeee92b3d input=2e71e985586d82dc]*/
730{
731    int status;
732    UINT result;
733
734    status = MsiSummaryInfoGetPropertyCount(self->h, &result);
735    if (status != ERROR_SUCCESS)
736        return msierror(status);
737
738    return PyLong_FromLong(result);
739}
740
741/*[clinic input]
742_msi.SummaryInformation.SetProperty
743    field: int
744        the name of the property, one of the PID_* constants
745    value as data: object
746        the new value of the property (integer or string)
747    /
748
749Set a property.
750[clinic start generated code]*/
751
752static PyObject *
753_msi_SummaryInformation_SetProperty_impl(msiobj *self, int field,
754                                         PyObject *data)
755/*[clinic end generated code: output=3d4692c8984bb675 input=f2a7811b905abbed]*/
756{
757    int status;
758
759    if (PyUnicode_Check(data)) {
760#if USE_UNICODE_WCHAR_CACHE
761        const WCHAR *value = _PyUnicode_AsUnicode(data);
762#else /* USE_UNICODE_WCHAR_CACHE */
763        WCHAR *value = PyUnicode_AsWideCharString(data, NULL);
764#endif /* USE_UNICODE_WCHAR_CACHE */
765        if (value == NULL) {
766            return NULL;
767        }
768        status = MsiSummaryInfoSetPropertyW(self->h, field, VT_LPSTR,
769            0, NULL, value);
770#if !USE_UNICODE_WCHAR_CACHE
771        PyMem_Free(value);
772#endif /* USE_UNICODE_WCHAR_CACHE */
773    } else if (PyLong_CheckExact(data)) {
774        long value = PyLong_AsLong(data);
775        if (value == -1 && PyErr_Occurred()) {
776            return NULL;
777        }
778        status = MsiSummaryInfoSetProperty(self->h, field, VT_I4,
779            value, NULL, NULL);
780    } else {
781        PyErr_SetString(PyExc_TypeError, "unsupported type");
782        return NULL;
783    }
784
785    if (status != ERROR_SUCCESS)
786        return msierror(status);
787
788    Py_RETURN_NONE;
789}
790
791
792/*[clinic input]
793_msi.SummaryInformation.Persist
794
795Write the modified properties to the summary information stream.
796[clinic start generated code]*/
797
798static PyObject *
799_msi_SummaryInformation_Persist_impl(msiobj *self)
800/*[clinic end generated code: output=c564bd17f5e122c9 input=e3dda9d530095ef7]*/
801{
802    int status;
803
804    status = MsiSummaryInfoPersist(self->h);
805    if (status != ERROR_SUCCESS)
806        return msierror(status);
807    Py_RETURN_NONE;
808}
809
810static PyMethodDef summary_methods[] = {
811    _MSI_SUMMARYINFORMATION_GETPROPERTY_METHODDEF
812    _MSI_SUMMARYINFORMATION_GETPROPERTYCOUNT_METHODDEF
813    _MSI_SUMMARYINFORMATION_SETPROPERTY_METHODDEF
814    _MSI_SUMMARYINFORMATION_PERSIST_METHODDEF
815    { NULL, NULL }
816};
817
818static PyTypeObject summary_Type = {
819        PyVarObject_HEAD_INIT(NULL, 0)
820        "_msi.SummaryInformation",              /*tp_name*/
821        sizeof(msiobj), /*tp_basicsize*/
822        0,                      /*tp_itemsize*/
823        /* methods */
824        (destructor)msiobj_dealloc, /*tp_dealloc*/
825        0,                      /*tp_vectorcall_offset*/
826        0,                      /*tp_getattr*/
827        0,                      /*tp_setattr*/
828        0,                      /*tp_as_async*/
829        0,                      /*tp_repr*/
830        0,                      /*tp_as_number*/
831        0,                      /*tp_as_sequence*/
832        0,                      /*tp_as_mapping*/
833        0,                      /*tp_hash*/
834        0,                      /*tp_call*/
835        0,                      /*tp_str*/
836        PyObject_GenericGetAttr,/*tp_getattro*/
837        PyObject_GenericSetAttr,/*tp_setattro*/
838        0,                      /*tp_as_buffer*/
839        Py_TPFLAGS_DEFAULT,     /*tp_flags*/
840        0,                      /*tp_doc*/
841        0,                      /*tp_traverse*/
842        0,                      /*tp_clear*/
843        0,                      /*tp_richcompare*/
844        0,                      /*tp_weaklistoffset*/
845        0,                      /*tp_iter*/
846        0,                      /*tp_iternext*/
847        summary_methods,        /*tp_methods*/
848        0,                      /*tp_members*/
849        0,                      /*tp_getset*/
850        0,                      /*tp_base*/
851        0,                      /*tp_dict*/
852        0,                      /*tp_descr_get*/
853        0,                      /*tp_descr_set*/
854        0,                      /*tp_dictoffset*/
855        0,                      /*tp_init*/
856        0,                      /*tp_alloc*/
857        0,                      /*tp_new*/
858        0,                      /*tp_free*/
859        0,                      /*tp_is_gc*/
860};
861
862/*************************** View objects **************/
863
864/*[clinic input]
865_msi.View.Execute
866    params as oparams: object
867        a record describing actual values of the parameter tokens
868        in the query or None
869    /
870
871Execute the SQL query of the view.
872[clinic start generated code]*/
873
874static PyObject *
875_msi_View_Execute(msiobj *self, PyObject *oparams)
876/*[clinic end generated code: output=f0f65fd2900bcb4e input=cb163a15d453348e]*/
877{
878    int status;
879    MSIHANDLE params = 0;
880
881    if (oparams != Py_None) {
882        if (!Py_IS_TYPE(oparams, &record_Type)) {
883            PyErr_SetString(PyExc_TypeError, "Execute argument must be a record");
884            return NULL;
885        }
886        params = ((msiobj*)oparams)->h;
887    }
888
889    status = MsiViewExecute(self->h, params);
890    if (status != ERROR_SUCCESS)
891        return msierror(status);
892
893    Py_RETURN_NONE;
894}
895
896/*[clinic input]
897_msi.View.Fetch
898
899Return a result record of the query.
900[clinic start generated code]*/
901
902static PyObject *
903_msi_View_Fetch_impl(msiobj *self)
904/*[clinic end generated code: output=ba154a3794537d4e input=7f3e3d06c449001c]*/
905{
906    int status;
907    MSIHANDLE result;
908
909    status = MsiViewFetch(self->h, &result);
910    if (status == ERROR_NO_MORE_ITEMS) {
911        Py_RETURN_NONE;
912    } else if (status != ERROR_SUCCESS) {
913        return msierror(status);
914    }
915
916    return record_new(result);
917}
918
919/*[clinic input]
920_msi.View.GetColumnInfo
921    kind: int
922        MSICOLINFO_NAMES or MSICOLINFO_TYPES
923    /
924
925Return a record describing the columns of the view.
926[clinic start generated code]*/
927
928static PyObject *
929_msi_View_GetColumnInfo_impl(msiobj *self, int kind)
930/*[clinic end generated code: output=e7c1697db9403660 input=afedb892bf564a3b]*/
931{
932    int status;
933    MSIHANDLE result;
934
935    if ((status = MsiViewGetColumnInfo(self->h, kind, &result)) != ERROR_SUCCESS)
936        return msierror(status);
937
938    return record_new(result);
939}
940
941/*[clinic input]
942_msi.View.Modify
943    kind: int
944        one of the MSIMODIFY_* constants
945    data: object
946        a record describing the new data
947    /
948
949Modify the view.
950[clinic start generated code]*/
951
952static PyObject *
953_msi_View_Modify_impl(msiobj *self, int kind, PyObject *data)
954/*[clinic end generated code: output=69aaf3ce8ddac0ba input=2828de22de0d47b4]*/
955{
956    int status;
957
958    if (!Py_IS_TYPE(data, &record_Type)) {
959        PyErr_SetString(PyExc_TypeError, "Modify expects a record object");
960        return NULL;
961    }
962
963    if ((status = MsiViewModify(self->h, kind, ((msiobj*)data)->h)) != ERROR_SUCCESS)
964        return msierror(status);
965
966    Py_RETURN_NONE;
967}
968
969/*[clinic input]
970_msi.View.Close
971
972Close the view.
973[clinic start generated code]*/
974
975static PyObject *
976_msi_View_Close_impl(msiobj *self)
977/*[clinic end generated code: output=488f7b8645ca104a input=de6927d1308c401c]*/
978{
979    int status;
980
981    if ((status = MsiViewClose(self->h)) != ERROR_SUCCESS)
982        return msierror(status);
983
984    Py_RETURN_NONE;
985}
986
987static PyMethodDef view_methods[] = {
988    _MSI_VIEW_EXECUTE_METHODDEF
989    _MSI_VIEW_GETCOLUMNINFO_METHODDEF
990    _MSI_VIEW_FETCH_METHODDEF
991    _MSI_VIEW_MODIFY_METHODDEF
992    _MSI_VIEW_CLOSE_METHODDEF
993    { NULL, NULL }
994};
995
996static PyTypeObject msiview_Type = {
997        PyVarObject_HEAD_INIT(NULL, 0)
998        "_msi.View",            /*tp_name*/
999        sizeof(msiobj), /*tp_basicsize*/
1000        0,                      /*tp_itemsize*/
1001        /* methods */
1002        (destructor)msiobj_dealloc, /*tp_dealloc*/
1003        0,                      /*tp_vectorcall_offset*/
1004        0,                      /*tp_getattr*/
1005        0,                      /*tp_setattr*/
1006        0,                      /*tp_as_async*/
1007        0,                      /*tp_repr*/
1008        0,                      /*tp_as_number*/
1009        0,                      /*tp_as_sequence*/
1010        0,                      /*tp_as_mapping*/
1011        0,                      /*tp_hash*/
1012        0,                      /*tp_call*/
1013        0,                      /*tp_str*/
1014        PyObject_GenericGetAttr,/*tp_getattro*/
1015        PyObject_GenericSetAttr,/*tp_setattro*/
1016        0,                      /*tp_as_buffer*/
1017        Py_TPFLAGS_DEFAULT,     /*tp_flags*/
1018        0,                      /*tp_doc*/
1019        0,                      /*tp_traverse*/
1020        0,                      /*tp_clear*/
1021        0,                      /*tp_richcompare*/
1022        0,                      /*tp_weaklistoffset*/
1023        0,                      /*tp_iter*/
1024        0,                      /*tp_iternext*/
1025        view_methods,           /*tp_methods*/
1026        0,                      /*tp_members*/
1027        0,                      /*tp_getset*/
1028        0,                      /*tp_base*/
1029        0,                      /*tp_dict*/
1030        0,                      /*tp_descr_get*/
1031        0,                      /*tp_descr_set*/
1032        0,                      /*tp_dictoffset*/
1033        0,                      /*tp_init*/
1034        0,                      /*tp_alloc*/
1035        0,                      /*tp_new*/
1036        0,                      /*tp_free*/
1037        0,                      /*tp_is_gc*/
1038};
1039
1040/*************************** Database objects **************/
1041
1042/*[clinic input]
1043_msi.Database.OpenView
1044    sql: Py_UNICODE
1045        the SQL statement to execute
1046    /
1047
1048Return a view object.
1049[clinic start generated code]*/
1050
1051static PyObject *
1052_msi_Database_OpenView_impl(msiobj *self, const Py_UNICODE *sql)
1053/*[clinic end generated code: output=e712e6a11229abfd input=50f1771f37e500df]*/
1054{
1055    int status;
1056    MSIHANDLE hView;
1057    msiobj *result;
1058
1059    if ((status = MsiDatabaseOpenViewW(self->h, sql, &hView)) != ERROR_SUCCESS)
1060        return msierror(status);
1061
1062    result = PyObject_New(struct msiobj, &msiview_Type);
1063    if (!result) {
1064        MsiCloseHandle(hView);
1065        return NULL;
1066    }
1067
1068    result->h = hView;
1069    return (PyObject*)result;
1070}
1071
1072/*[clinic input]
1073_msi.Database.Commit
1074
1075Commit the changes pending in the current transaction.
1076[clinic start generated code]*/
1077
1078static PyObject *
1079_msi_Database_Commit_impl(msiobj *self)
1080/*[clinic end generated code: output=f33021feb8b0cdd8 input=375bb120d402266d]*/
1081{
1082    int status;
1083
1084    if ((status = MsiDatabaseCommit(self->h)) != ERROR_SUCCESS)
1085        return msierror(status);
1086
1087    Py_RETURN_NONE;
1088}
1089
1090/*[clinic input]
1091_msi.Database.GetSummaryInformation
1092    count: int
1093        the maximum number of updated values
1094    /
1095
1096Return a new summary information object.
1097[clinic start generated code]*/
1098
1099static PyObject *
1100_msi_Database_GetSummaryInformation_impl(msiobj *self, int count)
1101/*[clinic end generated code: output=781e51a4ea4da847 input=18a899ead6521735]*/
1102{
1103    int status;
1104    MSIHANDLE result;
1105    msiobj *oresult;
1106
1107    status = MsiGetSummaryInformation(self->h, NULL, count, &result);
1108    if (status != ERROR_SUCCESS)
1109        return msierror(status);
1110
1111    oresult = PyObject_New(struct msiobj, &summary_Type);
1112    if (!oresult) {
1113        MsiCloseHandle(result);
1114        return NULL;
1115    }
1116
1117    oresult->h = result;
1118    return (PyObject*)oresult;
1119}
1120
1121static PyMethodDef db_methods[] = {
1122    _MSI_DATABASE_OPENVIEW_METHODDEF
1123    _MSI_DATABASE_COMMIT_METHODDEF
1124    _MSI_DATABASE_GETSUMMARYINFORMATION_METHODDEF
1125    _MSI_DATABASE_CLOSE_METHODDEF
1126    { NULL, NULL }
1127};
1128
1129static PyTypeObject msidb_Type = {
1130        PyVarObject_HEAD_INIT(NULL, 0)
1131        "_msi.Database",                /*tp_name*/
1132        sizeof(msiobj), /*tp_basicsize*/
1133        0,                      /*tp_itemsize*/
1134        /* methods */
1135        (destructor)msiobj_dealloc, /*tp_dealloc*/
1136        0,                      /*tp_vectorcall_offset*/
1137        0,                      /*tp_getattr*/
1138        0,                      /*tp_setattr*/
1139        0,                      /*tp_as_async*/
1140        0,                      /*tp_repr*/
1141        0,                      /*tp_as_number*/
1142        0,                      /*tp_as_sequence*/
1143        0,                      /*tp_as_mapping*/
1144        0,                      /*tp_hash*/
1145        0,                      /*tp_call*/
1146        0,                      /*tp_str*/
1147        PyObject_GenericGetAttr,/*tp_getattro*/
1148        PyObject_GenericSetAttr,/*tp_setattro*/
1149        0,                      /*tp_as_buffer*/
1150        Py_TPFLAGS_DEFAULT,     /*tp_flags*/
1151        0,                      /*tp_doc*/
1152        0,                      /*tp_traverse*/
1153        0,                      /*tp_clear*/
1154        0,                      /*tp_richcompare*/
1155        0,                      /*tp_weaklistoffset*/
1156        0,                      /*tp_iter*/
1157        0,                      /*tp_iternext*/
1158        db_methods,             /*tp_methods*/
1159        0,                      /*tp_members*/
1160        0,                      /*tp_getset*/
1161        0,                      /*tp_base*/
1162        0,                      /*tp_dict*/
1163        0,                      /*tp_descr_get*/
1164        0,                      /*tp_descr_set*/
1165        0,                      /*tp_dictoffset*/
1166        0,                      /*tp_init*/
1167        0,                      /*tp_alloc*/
1168        0,                      /*tp_new*/
1169        0,                      /*tp_free*/
1170        0,                      /*tp_is_gc*/
1171};
1172
1173#define Py_NOT_PERSIST(x, flag)                        \
1174    (x != (SIZE_T)(flag) &&                      \
1175    x != ((SIZE_T)(flag) | MSIDBOPEN_PATCHFILE))
1176
1177#define Py_INVALID_PERSIST(x)                \
1178    (Py_NOT_PERSIST(x, MSIDBOPEN_READONLY) &&  \
1179    Py_NOT_PERSIST(x, MSIDBOPEN_TRANSACT) &&   \
1180    Py_NOT_PERSIST(x, MSIDBOPEN_DIRECT) &&     \
1181    Py_NOT_PERSIST(x, MSIDBOPEN_CREATE) &&     \
1182    Py_NOT_PERSIST(x, MSIDBOPEN_CREATEDIRECT))
1183
1184/*[clinic input]
1185_msi.OpenDatabase
1186    path: Py_UNICODE
1187        the file name of the MSI file
1188    persist: int
1189        the persistence mode
1190    /
1191
1192Return a new database object.
1193[clinic start generated code]*/
1194
1195static PyObject *
1196_msi_OpenDatabase_impl(PyObject *module, const Py_UNICODE *path, int persist)
1197/*[clinic end generated code: output=d34b7202b745de05 input=1300f3b97659559b]*/
1198{
1199    int status;
1200    MSIHANDLE h;
1201    msiobj *result;
1202
1203    /* We need to validate that persist is a valid MSIDBOPEN_* value. Otherwise,
1204       MsiOpenDatabase may treat the value as a pointer, leading to unexpected
1205       behavior. */
1206    if (Py_INVALID_PERSIST(persist))
1207        return msierror(ERROR_INVALID_PARAMETER);
1208    status = MsiOpenDatabaseW(path, (LPCWSTR)(SIZE_T)persist, &h);
1209    if (status != ERROR_SUCCESS)
1210        return msierror(status);
1211
1212    result = PyObject_New(struct msiobj, &msidb_Type);
1213    if (!result) {
1214        MsiCloseHandle(h);
1215        return NULL;
1216    }
1217    result->h = h;
1218    return (PyObject*)result;
1219}
1220
1221/*[clinic input]
1222_msi.CreateRecord
1223    count: int
1224        the number of fields of the record
1225    /
1226
1227Return a new record object.
1228[clinic start generated code]*/
1229
1230static PyObject *
1231_msi_CreateRecord_impl(PyObject *module, int count)
1232/*[clinic end generated code: output=0ba0a00beea3e99e input=53f17d5b5d9b077d]*/
1233{
1234    MSIHANDLE h;
1235
1236    h = MsiCreateRecord(count);
1237    if (h == 0)
1238        return msierror(0);
1239
1240    return record_new(h);
1241}
1242
1243
1244static PyMethodDef msi_methods[] = {
1245        _MSI_UUIDCREATE_METHODDEF
1246        _MSI_FCICREATE_METHODDEF
1247        _MSI_OPENDATABASE_METHODDEF
1248        _MSI_CREATERECORD_METHODDEF
1249        {NULL,          NULL}           /* sentinel */
1250};
1251
1252static char msi_doc[] = "Documentation";
1253
1254
1255static struct PyModuleDef _msimodule = {
1256        PyModuleDef_HEAD_INIT,
1257        "_msi",
1258        msi_doc,
1259        -1,
1260        msi_methods,
1261        NULL,
1262        NULL,
1263        NULL,
1264        NULL
1265};
1266
1267PyMODINIT_FUNC
1268PyInit__msi(void)
1269{
1270    PyObject *m;
1271
1272    m = PyModule_Create(&_msimodule);
1273    if (m == NULL)
1274        return NULL;
1275
1276    PyModule_AddIntConstant(m, "MSIDBOPEN_CREATEDIRECT", (long)(SIZE_T)MSIDBOPEN_CREATEDIRECT);
1277    PyModule_AddIntConstant(m, "MSIDBOPEN_CREATE", (long)(SIZE_T)MSIDBOPEN_CREATE);
1278    PyModule_AddIntConstant(m, "MSIDBOPEN_DIRECT", (long)(SIZE_T)MSIDBOPEN_DIRECT);
1279    PyModule_AddIntConstant(m, "MSIDBOPEN_READONLY", (long)(SIZE_T)MSIDBOPEN_READONLY);
1280    PyModule_AddIntConstant(m, "MSIDBOPEN_TRANSACT", (long)(SIZE_T)MSIDBOPEN_TRANSACT);
1281    PyModule_AddIntConstant(m, "MSIDBOPEN_PATCHFILE", (long)(SIZE_T)MSIDBOPEN_PATCHFILE);
1282
1283    PyModule_AddIntMacro(m, MSICOLINFO_NAMES);
1284    PyModule_AddIntMacro(m, MSICOLINFO_TYPES);
1285
1286    PyModule_AddIntMacro(m, MSIMODIFY_SEEK);
1287    PyModule_AddIntMacro(m, MSIMODIFY_REFRESH);
1288    PyModule_AddIntMacro(m, MSIMODIFY_INSERT);
1289    PyModule_AddIntMacro(m, MSIMODIFY_UPDATE);
1290    PyModule_AddIntMacro(m, MSIMODIFY_ASSIGN);
1291    PyModule_AddIntMacro(m, MSIMODIFY_REPLACE);
1292    PyModule_AddIntMacro(m, MSIMODIFY_MERGE);
1293    PyModule_AddIntMacro(m, MSIMODIFY_DELETE);
1294    PyModule_AddIntMacro(m, MSIMODIFY_INSERT_TEMPORARY);
1295    PyModule_AddIntMacro(m, MSIMODIFY_VALIDATE);
1296    PyModule_AddIntMacro(m, MSIMODIFY_VALIDATE_NEW);
1297    PyModule_AddIntMacro(m, MSIMODIFY_VALIDATE_FIELD);
1298    PyModule_AddIntMacro(m, MSIMODIFY_VALIDATE_DELETE);
1299
1300    PyModule_AddIntMacro(m, PID_CODEPAGE);
1301    PyModule_AddIntMacro(m, PID_TITLE);
1302    PyModule_AddIntMacro(m, PID_SUBJECT);
1303    PyModule_AddIntMacro(m, PID_AUTHOR);
1304    PyModule_AddIntMacro(m, PID_KEYWORDS);
1305    PyModule_AddIntMacro(m, PID_COMMENTS);
1306    PyModule_AddIntMacro(m, PID_TEMPLATE);
1307    PyModule_AddIntMacro(m, PID_LASTAUTHOR);
1308    PyModule_AddIntMacro(m, PID_REVNUMBER);
1309    PyModule_AddIntMacro(m, PID_LASTPRINTED);
1310    PyModule_AddIntMacro(m, PID_CREATE_DTM);
1311    PyModule_AddIntMacro(m, PID_LASTSAVE_DTM);
1312    PyModule_AddIntMacro(m, PID_PAGECOUNT);
1313    PyModule_AddIntMacro(m, PID_WORDCOUNT);
1314    PyModule_AddIntMacro(m, PID_CHARCOUNT);
1315    PyModule_AddIntMacro(m, PID_APPNAME);
1316    PyModule_AddIntMacro(m, PID_SECURITY);
1317
1318    MSIError = PyErr_NewException ("_msi.MSIError", NULL, NULL);
1319    if (!MSIError)
1320        return NULL;
1321    PyModule_AddObject(m, "MSIError", MSIError);
1322    return m;
1323}
1324