/*++ Copyright (c) Microsoft Corporation. All rights reserved. For Internal use only! Module Name: INFSCAN installscan.cpp Abstract: Individual install section scanner class main entry point InfScan::ScanInstallSection WARNING! WARNING! All of this implementation relies on intimate knowledge of SetupAPI's SETUPAPI!SetupInstallFromInfSection It is re-implemented here for speed due to having to process 700+ INF's in a single go at the cost of having to maintain this. DO NOT (I repeat) DO NOT re-implement the code here without consultation with SetupAPI owner. History: Created July 2001 - JamieHun --*/ #include "precomp.h" #pragma hdrstop InstallScan::InstallScan() /*++ Routine Description: Initialization --*/ { pGlobalScan = NULL; pInfScan = NULL; PlatformMask = 0; pTargetDirectory = NULL; NotDeviceInstall = false; HasDependentFileChanged = false; } InstallScan::~InstallScan() /*++ Routine Description: Cleanup allocated data/handles --*/ { } void InstallScan::AddHWIDs(const StringSet & hwids) /*++ Routine Description: Add to list of HWIDs effected by this section --*/ { StringSet::iterator i; for(i = hwids.begin(); i != hwids.end(); i++) { HWIDs.insert(*i); } } void InstallScan::GetHWIDs(StringSet & hwids) /*++ Routine Description: Retrieve list of HWIDs effected by this section --*/ { StringSet::iterator i; for(i = HWIDs.begin(); i != HWIDs.end(); i++) { hwids.insert(*i); } } int InstallScan::ScanInstallSection() /*++ Routine Description: Main entry point for processing an install section WARNING! tightly coupled with SetupAPI implementation that could change MAINTAINANCE: keep this in step with supported Needs/nesting syntax MAINTAINANCE: keep in step with install processing Arguments: NONE Return Value: 0 on success --*/ { int res; // // prime initial list of search INF's // Layouts(); int count = 1; // no more than 1 res = RecurseKeyword(Section,TEXT("Include"),IncludeCallback,count); if(res != 0) { return res; } if(count<0) { Fail(MSG_MULTIPLE_INCLUDE,Section); } else if(Pedantic() && (count<1)) { Fail(MSG_INCLUDE,Section); } count = 1; // no more than 1 res = RecurseKeyword(Section,TEXT("Needs"),NeedsCallback,count); if(res != 0) { return res; } if(count<0) { Fail(MSG_MULTIPLE_NEEDS,Section); } else if(Pedantic() && (count<1)) { Fail(MSG_NEEDS,Section); } return CheckInstallSubSection(Section); } int InstallScan::RecurseKeyword(const SafeString & sect,const SafeString & keyword,RecurseKeywordCallback callback,int & count) /*++ Routine Description: Section parsing workhorse WARNING! Uses knowledge about how SetupAPI searches INF lists, could change MAINTAINANCE: Keep search algorithm in step with SetupAPI see aslo QuerySourceFile, GetLineCount and DoesSectionExist given keyword = val[,val...] each value is converted to lower-case and callback is called with information about what inf/section the value is in. Arguments: sect - name of section to search keyword - name of keyword callback - callback function for each hit count - must be initialized to max # of callbacks to invoke - returns -1 if there are more entries than wanted Return Value: 0 on success --*/ { INFCONTEXT context; int res; int i; SafeString value; // // we didn't call SetupOpenAppendInf for any INF's, so instead // enumerate all "included inf's" using our view of the world // this allows us to reset our view for each install section // to validate include/needs processing // ParseInfContextList::iterator AnInf; for(AnInf = InfSearchList.begin(); AnInf != InfSearchList.end(); AnInf++) { ParseInfContextBlob & TheInf = *AnInf; if(TheInf->InfHandle == INVALID_HANDLE_VALUE) { // // a fail-loaded inf // continue; } if(!SetupFindFirstLine(TheInf->InfHandle,sect.c_str(),keyword.c_str(),&context)) { // // can't find (keyword in) section in this inf // continue; } do { if(count>=0) { count--; if(count<0) { return 0; } } for(i=1;MyGetStringField(&context,i,value);i++) { res = (this->*callback)(TheInf,sect,keyword,value); if(res != 0) { return res; } } } while (SetupFindNextMatchLine(&context,keyword.c_str(),&context)); } return 0; } int InstallScan::IncludeCallback(ParseInfContextBlob & TheInf,const SafeString & sect,const SafeString & keyword,const SafeString & val) /*++ Routine Description: Callback for "INCLUDE" processing Add INF to the search-list MAINTAINANCE: keep this in step with supported Include/nesting syntax Arguments: TheInf - INF that the include was found in sect - section that the include was found in keyword - "INCLUDE" val - name of INF to include Return Value: 0 on success --*/ { if(!val.length()) { return 0; } // // simply 'append' the INF // return Include(val); } int InstallScan::NeedsCallback(ParseInfContextBlob & TheInf,const SafeString & sect,const SafeString & keyword,const SafeString & val) /*++ Routine Description: Callback for "NEEDS" processing process the specified needed section MAINTAINANCE: keep this in step with supported Needs/nesting syntax Arguments: TheInf - INF that the needs was found in sect - section that the needs was found in keyword - "NEEDS" val - name of section to process Return Value: 0 on success --*/ { if(!val.length()) { return 0; } INFCONTEXT context; // // concerning if section doesn't exist // if(!DoesSectionExist(val)) { Fail(MSG_NEEDS_NOSECT,sect,val); return 0; } // // catch recursive includes/needs // set want-count to zero so we don't actually recurse // (we need to use RecurseKeyword to ensure correct searching) // int res; int count = 0; // no more than zero (query) res = RecurseKeyword(val,TEXT("Include"),IncludeCallback,count); if(res != 0) { return res; } if(count<0) { Fail(MSG_RECURSIVE_INCLUDE,sect,val); } count = 0; // no more than zero (query) res = RecurseKeyword(val,TEXT("Needs"),NeedsCallback,count); if(res != 0) { return res; } if(count<0) { Fail(MSG_RECURSIVE_NEEDS,sect,val); } return CheckInstallSubSection(val); } int InstallScan::CheckInstallSubSection(const SafeString & section) /*++ Routine Description: Effectively SetupInstallInfSection Processed for primary and each needed section WARNING! tightly coupled with SetupAPI implementation that could change MAINTAINANCE: keep in step with install processing Arguments: NONE Return Value: 0 on success --*/ { int res; res = CheckCopyFiles(section); if(res != 0) { return res; } return 0; } int InstallScan::CheckCopyFiles(const SafeString & section) /*++ Routine Description: Process CopyFiles entries in install section WARNING! tightly coupled with SetupAPI implementation that could change MAINTAINANCE: keep in step with CopyFiles processing Arguments: section - section to check for CopyFiles= entries Return Value: 0 on success --*/ { // // enumerate all CopyFiles entries in this section // int count = -1; // there can be any # of CopyFiles keywords in a section return RecurseKeyword(section,TEXT("CopyFiles"),CopyFilesCallback,count); } int InstallScan::CopyFilesCallback(ParseInfContextBlob & TheInf,const SafeString & section,const SafeString & keyword,const SafeString & val) /*++ Routine Description: Process CopyFiles entries in install section (callback) WARNING! tightly coupled with SetupAPI implementation that could change MAINTAINANCE: keep in step with CopyFiles processing MAINTAINANCE: keep in step with CopyFiles section syntax MAINTAINANCE: keep in step with DestinationDirs section syntax Arguments: TheInf - INF that the needs was found in sect - section that the needs was found in keyword - "CopyFiles" val - section or @file Return Value: 0 on success --*/ { if(!val.length()) { return 0; } // // a single copy-files section entry // INFCONTEXT context; int res; bool FoundAny = false; if(val[0] == TEXT('@')) { // // immediate copy, use default target as specified in context inf // SafeString source = val.substr(1); return CheckSingleCopyFile(TheInf,TheInf->GetDefaultTargetDirectory(),source,source,section); } // // interpret as a section, we need to enumerate through all the INF's // that have this particular copy section // ParseInfContextList::iterator AnInf; for(AnInf = InfSearchList.begin(); AnInf != InfSearchList.end(); AnInf++) { ParseInfContextBlob & TheCopyInf = *AnInf; if(TheCopyInf->InfHandle == INVALID_HANDLE_VALUE) { // // a fail-loaded inf // continue; } DWORD flgs = TheCopyInf->DoingCopySection(val,PlatformMask); if(flgs!=0) { // // we have already done this copy section in this INF // if(flgs != (DWORD)(-1)) { FoundAny = true; if(flgs & PLATFORM_MASK_MODIFIEDFILES) { // // hint that one of the files in this copy section // was determined to be modified // HasDependentFileChanged = true; } } continue; } // // section contains multiple copy-files // if(!SetupFindFirstLine(TheCopyInf->InfHandle,val.c_str(),NULL,&context)) { if(SetupGetLineCount(TheCopyInf->InfHandle, val.c_str()) != 0) { // // make a note that this inf doesn't have this copy section // TheCopyInf->NoCopySection(val); } else { FoundAny = true; } continue; } // // we've found copy section inside TheCopyInf that we haven't // previously processed for this platform // FoundAny = true; // // determine target directory for this copy section // this must be in same inf as copy section // TargetDirectoryEntry *pTargDir = TheCopyInf->GetTargetDirectory(val); SafeString target; SafeString source; // // enumerate through each = // do { // // each line consists of [,] // if(!MyGetStringField(&context,1,target) || !target.length()) { continue; } if(!MyGetStringField(&context,2,source) || !source.length()) { source = target; } // // source/target always in lower-case to aid comparisons // res = CheckSingleCopyFile(TheCopyInf,pTargDir,target,source,val); if(res != 0) { return res; } } while(SetupFindNextLine(&context,&context)); } if(!FoundAny) { Fail(MSG_MISSING_COPY_SECTION,val,section); } return 0; } int InstallScan::CheckSingleCopyFile(ParseInfContextBlob & TheInf,TargetDirectoryEntry *pTargDir,const SafeString & target,const SafeString & source,const SafeString & section) /*++ Routine Description: Process a single CopyFile entry (either immediate or not) WARNING! tightly coupled with SetupAPI implementation that could change MAINTAINANCE: keep in step with CopyFiles processing MAINTAINANCE: keep in step with CopyFiles section syntax Arguments: TheInf - Inf that the copy section is in pTargDir - relevent target directory entry for this copy target - name of target file source - name of source file section - name of copyfiles section, or of install section Return Value: 0 on success --*/ { if(!target.length()) { return 0; } if(!source.length()) { return 0; } // // this is a single copy target/source entry // // need to lookup file in SourceDisksFiles // for matching platform // // for each file, need to // (1) queue source name // (2) textmode & guimode validate target // SourceDisksFilesList sources; int res = QuerySourceFile(TheInf,section,source,sources); if(res != 0) { return res; } if(!sources.size()) { return 0; } SafeString destdir; if(pGlobalScan->TargetsToo) { int IsDriverFile = NotDeviceInstall ? 0 : 1; if(IsDriverFile) { // // make a special note that we determined at least once that this is a driver file // wrt this primary INF // (this saves us reporting that file is and is not a driver file for a given inf) // pInfScan->DriverSourceCheck.insert(source); } else if(pInfScan->DriverSourceCheck.find(source) != pInfScan->DriverSourceCheck.end()) { IsDriverFile = 1; // we found this to be a driver fill during driver pass } // // we want a more detailed output for further analysis // or import into database // basic_ostringstream line; // // source name // line << QuoteIt(source); // // target location // if(pTargDir) { line << TEXT(",") << pTargDir->DirId << TEXT(",") << QuoteIt(pTargDir->SubDir); } else { line << TEXT(",,"); } // // final name and if this appears to be a driver file or not // line << TEXT(",") << QuoteIt(target) << TEXT(",") << (NotDeviceInstall ? TEXT("0") : TEXT("1")); // // report primary INF // line << TEXT(",") << QuoteIt(GetFileNamePart(pInfScan->PrimaryInf->InfName)); if(!IsDriverFile) { // // if this doesn't appear to be a driver file, report install section // line << TEXT(",") << QuoteIt(Section); } pInfScan->SourceFiles.push_back(line.str()); } else { // // just source will do // pInfScan->SourceFiles.push_back(source); } if((pGlobalScan->BuildChangedDevices & BUILD_CHANGED_DEVICES_DEPCHANGED) && !pInfScan->HasDependentFileChanged && pGlobalScan->IsFileChanged(source)) { HasDependentFileChanged = true; // // since we only do a copy section once, we need to make the copy section // 'dirty' so next time we reference section out of here // TheInf->DoingCopySection(section,PLATFORM_MASK_MODIFIEDFILES); } if(pGlobalScan->IgnoreErrors) { // // if not interested in checking errors // we're done // return 0; } SourceDisksFilesList::iterator i; for(i = sources.begin(); i != sources.end(); i++) { i->Used = true; if((i->UpgradeDisposition != 3) || (i->TextModeDisposition != 3)) { // // need to do a consistency check // if(!pTargDir) { // // error already reported (?) // continue; } if(pTargDir->DirId != 10) { // // DirId should be 10 or normalized off 10 // Fail(MSG_TEXTMODE_TARGET_MISMATCH,section,source); continue; } if(!i->TargetDirectory) { // // error already reported // continue; } IntToString::iterator gd = pGlobalScan->GlobalDirectories.find(i->TargetDirectory); if(gd == pGlobalScan->GlobalDirectories.end()) { // // no textmode subdir for specified dir // Fail(MSG_TEXTMODE_TARGET_UNKNOWN,section,source); continue; } SafeString gm_target = PathConcat(pTargDir->SubDir,target); SafeString tm_target = PathConcat(gd->second,i->TargetName.length() ? i->TargetName : source); if(gm_target.compare(tm_target)!=0) { Fail(MSG_TEXTMODE_TARGET_MISMATCH,section,source); continue; } // // get here, user-mode and text-mode match // } } return 0; } int InstallScan::Include(const SafeString & name) /*++ Routine Description: Process a single Include entry WARNING! tightly coupled with SetupAPI implementation that could change MAINTAINANCE: keep in step with INCLUDE processing Arguments: name - argument passed into INCLUDE Return Value: 0 on success --*/ { // // nested include // if(Included.find(name) == Included.end()) { // // first time this install section has come across this file // Included.insert(name); // so we don't try to re-load ParseInfContextBlob & ThisInf = pInfScan->Include(name,true); InfSearchList.push_back(ThisInf); } return 0; } int InstallScan::Layouts() /*++ Routine Description: Prime search list with primary inf and list of layout files WARNING! tightly coupled with SetupAPI implementation that could change MAINTAINANCE: keep in step with LAYOUT processing DISCREPENCY: layout files list currently limited to layout.inf Arguments: NONE Return Value: 0 on success --*/ { // // start the search list with the default inf // Included.insert(pInfScan->PrimaryInf->InfName); InfSearchList.push_back(pInfScan->PrimaryInf); // // start the search list with layout inf's // // we can do this with pGlobalScan as the data is readable but not modified // there is an interlocked dependency on multi-processors // ParseInfContextList::iterator i; for(i = pGlobalScan->LayoutInfs.begin(); i != pGlobalScan->LayoutInfs.end(); i++) { Included.insert((*i)->InfName); // so we don't try to re-load InfSearchList.push_back(*i); // interlocked dependency } return 0; } int InstallScan::QuerySourceFile(ParseInfContextBlob & TheInf,const SafeString & section,const SafeString & source,SourceDisksFilesList & Target) /*++ Routine Description: Determine list of possible source entries for given source file This can be zero (bad layout) 1 (typical) >1 (multiple targets) Arguments: TheInf - INF of the copy section, or install section section - name of the copy section or install section (for error reporting only) source - name of source file Target - returned list of source media information Return Value: 0 on success --*/ { int res =0; Target.clear(); // // return list of entries matching source file // DWORD platforms = pGlobalScan->Version.PlatformMask & PlatformMask & (PLATFORM_MASK_NT|PLATFORM_MASK_WIN); // // attempt to search within context of specified inf // res = TheInf->QuerySourceFile(platforms,section,source,Target); if(res != 0) { if(res<0) { // // non-fatal // res = 0; } return res; } if(Target.empty()) { // // now attempt to search within context of any inf's // ParseInfContextList::iterator AnInf; for(AnInf = InfSearchList.begin(); AnInf != InfSearchList.end(); AnInf++) { ParseInfContextBlob & TheLayoutInf = *AnInf; if(TheLayoutInf->InfHandle == INVALID_HANDLE_VALUE) { // // a fail-loaded inf // continue; } res = TheLayoutInf->QuerySourceFile(platforms,section,source,Target); if(res != 0) { if(res<0) { // // non-fatal // res = 0; } return res; } if(!Target.empty()) { // // found a hit // break; } } } if(Target.empty()) { // // not found // Fail(MSG_SOURCE_NOT_LISTED,section,source); return 0; } return 0; } LONG InstallScan::GetLineCount(const SafeString & section) /*++ Routine Description: Simulates SetupGetLineCount WARNING! Uses knowledge about how SetupAPI searches INF lists, could change MAINTAINANCE: Keep search algorithm in step with SetupAPI reference RecurseKeyword Arguments: section - name of section to count Return Value: # of lines or -1 if no sections found --*/ { INFCONTEXT context; int res; int i; SafeString value; LONG count = -1; ParseInfContextList::iterator AnInf; for(AnInf = InfSearchList.begin(); AnInf != InfSearchList.end(); AnInf++) { ParseInfContextBlob & TheInf = *AnInf; if(TheInf->InfHandle == INVALID_HANDLE_VALUE) { // // a fail-loaded inf // continue; } LONG actcount = SetupGetLineCount(TheInf->InfHandle,section.c_str()); if(actcount >= 0) { if(count<=0) { count = actcount; } else { count += actcount; } } } return 0; } bool InstallScan::DoesSectionExist(const SafeString & section) /*++ Routine Description: Optimally simulates "SetupGetLineCount()>=0" WARNING! Uses knowledge about how SetupAPI searches INF lists, could change MAINTAINANCE: Keep search algorithm in step with SetupAPI reference SetupGetLineCount and RecurseKeyword Arguments: section - name of section to count Return Value: true if named section exists --*/ { // // optimized version of (GetLineCount(section)>=0) // INFCONTEXT context; int res; int i; SafeString value; ParseInfContextList::iterator AnInf; for(AnInf = InfSearchList.begin(); AnInf != InfSearchList.end(); AnInf++) { ParseInfContextBlob & TheInf = *AnInf; if(TheInf->InfHandle == INVALID_HANDLE_VALUE) { // // a fail-loaded inf // continue; } LONG actcount = SetupGetLineCount(TheInf->InfHandle,section.c_str()); if(actcount >= 0) { return true; } } return false; }