Annotation of mstools/samples/regmpad/regdb.c, revision 1.1

1.1     ! root        1: // RegDB.c -- Implements the semantics of the registry interface for this
        !             2: //            application. The visible interfaces and data structures are
        !             3: //            defined in RegDB.h.
        !             4: 
        !             5: /*
        !             6:  *
        !             7:                                 Overview
        !             8:                                 --------
        !             9: 
        !            10: The NT Registry is an object database consisting of keys and values. Keys
        !            11: have names and may contain other keys and values. A value is a name paired
        !            12: with a data object and a data type. The keys in the registry are analogous
        !            13: to the directories in a file system. In that vein the values are analogous
        !            14: to files.
        !            15: 
        !            16: Access to a key and its associated set of values is mediated by a key handle.
        !            17: Four key handles are given as predefined constants. Those handles correspond
        !            18: to the roots of key trees which have special signifigance. Handles for the
        !            19: other keys in the registry database may be constructed via the Registry's
        !            20: Open and Create interfaces using an existing key handle and a relative path
        !            21: string.
        !            22: 
        !            23: The predefined key handles are:
        !            24: 
        !            25: 
        !            26:     HKEY_LOCAL_MACHINE -- This handle refers to a tree of keys and values
        !            27:                           which characterize the state of the local machine.
        !            28:                           It contains state information global to everyone
        !            29:                           who uses the machine.
        !            30: 
        !            31:     HKEY_CLASSES_ROOT  -- This handle refers to a subtree within
        !            32:                           HKEY_LOCAL_MACHINE. It defines the associations
        !            33:                           between file extensions and document types as well
        !            34:                           as the command strings for shell and DDE/OLE actions.
        !            35: 
        !            36:     HKEY_USERS         -- This handle refers to a tree of information about
        !            37:                           the people who use this machine. The top level of
        !            38:                           the tree consists of a .DEFAULT key and one or more
        !            39:                           entries for specific people. The specific entries
        !            40:                           are created dynamically and are initially based on
        !            41:                           the content of the .DEFAULT key. The key names for
        !            42:                           the specific entries are SIDs which define the
        !            43:                           permissions given to the corresponding people.
        !            44: 
        !            45:     HKEY_CURRENT_USER  -- This handle refers to a specific user key within
        !            46:                           HKEY_USERS. It denotes the information tree for
        !            47:                           the currently active user id.
        !            48: 
        !            49: 
        !            50:                           Application Conventions
        !            51:                           -----------------------
        !            52: 
        !            53: Applications need to manipulate three of the above key trees. At installation
        !            54: time an application should adjust HKEY_CLASSES_ROOT to define the documents
        !            55: which it handles together with their file extensions and its shell and
        !            56: DDE/OLE command strings. At the same time it needs to add information global
        !            57: to all users to the HKEY_USERS\.DEFAULT key.
        !            58: 
        !            59: Subsequently an application will need to place per-user information in the
        !            60: HKEY_CURRENT_USER subtree. That information will include preferences as well
        !            61: as historical information such as lists of recently opened files.
        !            62: 
        !            63: The conventions appropriate to the HKEY_CLASSES_ROOT will not be described
        !            64: or demonstrated in this sample application. The focus here will be on
        !            65: HKEY_USERS\.DEFAULT and HKEY_CURRENT_USER subtrees.
        !            66: 
        !            67: Within both of those subtrees information related to version 2.5 of the
        !            68: Bazooka application published by Trey Software will be clustered in the
        !            69: subkey:
        !            70: 
        !            71:       software\"Trey Software"\Bazooka\2.5
        !            72: 
        !            73: and in general applications will use a path with the structure
        !            74: 
        !            75:       software \ <company name> \ <application name> \ <version number>
        !            76: 
        !            77: to access their state information.
        !            78: 
        !            79: After an application has been installed almost all registry changes will
        !            80: involve HKEY_CURRENT_USER and will concern a specific user's preferences
        !            81: or history.
        !            82: 
        !            83:  */
        !            84: 
        !            85: #include "multipad.h"
        !            86: 
        !            87: // #include <windows.H>
        !            88: // #include <winbase.h>
        !            89: #include <malloc.h>
        !            90: #include "regdb.h"
        !            91: 
        !            92: HKEY hkGlobal  = NULL;  // Key Handle for global registry data
        !            93: HKEY hkPerUser = NULL;  // Key Handle for per-user registry data
        !            94: 
        !            95: BOOL fTextWrapDefault = FALSE; // Set from registry data.
        !            96: 
        !            97: HANDLE hmtxRegGlobal  = NULL; // Mutex for serializing Local Machine Data.
        !            98: HANDLE hmtxRegPerUser = NULL; // Mutex for serializing Current User Data
        !            99: 
        !           100: BOOL RunningAsAdministrator()
        !           101: {
        !           102:    BOOL  fAdmin;
        !           103:    HANDLE htkThread;
        !           104:    TOKEN_GROUPS *ptg = NULL;
        !           105:    DWORD cbTokenGroups;
        !           106:    DWORD iGroup;
        !           107:    SID_IDENTIFIER_AUTHORITY SystemSidAuthority= SECURITY_NT_AUTHORITY;
        !           108:    PSID psidAdmin;
        !           109: 
        !           110:    // This function returns TRUE if the user identifier associated with this
        !           111:    // process is a member of the the Administrators group.
        !           112: 
        !           113:    // First we must open a handle to the access token for this thread.
        !           114: 
        !           115:    if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, FALSE, &htkThread))
        !           116:       if (GetLastError() == ERROR_NO_TOKEN)
        !           117:       {
        !           118:          // If the thread does not have an access token, we'll examine the
        !           119:          // access token associated with the process.
        !           120: 
        !           121:          if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &htkThread))
        !           122:          return FALSE;
        !           123:       }
        !           124:       else return FALSE;
        !           125: 
        !           126:    // Then we must query the size of the group information associated with
        !           127:    // the token. Note that we expect a FALSE result from GetTokenInformation
        !           128:    // because we've given it a NULL buffer. On exit cbTokenGroups will tell
        !           129:    // the size of the group information.
        !           130: 
        !           131:    if (GetTokenInformation(htkThread, TokenGroups, NULL, 0, &cbTokenGroups))
        !           132:       return FALSE;
        !           133: 
        !           134:    // Here we verify that GetTokenInformation failed for lack of a large
        !           135:    // enough buffer.
        !           136: 
        !           137:    if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
        !           138:       return FALSE;
        !           139: 
        !           140:    // Now we allocate a buffer for the group information.
        !           141:    // Since _alloca allocates on the stack, we don't have
        !           142:    // to explicitly deallocate it. That happens automatically
        !           143:    // when we exit this function.
        !           144: 
        !           145:    if (!(ptg= _alloca(cbTokenGroups))) return FALSE;
        !           146: 
        !           147:    // Now we ask for the group information again.
        !           148:    // This may fail if an administrator has added this account
        !           149:    // to an additional group between our first call to
        !           150:    // GetTokenInformation and this one.
        !           151: 
        !           152:    if (!GetTokenInformation(htkThread, TokenGroups, ptg, cbTokenGroups,
        !           153:                                        &cbTokenGroups
        !           154:                            )
        !           155:       )
        !           156:       return FALSE;
        !           157: 
        !           158:    // Now we must create a System Identifier for the Admin group.
        !           159: 
        !           160:    if (!AllocateAndInitializeSid
        !           161:           (&SystemSidAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID,
        !           162:                                    DOMAIN_ALIAS_RID_ADMINS,
        !           163:                                    0, 0, 0, 0, 0, 0,
        !           164:                                    &psidAdmin
        !           165:           )
        !           166:       )
        !           167:       return FALSE;
        !           168: 
        !           169:    // Finally we'll iterate through the list of groups for this access
        !           170:    // token looking for a match against the SID we created above.
        !           171: 
        !           172:    fAdmin= FALSE;
        !           173: 
        !           174:    for (iGroup= 0; iGroup < ptg->GroupCount; iGroup++)
        !           175:       if (EqualSid(ptg->Groups[iGroup].Sid, psidAdmin))
        !           176:       {
        !           177:          fAdmin= TRUE;
        !           178: 
        !           179:          break;
        !           180:       }
        !           181: 
        !           182:    // Before we exit we must explicity deallocate the SID
        !           183:    // we created.
        !           184: 
        !           185:    FreeSid(psidAdmin);
        !           186: 
        !           187:    return(fAdmin);
        !           188: }
        !           189: 
        !           190: 
        !           191: BOOL InstallApp(PSZ pszPathBuff)
        !           192: {
        !           193:    // This function attempts to install global data for this
        !           194:    // application in the HKEY_LOCAL_MACHINE portion of the
        !           195:    // registry database.
        !           196: 
        !           197:    // The parameter pszPathBuff refers to a null terminated
        !           198:    // string which defines where the new key should be located
        !           199:    // in the LOCAL_MACHINE tree.
        !           200: 
        !           201:    // We requires that the current user have administrative
        !           202:    // privileges. That requirement insures that the owner
        !           203:    // tag for the global registry entries will be the
        !           204:    // Administrator group and not a particular user id.
        !           205: 
        !           206:    // We also assume that hmtxRegGlobal is held when this function is called.
        !           207: 
        !           208:    // First we'll see whether this user has admin privileges...
        !           209:    // Only administrators can install this application...
        !           210: 
        !           211:    if (!RunningAsAdministrator())
        !           212:    {
        !           213:       MPError(hwndFrame, MB_OK | MB_ICONHAND, IDS_CANTINSTALL, NULL);
        !           214: 
        !           215:       return FALSE;
        !           216:    }
        !           217: 
        !           218:    // Then we bring up a dialog to get the global configuration information
        !           219:    // we'll be storing in the HKEY_LOCAL_MACHINE tree. The dialog proc
        !           220:    // will call StoreAppConfig with that configuration data.
        !           221: 
        !           222:    if (!DialogBoxParam(hInst, MAKEINTRESOURCE(DLG_INSTALL),
        !           223:                        hwndFrame, InstallDlgProc,
        !           224:                        (LPARAM) pszPathBuff
        !           225:                       )
        !           226:       ) return FALSE;
        !           227: }
        !           228: 
        !           229: BOOL StoreAppConfig(HWND hwnd, PSZ pszPathBuff, PSZ pszInstallName,
        !           230:                     PSZ pszInstallOrg, BOOL fTextWrapDefault
        !           231:                    )
        !           232: {
        !           233:    // This function attempts to install global data for this
        !           234:    // application in the HKEY_LOCAL_MACHINE portion of the
        !           235:    // registry database. It is called from InstallDlgProc.
        !           236: 
        !           237:    // The parameter hwnd denotes the window associated with this
        !           238:    // call to StoreAppConfig. It's used with the calls to MPError
        !           239:    // below.
        !           240: 
        !           241:    // The parameter pszPathBuff refers to a null terminated
        !           242:    // string which defines where the new key should be located
        !           243:    // in the LOCAL_MACHINE tree.
        !           244: 
        !           245:    // The pszInstallName and pszInstallOrg parameters are text strings
        !           246:    // which denote the person and the organization which has installed
        !           247:    // this app in the HKEY_LOCAL_MACHINE portion of the registry.
        !           248: 
        !           249:    // The fTextWrapDefault is a boolean value which will be stored
        !           250:    // in the DEFAULT subkey. Values in the DEFAULT subkey are copied
        !           251:    // into the HKEY_PER_USER area during user initialization. (See
        !           252:    // the InitUser function below.)
        !           253: 
        !           254:    // We assume that hmtxRegGlobal is held when this function is called.
        !           255: 
        !           256:    HKEY  hkGlobal     = NULL;
        !           257:    HKEY  hkDefaults   = NULL;
        !           258:    PSID  psidAdmins   = NULL;
        !           259:    PSID  psidEveryone = NULL;
        !           260:    PACL  paclKey      = NULL;
        !           261: 
        !           262:    BOOL  fInstalled   = FALSE;
        !           263: 
        !           264:    long  lResult;
        !           265:    DWORD dwDisposition;
        !           266: 
        !           267:    BYTE  abEmptyStringSet[2];
        !           268: 
        !           269:    SID_IDENTIFIER_AUTHORITY SystemSidAuthority= SECURITY_NT_AUTHORITY;
        !           270:    SID_IDENTIFIER_AUTHORITY  WorldSidAuthority= SECURITY_WORLD_SID_AUTHORITY;
        !           271: 
        !           272:    SECURITY_ATTRIBUTES sa;
        !           273:    SECURITY_DESCRIPTOR sdPermissions;
        !           274:    // Next we'll setup the security attributes we're going to
        !           275:    // use with the application's global key.
        !           276: 
        !           277:    sa.nLength              = sizeof(SECURITY_ATTRIBUTES);
        !           278:    sa.bInheritHandle       = FALSE;
        !           279:    sa.lpSecurityDescriptor = &sdPermissions;
        !           280: 
        !           281:    // Here we're creating a System Identifier (SID) to represent
        !           282:    // the Admin group.
        !           283: 
        !           284:    if (!AllocateAndInitializeSid
        !           285:           (&SystemSidAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID,
        !           286:                                    DOMAIN_ALIAS_RID_ADMINS,
        !           287:                                    0, 0, 0, 0, 0, 0,
        !           288:                                    &psidAdmins
        !           289:           )
        !           290:       )
        !           291:       goto security_failure;
        !           292: 
        !           293:    // Now we'll construct a System Identifier which represents
        !           294:    // all users.
        !           295: 
        !           296:    if (!AllocateAndInitializeSid
        !           297:           (&WorldSidAuthority, 1, SECURITY_WORLD_RID,
        !           298:            0, 0, 0, 0, 0, 0, 0,
        !           299:            &psidEveryone
        !           300:           )
        !           301:       )
        !           302:       goto security_failure;
        !           303: 
        !           304:    if (!InitializeSecurityDescriptor(&sdPermissions,
        !           305:                                      SECURITY_DESCRIPTOR_REVISION1
        !           306:                                     )
        !           307:       )
        !           308:       goto security_failure;
        !           309: 
        !           310:    // We want the admin group to own this key.
        !           311: 
        !           312:    if (!SetSecurityDescriptorOwner(&sdPermissions, psidAdmins, 0))
        !           313:       goto security_failure;
        !           314: 
        !           315:    // Finally we must allocate and construct the discretionary
        !           316:    // access control list (DACL) for the key.
        !           317: 
        !           318:    // Note that _alloca allocates memory on the stack frame
        !           319:    // which will automatically be deallocated when this routine
        !           320:    // exits.
        !           321: 
        !           322:    if (!(paclKey= (PACL) _alloca(ACL_BUFFER_SIZE)))
        !           323:       goto memory_limited;
        !           324: 
        !           325:    if (!InitializeAcl(paclKey, ACL_BUFFER_SIZE, ACL_REVISION2))
        !           326:       goto security_failure;
        !           327: 
        !           328:    // Our DACL will contain two access control entries (ACEs). One which allows
        !           329:    // members of the Admin group complete access to the key, and one which gives
        !           330:    // read-only access to everyone.
        !           331: 
        !           332:    if (!AddAccessAllowedAce(paclKey, ACL_REVISION2, KEY_ALL_ACCESS, psidAdmins))
        !           333:       goto security_failure;
        !           334: 
        !           335:    if (!AddAccessAllowedAce(paclKey, ACL_REVISION2, KEY_READ, psidEveryone))
        !           336:       goto security_failure;
        !           337: 
        !           338:    // We must bind this DACL to the security descriptor...
        !           339: 
        !           340:    if (!SetSecurityDescriptorDacl(&sdPermissions, TRUE, paclKey, FALSE))
        !           341:       goto security_failure;
        !           342: 
        !           343:    // Now we'll attempt to create the key with the security attributes...
        !           344: 
        !           345:    lResult= RegCreateKeyEx(HKEY_LOCAL_MACHINE, pszPathBuff, 0,
        !           346:                            "Application Global Data", REG_OPTION_NON_VOLATILE,
        !           347:                            KEY_ALL_ACCESS,
        !           348:                            &sa, &hkGlobal, &dwDisposition
        !           349:                           );
        !           350: 
        !           351:    if (lResult != ERROR_SUCCESS) goto registry_access_error;
        !           352: 
        !           353:    // Usually the disposition value will indicate that we've created a
        !           354:    // new key. Sometimes it may instead state that we've opened an existing
        !           355:    // key. This can happen when installation is incomplete and interrupted,
        !           356:    // say by loss of electrical power.
        !           357: 
        !           358:    if (   dwDisposition != REG_CREATED_NEW_KEY
        !           359:        && dwDisposition != REG_OPENED_EXISTING_KEY
        !           360:       ) goto registry_access_error;
        !           361: 
        !           362:    // Now we'll add two values to the global key.
        !           363: 
        !           364:    // These values are simple strings which identify the name and
        !           365:    // organization associated with this installation.
        !           366: 
        !           367:    lResult= RegSetValueEx(hkGlobal, KEY_VALUE_INSTALL_NAME, 0, REG_SZ,
        !           368:                                     pszInstallName, strlen(pszInstallName)+1
        !           369:                          );
        !           370: 
        !           371:    if (lResult != ERROR_SUCCESS) goto registry_access_error;
        !           372: 
        !           373:    lResult= RegSetValueEx(hkGlobal, KEY_VALUE_INSTALL_ORG, 0, REG_SZ,
        !           374:                                     pszInstallOrg, strlen(pszInstallOrg)+1
        !           375:                          );
        !           376: 
        !           377:    if (lResult != ERROR_SUCCESS) goto registry_access_error;
        !           378: 
        !           379:    // We've created the global key. Now we must create the "Defaults" subkey
        !           380:    // and set its value(s).
        !           381: 
        !           382:    lResult= RegCreateKeyEx(hkGlobal, DEFAULTS_PATH, 0,
        !           383:                            "Defaults for Per-User Data",
        !           384:                            REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS,
        !           385:                            &sa, &hkDefaults, &dwDisposition
        !           386:                           );
        !           387: 
        !           388:    if (lResult != ERROR_SUCCESS) goto registry_access_error;
        !           389: 
        !           390:    // Usually the disposition value will indicate that we've created a
        !           391:    // new key. Sometimes it may instead state that we've opened an existing
        !           392:    // key. This can happen when installation is incomplete and interrupted,
        !           393:    // say by loss of electrical power.
        !           394: 
        !           395:    if (   dwDisposition != REG_CREATED_NEW_KEY
        !           396:        && dwDisposition != REG_OPENED_EXISTING_KEY
        !           397:       ) goto registry_access_error;
        !           398: 
        !           399:    // Now we'll add a collection of values to the Defaults subkey.
        !           400:    // When per-user data is constructed for a particular userid, these
        !           401:    // values will define the initial state of the per-user data.
        !           402: 
        !           403:    // In this demo application we store two items -- a word-wrap flag
        !           404:    // and a file name set.
        !           405: 
        !           406:    lResult= RegSetValueEx(hkDefaults, WORD_WRAP_DEFAULT, 0, REG_DWORD,
        !           407:                           (LPBYTE) &fTextWrapDefault,
        !           408:                           sizeof(fTextWrapDefault)
        !           409:                          );
        !           410: 
        !           411:    if (lResult != ERROR_SUCCESS) goto registry_access_error;
        !           412: 
        !           413:    abEmptyStringSet[0]= '\0';
        !           414:    abEmptyStringSet[1]= '\0';
        !           415: 
        !           416:    lResult= RegSetValueEx(hkDefaults, LAST_FILE_SET, 0, REG_MULTI_SZ,
        !           417:                           (LPBYTE) &abEmptyStringSet, 2
        !           418:                          );
        !           419: 
        !           420:    if (lResult != ERROR_SUCCESS) goto registry_access_error;
        !           421: 
        !           422:    // Finally, we'll force our registry data out to disk via the
        !           423:    // flush key api:
        !           424: 
        !           425:    lResult= RegFlushKey(hkGlobal);
        !           426: 
        !           427:    // Then we'll write out the REG_INSTALLED flag. Note that its
        !           428:    // value is unimportant. Only its existence matters.
        !           429: 
        !           430:    fInstalled= TRUE;
        !           431: 
        !           432:    lResult= RegSetValueEx(hkGlobal, REG_INSTALLED, 0, REG_DWORD,
        !           433:                           (LPBYTE) &fInstalled, sizeof(fInstalled)
        !           434:                          );
        !           435: 
        !           436:    if (lResult != ERROR_SUCCESS) goto registry_access_error;
        !           437: 
        !           438:    RegCloseKey(hkGlobal);
        !           439: 
        !           440:    RegCloseKey(hkDefaults);
        !           441: 
        !           442:    FreeSid(psidAdmins);
        !           443: 
        !           444:    return TRUE;
        !           445: 
        !           446: registry_access_error:
        !           447: 
        !           448:    MPError(hwnd, MB_OK | MB_ICONHAND, IDS_REG_ACCESS_ERROR, NULL);
        !           449: 
        !           450:    // We've constructed some, but not all of the global key state.
        !           451:    // So we must remove any keys we created. The Defaults key must
        !           452:    // be deleted first before the Global key can be deleted.
        !           453: 
        !           454:    if (hkDefaults) RegDeleteKey(hkGlobal, DEFAULTS_PATH);
        !           455: 
        !           456:    if (hkGlobal) RegDeleteKey(HKEY_LOCAL_MACHINE, pszPathBuff);
        !           457: 
        !           458:    goto clean_up_after_failure;
        !           459: 
        !           460: memory_limited:
        !           461: 
        !           462:    MPError(hwnd, MB_OK | MB_ICONHAND, IDS_MEMORY_LIMITED, NULL);
        !           463:    goto clean_up_after_failure;
        !           464: 
        !           465: security_failure:
        !           466: 
        !           467:    MPError(hwnd, MB_OK | MB_ICONHAND, IDS_SECURITY_FAIL_I, NULL);
        !           468: 
        !           469: clean_up_after_failure:
        !           470: 
        !           471:    if (psidAdmins  ) FreeSid(psidAdmins  );
        !           472:    if (psidEveryone) FreeSid(psidEveryone);
        !           473: 
        !           474:    return FALSE;
        !           475: }
        !           476: 
        !           477: PSID GetCurrentUserInfo()
        !           478: {
        !           479:    // This function returns security information about the person who owns
        !           480:    // this thread.
        !           481: 
        !           482:    HANDLE htkThread;
        !           483: 
        !           484:    TOKEN_USER *ptu;
        !           485:    DWORD      cbtu;
        !           486: 
        !           487:    TOKEN_GROUPS *ptg = NULL;
        !           488:    SID_IDENTIFIER_AUTHORITY SystemSidAuthority= SECURITY_NT_AUTHORITY;
        !           489: 
        !           490:    // First we must open a handle to the access token for this thread.
        !           491: 
        !           492:    if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, FALSE, &htkThread))
        !           493:       if (GetLastError() == ERROR_NO_TOKEN)
        !           494:       {
        !           495:          // If the thread does not have an access token, we'll examine the
        !           496:          // access token associated with the process.
        !           497: 
        !           498:          if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &htkThread))
        !           499:          return NULL;
        !           500:       }
        !           501:       else return NULL;
        !           502: 
        !           503: 
        !           504:    if (GetTokenInformation(htkThread, TokenUser, NULL, 0, &cbtu))
        !           505:       return NULL;
        !           506: 
        !           507:    // Here we verify that GetTokenInformation failed for lack of a large
        !           508:    // enough buffer.
        !           509: 
        !           510:    if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
        !           511:       return NULL;
        !           512: 
        !           513:    // Now we allocate a buffer for the group information.
        !           514:    // Since _alloca allocates on the stack, we don't have
        !           515:    // to explicitly deallocate it. That happens automatically
        !           516:    // when we exit this function.
        !           517: 
        !           518:    if (!(ptu= LocalAlloc(LPTR, cbtu))) return NULL;
        !           519: 
        !           520:    // Now we ask for the user information again.
        !           521:    // This may fail if an administrator has changed SID information
        !           522:    // for this user.
        !           523: 
        !           524:    if (!GetTokenInformation(htkThread, TokenUser, ptu, cbtu, &cbtu))
        !           525:       return FALSE;
        !           526: 
        !           527: // if (GetTokenInformation(htkThread, TokenUser, &tu, sizeof(tu), &cbtu))
        !           528: //    return NULL;
        !           529: 
        !           530:    return ptu;
        !           531: }
        !           532: 
        !           533: BOOL InitUser(HKEY hkGlobal, PSZ pszPathBuff)
        !           534: {
        !           535:    // This function sets up the per-user key area for a new user.
        !           536:    // It will be called the very first time a user runs the application.
        !           537: 
        !           538:    // The initial per-user information is copied over from a set of defaults
        !           539:    // stored in the global key area.
        !           540: 
        !           541:    // We assume that hkGlobal is a registry key for the global area
        !           542:    // used by this application. We assume pszPathBuff defines where
        !           543:    // the per-user data should be stored in the CURRENT_USER tree.
        !           544: 
        !           545:    // We also assume that hmtxRegPerUser is held when this function is called.
        !           546: 
        !           547:    HANDLE hkPerUser  = NULL;
        !           548:    HANDLE hkDefaults = NULL;
        !           549: 
        !           550:    BOOL fInstalled= FALSE;
        !           551: 
        !           552:    LONG lResult;
        !           553: 
        !           554:    DWORD dwType, cbData;
        !           555: 
        !           556:    PSZ pszFileList;
        !           557: 
        !           558:    DWORD dwDisposition;
        !           559: 
        !           560:    TOKEN_USER *ptu = NULL;
        !           561: 
        !           562:    PSID psidUser   = NULL,
        !           563:         psidAdmins = NULL;
        !           564: 
        !           565:    PACL  paclKey = NULL;
        !           566: 
        !           567:    BOOL fWordWrap;
        !           568:    PSZ  pszFileSet = NULL;
        !           569: 
        !           570:    SID_IDENTIFIER_AUTHORITY SystemSidAuthority= SECURITY_NT_AUTHORITY;
        !           571: 
        !           572:    SECURITY_ATTRIBUTES sa;
        !           573:    SECURITY_DESCRIPTOR sdPermissions;
        !           574: 
        !           575: 
        !           576:    // First we'll setup the security attributes we're going to
        !           577:    // use with the application's global key.
        !           578: 
        !           579:    sa.nLength              = sizeof(SECURITY_ATTRIBUTES);
        !           580:    sa.bInheritHandle       = FALSE;
        !           581:    sa.lpSecurityDescriptor = &sdPermissions;
        !           582: 
        !           583:    // Here we're creating a System Identifier (SID) to represent
        !           584:    // the Admin group.
        !           585: 
        !           586:    if (!AllocateAndInitializeSid
        !           587:           (&SystemSidAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID,
        !           588:                                    DOMAIN_ALIAS_RID_ADMINS,
        !           589:                                    0, 0, 0, 0, 0, 0,
        !           590:                                    &psidAdmins
        !           591:           )
        !           592:       )
        !           593:       goto security_failure;
        !           594: 
        !           595:    // We also need a SID for the current user.
        !           596: 
        !           597:    if (   !(ptu= GetCurrentUserInfo())
        !           598:        || !(psidUser= ptu->User.Sid)
        !           599:       ) goto security_failure;
        !           600: 
        !           601:    if (!InitializeSecurityDescriptor(&sdPermissions,
        !           602:                                      SECURITY_DESCRIPTOR_REVISION1
        !           603:                                     )
        !           604:       )
        !           605:       goto security_failure;
        !           606: 
        !           607:    // We want the current user to own this key.
        !           608: 
        !           609:    if (!SetSecurityDescriptorOwner(&sdPermissions, psidUser, 0))
        !           610:       goto security_failure;
        !           611: 
        !           612:    // Finally we must allocate and construct the discretionary
        !           613:    // access control list (DACL) for the key.
        !           614: 
        !           615:    // Note that _alloca allocates memory on the stack frame
        !           616:    // which will automatically be deallocated when this routine
        !           617:    // exits.
        !           618: 
        !           619:    if (!(paclKey= (PACL) _alloca(ACL_BUFFER_SIZE)))
        !           620:       goto memory_limited;
        !           621: 
        !           622:    if (!InitializeAcl(paclKey, ACL_BUFFER_SIZE, ACL_REVISION2))
        !           623:       goto security_failure;
        !           624: 
        !           625:    // Our DACL will two access control entries (ACEs). The first ACE
        !           626:    // provides full access to the current user. The second ACE gives
        !           627:    // the Admin group full access. By default all other users will have
        !           628:    // no access to the key.
        !           629: 
        !           630:    // The reason for admin access is to allow an administrator to
        !           631:    // run special utilties to cleanup inconsistencies and disasters
        !           632:    // in the per-user data area.
        !           633: 
        !           634:    if (!AddAccessAllowedAce(paclKey, ACL_REVISION2, KEY_ALL_ACCESS, psidUser))
        !           635:       goto security_failure;
        !           636: 
        !           637:    if (!AddAccessAllowedAce(paclKey, ACL_REVISION2, KEY_ALL_ACCESS, psidAdmins))
        !           638:       goto security_failure;
        !           639: 
        !           640:    // We must bind this DACL to the security descriptor...
        !           641: 
        !           642:    if (!SetSecurityDescriptorDacl(&sdPermissions, TRUE, paclKey, FALSE))
        !           643:       goto security_failure;
        !           644: 
        !           645:    // Now we'll attempt to create the key with the security attributes...
        !           646: 
        !           647:    lResult= RegCreateKeyEx(HKEY_CURRENT_USER, pszPathBuff, 0,
        !           648:                            "Application Per-User Data", REG_OPTION_NON_VOLATILE,
        !           649:                            KEY_ALL_ACCESS,
        !           650:                            &sa, &hkPerUser, &dwDisposition
        !           651:                           );
        !           652: 
        !           653:    if (lResult != ERROR_SUCCESS) goto registry_access_error;
        !           654: 
        !           655:    // Usually the disposition value will indicate that we've created a
        !           656:    // new key. Sometimes it may instead state that we've opened an existing
        !           657:    // key. This can happen when installation is incomplete and interrupted,
        !           658:    // say by loss of electrical power.
        !           659: 
        !           660:    if (   dwDisposition != REG_CREATED_NEW_KEY
        !           661:        && dwDisposition != REG_OPENED_EXISTING_KEY
        !           662:       ) goto registry_access_error;
        !           663: 
        !           664:    // Now we must open the Defaults subkey in the global area.
        !           665: 
        !           666:    lResult= RegOpenKeyEx(hkGlobal, DEFAULTS_PATH, 0, KEY_READ, &hkDefaults);
        !           667: 
        !           668:    if (lResult != ERROR_SUCCESS)
        !           669:    {
        !           670:       // Can't open the "Defaults" subkey for read access.
        !           671:       // This shouldn't happen normally. The algorithmic sequence for
        !           672:       // installing this application and then creating user profile
        !           673:       // data should prevent it.
        !           674: 
        !           675:       // It is possible for someone running RegEdit32 to delete the key,
        !           676:       // however.
        !           677: 
        !           678:       goto registry_damage_error;
        !           679:    }
        !           680: 
        !           681:    // Now we'll copy two default values to the per-user key:
        !           682:    //
        !           683:    //   -- a word-wrap flag
        !           684:    //   -- a list of file names
        !           685: 
        !           686:    cbData= sizeof(fWordWrap);
        !           687: 
        !           688:    lResult= RegQueryValueEx(hkDefaults, WORD_WRAP_DEFAULT, NULL,
        !           689:                             &dwType, (LPBYTE) &fWordWrap, &cbData
        !           690:                            );
        !           691: 
        !           692:    if (   lResult != ERROR_SUCCESS
        !           693:        || dwType  != REG_DWORD
        !           694:       ) goto registry_damage_error;
        !           695: 
        !           696: 
        !           697:    lResult= RegSetValueEx(hkPerUser, WORD_WRAP_DEFAULT, 0, REG_DWORD,
        !           698:                           (LPBYTE) &fWordWrap, sizeof(fWordWrap)
        !           699:                          );
        !           700: 
        !           701:    if (lResult != ERROR_SUCCESS) goto registry_access_error;
        !           702: 
        !           703:    cbData= 0;
        !           704: 
        !           705:    lResult= RegQueryValueEx(hkDefaults, LAST_FILE_SET, NULL, &dwType,
        !           706:                             NULL, &cbData
        !           707:                            );
        !           708: 
        !           709:    if (   lResult != ERROR_SUCCESS
        !           710:        || dwType != REG_MULTI_SZ
        !           711:       )
        !           712:      goto registry_damage_error;
        !           713: 
        !           714:    pszFileList= (PSZ) _alloca(cbData);
        !           715: 
        !           716:    if (!pszFileList) goto memory_limited;
        !           717: 
        !           718:    lResult= RegQueryValueEx(hkDefaults, LAST_FILE_SET, NULL, &dwType,
        !           719:                             (LPBYTE) pszFileList, &cbData
        !           720:                            );
        !           721: 
        !           722:    if (lResult != ERROR_SUCCESS) goto registry_damage_error;
        !           723: 
        !           724:    lResult= RegSetValueEx(hkPerUser, LAST_FILE_SET, 0, REG_MULTI_SZ,
        !           725:                           (LPBYTE) pszFileList, cbData
        !           726:                          );
        !           727: 
        !           728:    if (lResult != ERROR_SUCCESS) goto registry_access_error;
        !           729: 
        !           730:    // Now we force our registry subtree out to disk
        !           731:    // via the flush key api:
        !           732: 
        !           733:    lResult= RegFlushKey(hkPerUser);
        !           734: 
        !           735:    // Finally we store the REG_INSTALLED value in the registry to
        !           736:    // indicate that installation has completed. Note that its value
        !           737:    // is irrelevant. Only its presence or absence is meaningful.
        !           738: 
        !           739:    fInstalled= TRUE;
        !           740: 
        !           741:    lResult= RegSetValueEx(hkPerUser, REG_INSTALLED, 0, REG_DWORD,
        !           742:                           (LPBYTE) &fInstalled, sizeof(fInstalled)
        !           743:                          );
        !           744: 
        !           745:    RegCloseKey(hkPerUser);
        !           746:    RegCloseKey(hkDefaults);
        !           747: 
        !           748:    FreeSid(psidAdmins);
        !           749: 
        !           750:    LocalFree(ptu);
        !           751: 
        !           752:    return(TRUE);
        !           753: 
        !           754: registry_damage_error:
        !           755: 
        !           756:    // We'll display a warning that the registry info has
        !           757:    // been damaged.
        !           758: 
        !           759:    MPError(hwndFrame, MB_OK | MB_ICONHAND, IDS_MUTEX_LOGIC_ERR, NULL);
        !           760: 
        !           761:    // Then we discard the REG_INSTALLED flag to insure that a reinstallation
        !           762:    // can proceed.
        !           763: 
        !           764:    RegDeleteValue(hkGlobal, REG_INSTALLED);
        !           765: 
        !           766:    goto clean_up_registry_keys;
        !           767: 
        !           768: registry_access_error:
        !           769: 
        !           770:    MPError(hwndFrame, MB_OK | MB_ICONHAND, IDS_REG_ACCESS_ERROR, NULL);
        !           771: 
        !           772: clean_up_registry_keys:
        !           773: 
        !           774:    // We've constructed some, but not all of the global key state.
        !           775:    // So we must remove any keys we created. The Defaults key must
        !           776:    // be deleted first before the Global key can be deleted.
        !           777: 
        !           778:    if (hkPerUser) RegDeleteKey(HKEY_CURRENT_USER, pszPathBuff);
        !           779: 
        !           780:    if (hkDefaults) RegCloseKey(hkDefaults);
        !           781: 
        !           782:    goto clean_up_after_failure;
        !           783: 
        !           784: memory_limited:
        !           785: 
        !           786:    MPError(hwndFrame, MB_OK | MB_ICONHAND, IDS_MEMORY_LIMITED, NULL);
        !           787:    goto clean_up_after_failure;
        !           788: 
        !           789: security_failure:
        !           790: 
        !           791:    MPError(hwndFrame, MB_OK | MB_ICONHAND, IDS_SECURITY_FAIL_I, NULL);
        !           792: 
        !           793: clean_up_after_failure:
        !           794: 
        !           795:    if (psidAdmins) FreeSid(psidAdmins  );
        !           796:    if (ptu       ) LocalFree(psidUser);
        !           797: 
        !           798:    return FALSE;
        !           799: }
        !           800: 
        !           801: BOOL CreateAppKeys()
        !           802: {
        !           803:    long lResult;
        !           804:    BOOL fSuccess;
        !           805:    BYTE abPathBuffer[MAX_PATH];
        !           806:    BYTE abMutexName [MAX_PATH];
        !           807: 
        !           808:    BOOL  fInstalled= FALSE;
        !           809:    DWORD dwType, cbData;
        !           810: 
        !           811:    // Here we're constructing registry key handles for global data and
        !           812:    // per-user data. The global data is kept in the HKEY_LOCAL_MACHINE
        !           813:    // tree, and the per-user data is kept in the HKEY_CURRENT_USER tree.
        !           814:    // For both trees the data for this program is kept in the same
        !           815:    // relative location.
        !           816: 
        !           817:    // The registry handles are returned in the global variables hkGlobal
        !           818:    // and hkPerUser. In addition two global mutex handles (hmtxRegGlobal
        !           819:    // and hmtxRegPerUser are created. The mutexes are used to serialize
        !           820:    // registry accesses among instances of this application at start-up.
        !           821:    // That serialization is necessary to insure that a complete registry
        !           822:    // environment is present when the application starts.
        !           823: 
        !           824:    // Note that individual registry reads and writes do not need to be
        !           825:    // serialized -- only collections of reads and writes which must be
        !           826:    // consistent with each other.
        !           827: 
        !           828:    // Note all so the use of the registry value REG_INSTALLED. It is the
        !           829:    // last value written to the HKEY_LOCAL_MACHINE and HKEY_CURRENT_USER
        !           830:    // registry tree during the installation sequence. Whenever this application
        !           831:    // starts, it looks for REG_INSTALLED as a sign that the registry has
        !           832:    // been properly setup. If it doesn't find it, we assume that either
        !           833:    // setup hasn't been done or has been done incompletely.
        !           834: 
        !           835:    // First we'll construct a relative path to the keys we're going to
        !           836:    // open. The path has the structure:
        !           837: 
        !           838:    //    Software \ <Company Name> \ <Application Name> \ <Version Number>
        !           839: 
        !           840:    wsprintf(abPathBuffer, "Software\\%s\\%s\\%s",
        !           841:             COMPANY_NAME, APPLICATION_NAME, VERSION_NUMBER
        !           842:            );
        !           843: 
        !           844:    // Since two instance of this application could be running simultaneously,
        !           845:    // we use a named mutext to serialize registry accesses.
        !           846: 
        !           847:    wsprintf(abMutexName, "Software/%s/%s/%s/Globals",
        !           848:             COMPANY_NAME, APPLICATION_NAME, VERSION_NUMBER
        !           849:            );
        !           850: 
        !           851:    if (!(hmtxRegGlobal= CreateMutex(NULL, FALSE, abMutexName))) goto failure_exit;
        !           852: 
        !           853:    wsprintf(abMutexName, "Software/%s/%s/%s/PerUser",
        !           854:             COMPANY_NAME, APPLICATION_NAME, VERSION_NUMBER
        !           855:            );
        !           856: 
        !           857:    if (!(hmtxRegPerUser= CreateMutex(NULL, FALSE, abMutexName))) goto failure_exit;
        !           858: 
        !           859: 
        !           860:    // First we'll attempt to open a key to the global data...
        !           861: 
        !           862:    for (lResult= ~ERROR_SUCCESS; lResult != ERROR_SUCCESS; )
        !           863:    {
        !           864:       // We serialize the code in this loop via hmtxRegGlobal.
        !           865: 
        !           866:       lResult= WaitForSingleObject(hmtxRegGlobal, 0xFFFFFFFF);
        !           867: 
        !           868:       if (   lResult != WAIT_ABANDONED
        !           869:           && lResult != WAIT_OBJECT_0
        !           870:          ) goto failure_exit;
        !           871: 
        !           872:       lResult= RegOpenKeyEx(HKEY_LOCAL_MACHINE, abPathBuffer, 0, KEY_READ,
        !           873:                                                 &hkGlobal
        !           874:                            );
        !           875: 
        !           876:       // Note that we also look for the REG_INSTALLED flag.
        !           877: 
        !           878:       cbData= sizeof(fInstalled);
        !           879: 
        !           880:       if (   ERROR_SUCCESS != lResult
        !           881:           || ERROR_SUCCESS != RegQueryValueEx(hkGlobal, REG_INSTALLED, 0,
        !           882:                                               &dwType, (LPBYTE) &fInstalled,
        !           883:                                               &cbData
        !           884:                                              )
        !           885:          )
        !           886:       {
        !           887:          hkGlobal= NULL;
        !           888: 
        !           889:          // If we can't open the global key, this probably means that the
        !           890:          // application hasn't been installed yet. So we'll try to install it.
        !           891: 
        !           892:          // If the installation succeeds, we'll try opening the global key
        !           893:          // again. Otherwise we'll just fail.
        !           894: 
        !           895:          fSuccess= InstallApp(abPathBuffer);
        !           896: 
        !           897:          ReleaseMutex(hmtxRegGlobal);
        !           898: 
        !           899:          if (fSuccess) continue;
        !           900:          else goto failure_exit;
        !           901:       }
        !           902: 
        !           903:       ReleaseMutex(hmtxRegGlobal);
        !           904:    }
        !           905: 
        !           906:    for (lResult= ~ERROR_SUCCESS; lResult != ERROR_SUCCESS; )
        !           907:    {
        !           908:       // We serialize the code in this loop via hmtxRegPerUser.
        !           909: 
        !           910:       lResult= WaitForSingleObject(hmtxRegPerUser, 0xFFFFFFFF);
        !           911: 
        !           912:       if (   lResult != WAIT_ABANDONED
        !           913:           && lResult != WAIT_OBJECT_0
        !           914:          ) goto failure_exit;
        !           915: 
        !           916:       // Now we'll try to open an handle to the per-user key for this user and
        !           917:       // this application.
        !           918: 
        !           919:       lResult= RegOpenKeyEx(HKEY_CURRENT_USER, abPathBuffer, 0, KEY_ALL_ACCESS,
        !           920:                                                &hkPerUser
        !           921:                            );
        !           922: 
        !           923:       // Note that we also look for the REG_INSTALLED flag.
        !           924: 
        !           925:       cbData= sizeof(fInstalled);
        !           926: 
        !           927:       if (   ERROR_SUCCESS != lResult
        !           928:           || ERROR_SUCCESS != RegQueryValueEx(hkPerUser, REG_INSTALLED, 0,
        !           929:                                               &dwType, (LPBYTE) &fInstalled,
        !           930:                                               &cbData
        !           931:                                              )
        !           932:          )
        !           933:       {
        !           934:          // The per-user open call failed. We infer that this is the first
        !           935:          // time this user has invoked this application. So next we'll try
        !           936:          // to initial a per-user area for them.
        !           937: 
        !           938:          hkPerUser= NULL;
        !           939: 
        !           940:          fSuccess= InitUser(hkGlobal, abPathBuffer);
        !           941: 
        !           942:          ReleaseMutex(hmtxRegPerUser);
        !           943: 
        !           944:          if (fSuccess) continue;
        !           945:          else goto failure_exit;
        !           946:       }
        !           947: 
        !           948:       ReleaseMutex(hmtxRegPerUser);
        !           949:    }
        !           950: 
        !           951:    return TRUE;
        !           952: 
        !           953: failure_exit:
        !           954: 
        !           955:    // When we're exiting because of a failure, we must clean up
        !           956:    // by closing any handles we've created along the way.
        !           957: 
        !           958:    if (hmtxRegGlobal) CloseHandle(hmtxRegGlobal);
        !           959: 
        !           960:    if (hmtxRegPerUser) CloseHandle(hmtxRegPerUser);
        !           961: 
        !           962:    if (hkGlobal) RegCloseKey(hkGlobal);
        !           963: 
        !           964:    return FALSE;
        !           965: }
        !           966: 
        !           967: BOOL LoadConfiguration()
        !           968: {
        !           969:    // This routine loads the per-user configuration data.
        !           970: 
        !           971:    // Two items are kept as configuration data:
        !           972:    //
        !           973:    //   fTextWrapDefault -- a BOOL which defines whether the text windows
        !           974:    //                       fold text at the right edge of the window.
        !           975:    //
        !           976:    //   A REG_MULTI_SZ list of file names.
        !           977:    //
        !           978:    //     This list represents the files which were open at the end of the
        !           979:    //     last RegMPad session. We'll attempt to reopen those files.
        !           980: 
        !           981:    LONG  lResult, cbData;
        !           982:    DWORD dwType;
        !           983:    PSZ   pszFileList= NULL;
        !           984: 
        !           985:    CHAR *pc;
        !           986: 
        !           987:    cbData= sizeof(fTextWrapDefault);
        !           988: 
        !           989:    lResult= RegQueryValueEx(hkPerUser, WORD_WRAP_DEFAULT, NULL,
        !           990:                             &dwType, (LPBYTE) &fTextWrapDefault, &cbData
        !           991:                            );
        !           992: 
        !           993:    if (lResult != ERROR_SUCCESS) return FALSE;
        !           994: 
        !           995:    cbData= 0;
        !           996: 
        !           997:    lResult= RegQueryValueEx(hkPerUser, LAST_FILE_SET, NULL, &dwType,
        !           998:                             NULL, &cbData
        !           999:                            );
        !          1000: 
        !          1001:    if (   lResult != ERROR_SUCCESS
        !          1002:        || dwType  != REG_MULTI_SZ
        !          1003:       ) return FALSE;
        !          1004: 
        !          1005:    pszFileList= (PSZ) _alloca(cbData);
        !          1006: 
        !          1007:    if (!pszFileList) return FALSE;
        !          1008: 
        !          1009:    lResult= RegQueryValueEx(hkPerUser, LAST_FILE_SET, NULL, &dwType,
        !          1010:                             (LPBYTE) pszFileList, &cbData
        !          1011:                            );
        !          1012: 
        !          1013:    if (lResult != ERROR_SUCCESS) return FALSE;
        !          1014: 
        !          1015:    for (pc= pszFileList; *pc; )
        !          1016:    {
        !          1017:       if (!AlreadyOpen(pc)) AddFile(pc);
        !          1018: 
        !          1019:       for (; *pc++; );
        !          1020:    }
        !          1021: 
        !          1022:    return TRUE;
        !          1023: }
        !          1024: 
        !          1025: BOOL SaveConfiguration()
        !          1026: {
        !          1027:    // This routine saves the per-user configuration data to the registry.
        !          1028:    // That data consists of two items:
        !          1029:    //
        !          1030:    //   fTextWrapDefault -- a BOOL which defines whether the text windows
        !          1031:    //                       fold text at the right edge of the window.
        !          1032:    //
        !          1033:    //   A REG_MULTI_SZ list of file names.
        !          1034:    //
        !          1035:    //     This list represents the files which were open at the end of the
        !          1036:    //     last RegMPad session.
        !          1037: 
        !          1038:    LONG  lResult;
        !          1039:    HWND  hwnd;
        !          1040:    PBYTE pb, pbNext;
        !          1041:    LONG  cb, cbTotal;
        !          1042: 
        !          1043:    lResult= RegSetValueEx(hkPerUser, WORD_WRAP_DEFAULT, 0, REG_DWORD,
        !          1044:                           (CONST LPBYTE) &fTextWrapDefault,
        !          1045:                           sizeof(fTextWrapDefault)
        !          1046:                          );
        !          1047: 
        !          1048:    if (lResult != ERROR_SUCCESS) return FALSE;
        !          1049: 
        !          1050:    for (hwnd= GetWindow(hwndMDIClient, GW_CHILD), cbTotal= 1;
        !          1051:         hwnd;
        !          1052:         hwnd= GetWindow(hwnd, GW_HWNDNEXT)
        !          1053:        )
        !          1054:    {
        !          1055:       // Skip if this is an icon title window...
        !          1056: 
        !          1057:       if (GetWindow(hwnd, GW_OWNER)) continue;
        !          1058: 
        !          1059:       if (GetWindowWord(hwnd, GWW_UNTITLED)) continue;
        !          1060: 
        !          1061:       cbTotal += GetWindowTextLength(hwnd) + 1;
        !          1062:    }
        !          1063: 
        !          1064:    pb= (PBYTE) _alloca(cbTotal);
        !          1065: 
        !          1066:    if (!pb) return FALSE;
        !          1067: 
        !          1068:    for (hwnd= GetWindow(hwndMDIClient, GW_CHILD), pbNext= pb;
        !          1069:         hwnd;
        !          1070:         hwnd= GetWindow(hwnd, GW_HWNDNEXT)
        !          1071:        )
        !          1072:    {
        !          1073:       // Skip if this is an icon title window...
        !          1074: 
        !          1075:       if (GetWindow(hwnd, GW_OWNER)) continue;
        !          1076: 
        !          1077:       if (GetWindowWord(hwnd, GWW_UNTITLED)) continue;
        !          1078: 
        !          1079:       cb= GetWindowTextLength(hwnd);
        !          1080: 
        !          1081:       GetWindowText(hwnd, pbNext, cb+1);
        !          1082: 
        !          1083:       pbNext+= cb+1;
        !          1084:    }
        !          1085: 
        !          1086:    *pbNext= 0;
        !          1087: 
        !          1088:    lResult= RegSetValueEx(hkPerUser, LAST_FILE_SET, 0, REG_MULTI_SZ,
        !          1089:                           pb, cbTotal
        !          1090:                          );
        !          1091: 
        !          1092:    return (lResult == ERROR_SUCCESS)? TRUE : FALSE;
        !          1093: }

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.