Friday, October 23, 2009

A MSSCCI Primer (How Microsoft Source Code Control API works)

I’ve intended to publish this article long time ago, and never found the time to finish all I wanted to wanted to be in it. I’ll publish it now as is, hoping it may still help someone get started…

------------------------

I've been recently asked what's the correct order in which MSSCCI functions should be called for integrating an IDE application like Visual Studio with a MSSCCI (Microsoft Source Code Control) provider.

While looking for references, I realized that the MSSCCI documentation in MSDN is a good reference for the API functions, but it doesn’t do a good job in describing MSSCCI concepts and how to start implementing a MSSCCI-compliant source control provider. It looks like it's time to write such page...

What is a MSSCCI dll?

Basically, a MSSCCI source control provider is a 32-bit Native dynamic link library, exporting a couple of predefined functions with names beginning with “Scc” (e.g. SccAdd, SccCheckout, etc). A MSSCCI provider doesn't need to implement all these functions, but there is a subset of the functions that must be implemented by any provider (more on that later) to make the provider be usable by an Integrated Development Environment (IDE) like Visual Studio.

An IDE loads the MSSCCI dll by calling LoadLibrary or LoadLibraryEx. After that it usually looks up and binds the MSSCCI function by name by calling GetProcAddress, and will start calling the Scc functions directly. When the IDE is done using the MSSCCI provider it can unload the dll by calling FreeLibrary.

There are no Unicode versions of the functions, therefore Unicode-ANSI conversion may need to be performed as necessary by both IDE and the source control provider.

Because the MSSCCI providers are 32 bit dlls, they cannot be loaded directly in 64 bit processes. To the best of my knowledge, there are no plans of creating a 64-bit version of the interface for loading a MSSCCI provider in a 64-bit process. However this limitation can be circumvented by creating a 32-bit COM object that loads and wraps the MSSCCI dll, further exposing COM interfaces functions similar to MSSCCI functions; the COM wrapper object can be co-created in 64-bit processes.

MSSCCI Connections

To perform source control operations with a MSSCCI provider, an IDE needs to open a connection; this is done with the SccOpenProject function. A connection is simply an association (1:1 mapping) between a folder in the source control storage database (project name in MSSCCI terms) and a folder on local disk (local project path). An example (for SourceSafe) might be:

        C:\My Solutions\Wincheat <----> "$/Solutions/Wincheat", CDFAAAA

When the connection is open, the provider can also perform authentication of the user for accessing the source control storage, etc.

Note there are no restriction on the folders mapping. Multiple connections/mappings can be opened from the same local folder pointing to different folders in the scc database; multiple connections can be opened from various local folders pointing to the same project in the scc database; and mapped projects can overlap.

After opening a source control connection, further source control operations are possible using that connection.Within a connection, files are identified only based on their local path; the path must be under the cone defined by the connection local root. The path in storage is automatically deduced based on the connection root folders. Files outside this connection root should not be controllable through this connection.

E.g. The C:\My Solutions\Wincheat\WinCheat.sln can be controlled through the connection above and is associated with $/Solutions/Wincheat/Wincheat.sln. The file C:\My Solutions\OtherSolution\OtherSolution.sln can’t be controlled using this connection, even though in the scc database there may be an matching item at $/Solutions/OtherSolution/OtherSolution.sln.

In the following example, there are 3 connections open

C:\My Solutions\Solution1             <----> "$/Solution1", BCDAAAA
D:\Project1                           <----> "$/Solution1/Project1", KRFAAAA
C:\My Solutions\Solution1\SubProject  <----> "$/Solution1/Project1", KRFAAAA

Depending on which connection is used, the same file in storage $/Solution1/Project1/File.txt can be referenced either as C:\My Solutions\Solution1\Project1\File.txt, D:\Project1\File.txt or C:\My Solutions\Solution1\SubProject\File.txt

A MSSCCI provider implementer should not make assumptions (based on enlistment or other similar mapping information specific to that provider) that a file in the storage is associated with only one file on local disk - at any time a new MSSCCI connection can be opened from any folder on local disk and the same file in storage can be accessed using that connection.

When a connection is no longer needed, the IDE should close it using the SccCloseProject function.

The server path used for opening a connection is specific to each source control provider. An IDE application has no idea of the format (and should not make any assumptions on the format). You may be asking yourself where does the projName string used in SccOpenProject comes from. Usually, during an Open from source control operation the IDE will call SccGetProjPath which allows the user to select a folder in the source control database. The string identifying the selected project is returned to the IDE, and used immediately in new connections, or is persisted in solution/project files and used on subsequent open from disk of that project/solution. Also, the IDE may attempt to retrieve server path strings from mssccprj.scc file if the source control provider creates and maintains such files.

A typical fragment showing opening a connection could look like this:

SccOpenProject(context1, project1, localpath1) open in context1 a connection
project1<---->localpath1
SccCheckin/SccGet/etc invoke source control functions using the connection open by using file paths under localpath1
SccCloseProject(context1) close the connection open in context1

 MSSCCI Contexts

You may have noticed that most of the MSSCCI functions have an argument LPVOID pvContext.

A context is obtained by calling SccInitialize and can be disposed of by calling SccUninitialize.

A context allows identifying the connection that should be used with a specific operation like Get, Checkin, etc, because at one point in time, at most one connection can be opened within a context.

A context is simply a data structure specific to each source control provider; it usually contains information like flags specifying whether a connection is opened or not within that context, information about the connection open, etc.

The typical order of operations with a source control database is:

SccInitialize() receives context
SccOpenProject(context, project, localpath) open in this context a connection
project<---->localpath
SccCheckin(context)/SccGet(context)/etc perform any source control operations using the context and the project<---->localpath connection. The file paths needs to be
SccCloseProject(context) close the connection open in this context
SccUninitialize(context) dispose of context

If an IDE needs to open a different connection to the database:

a) it can reuse an existing context for the new connection, but first it will have to close the currently open connection within that context. Typical order of operations is:

SccInitialize() receives context1
SccOpenProject(context1, project1, localpath1) open in context1 connection
project1<---->localpath1
SccCloseProject(context1) close the connection open in context1
SccOpenProject(context1, project2, localpath2) open in context1 new connection
project2<---->localpath2
SccCloseProject(context1) close the new connection open in context1
SccUninitialize(context1) dispose of context1

b) it can create a new context and open the new connection with that context. For this to work, a provider must support the SCC_CAP_REENTRANT capability to declare it is reentrant, thread-safe and that it supports opening multiple connections. An order of operations may be:

SccInitialize() receives context1
SccOpenProject(context1, project1, localpath1) open in context1 connection
project1<---->localpath1
SccInitialize() receives context2
SccOpenProject(context2, project2, localpath2) open in context1 new connection
project2<---->localpath2
SccCloseProject(context1) close the new connection open in context1
SccCloseProject(context2) close the new connection open in context2
SccUninitialize(context2) dispose of context2
SccUninitialize(context1) dispose of context1

Note that operations on multiple contexts can be interspersed any way you like as long as the order of the operation within a context is the correct one.

Versions, capabilities and extended capabilities

When calling into a MSSCCI provider, an IDE needs to know which source control functionality is supported by the MSSCCI provider (which functions are implemented). There are a couple of functions that return this information.

SccGetVersion is one of the first functions an IDE will call. This returns the version of the MSSCCI provider (currently there can be providers implementing versions 1.0, 1.1, 1.2 or 1.3 of the MSSCCI spec). Certain functions are defined only by higher number versions MSSCCI spec (were introduced later). Thus if a provider reports supporting version 1.1 of the spec, an IDE won’t even bother binding and calling a function like SccWillCreateSccFile introduced in version 1.2 of the spec.

When a MSSCCI provider declares implementing a specific version of a spec, it is not required to implement all the functions defined by that spec version. For instance, a MSSCCI 1.2 provider may implement SccCreateSubProject, but may not support features like batching (SccBeginBatch) or viewing file history (SccHistory).  An IDE will not call a function if the MSSCCI provider does not declare supporting it.

To define the functionality supported, a provider should return the lpSccCaps capabilities flags during the call to SccInitialize(). E.g. Set and return the bit SCC_CAP_HISTORY in the lpSccCaps if your provider supports history operations.

For MSSCCI 1.3 and later providers, additional capabilities flags can be returned by implementing the SccGetExtendedCapabilities() function.

Beside flags specifying which functions are implemented, capabilities flags may specify other information about the provider, e.g. whether comments are supported on certain operations.

Note that an IDE may only use providers implementing specific functionality. E.g. VisualStudio 2002 and later can only use source control providers supporting multiple connections (reentrant providers, with version 1.1 or greater and defining SCC_CAP_REENTRANT flag).

If the provider does not define a capability flag introduced by a later MSSCCI spec version than the version implemented by the provider, the IDE assumes that functionality is not available in the provider, and may fallback to other scc operations. E.g. If a MSSCCI 1.1 provider is used with VS2003 and later, the lack of the SCC_CAP_MULTICHECKOUT capability will make VS attempt other ways of determining whether the provider supports or not multiple checkouts. Or, if a MSSCCI 1.2 provider is used with VS2005 and later to control websites, recursive enumeration of files in the source control database using SccPopulateDirList is not possible and VS will try alternative (and slower) ways of achieving the same result, using SccPopulateList and recursive SccGet operations.

Windows 7 cannot access Smb shares on Infrant ReadyNAS

 

Yesterday I installed Windows 7 upgrading from Vista, which preserved all my settings. Almost. After the first login, Windows 7 popped up balloon warnings that “Could not reconnect all network drives”

NetworkDrives

It turned out that all the drives I had mapped to Samba shares on the Infrant ReadyNAS system and that were working fine in Vista were now inaccessible, and they were showing as disconnected in Windows Explorer.

NetworkDrives2

Double clicking the shares in Windows Explorer, or attempting to access the server by address \\alinc-nas was displaying a logon prompt. Typing the correct password and pressing OK was useless, the logon dialog came back with a “Logon failure: unknown user name or bad password” error.

Logon2 

Attempting to view the shares from command line with “net view” was no better, this one complained “System error 53 has occurred / The network path was not found” or “System error 5 has occurred / Access is denied”

Command

I searched the net and eventually I found the solution. Thanks to Brandon for his article!

The problem can be solved by modifying a security policy, as described below:

- Open the StartMenu and search for “Local Security Policy”. Right click the application icon and choose to RunAsAdministrator. (alternatively you can open the Run dialog and launch “secpol.msc”).

- In the “Local Security Policy” window, expand SecuritySetting/LocalPolicies/SecurityOptions folders in the left pane tree and locate “Network security: LAN Manager authentication level” in the right pane list.

- Double click the line and change its value from the default “Send NTLMv2 response only” into “Send LM & NTLM – use NTLMv2 session security if negotiated”. Click Ok and agree with the warning. You’re done! The shares should now be accessible (there is no need to reboot).

image

image