大家把ntfs分区上的文件拷贝到非ntfs分区上时, 可能偶尔遇到过下面的情况, 系统提示会有数据丢失, 这是怎么回事呢?

    实际上ntfs文件系统引入了"流"这个概念, 每个文件都可以有多个流, 而我们一般只使用了一个, 通过给文件分配更多的流, 可以实现某种意义上的"文件隐藏". 例如可以控制台中使用下面的命令建立一个文本文件:
dir d:>abc.txt
它列出d:根目录的所有文件, 然后将其重定向到文件abc.txt, 现在你可以检查一下abc.txt的大小和内容, 并记录下来. 然后再执行下面这条命令
dir c:>abc.txt:stream.txt
执行完毕后, 检查abc.txt, 大小和内容都没有变化, 但其实abc.txt已经多了一个流stream.txt, 而重定向的内容就输出到了它里面, 不信使用下面的命令看一下(注意流的名字也要以.txt结尾, 否则notepad就找不到了):
notepad abc.txt:stream.txt
这样我们就把一个文件隐藏了, dir命令看不见, 文件属性看不到, 资源管理器也看不到, 如果不知道流的名字, notepad也是无法访问的.
    实际上, 流还可以不依赖于文件, 下面的命令也是合法的(先不要试, 否则可能会有点麻烦):
dir e:>:stream.txt
这是把流绑到了文件夹上, 这种流就更隐蔽了. 一般情况下要想删除流只有将其宿主删除, 如果你执行了刚才的命令, 并且是在根文件夹上执行的, 如果你想删除它, 那就恭喜你要格盘了:). 不过通过写程序还是不难删除流的, 只要调用DeleteFile, 并提供流的名字就行了. 要想枚举一个文件中的所有流, 目前只能通过BackupRead来完成. 我写了一个小程序, 通过它可以枚举、删除、导入导出流中的数据, 下面的是它的代码(写的比较仓促, 可能还有一些bug, 不过主要功能都实现了, 它的名字叫nsvw, 即Ntfs Stream Viewer).
#include <windows.h>
#include 
<stdio.h>
#include 
<locale.h>
#include 
<wchar.h>
#include 
<malloc.h>
#include 
<stddef.h>


enum RUN_MODE
{
    SHOW_USAGE 
= 0,
    SHOW_STREAMS,
    DELETE_STREAMS,
    IMPORT_STREAM,
    EXPORT_STREAM,
}
;


LPCWSTR g_szObj 
= NULL;
LPCWSTR g_szStrm 
= NULL;
LPCWSTR g_szFile 
= NULL;


int ParseArgs( int argc, LPWSTR* argv )
{
    
if( argc == 1 || argc == 3 )
        
return SHOW_USAGE;

    g_szObj 
= *(argv + 1);
    
if( argc == 2 )
        
return SHOW_STREAMS;

    LPCWSTR act 
= *(argv + 2);
    
if( act[0!= L'-' && act[0!= L'/' )
        
return SHOW_USAGE;

    
if( act[1== L'd' )
        
return DELETE_STREAMS;

    
if( argc == 4 || argc > 5 )
        
return SHOW_USAGE;

    g_szStrm 
= *(argv + 3);
    g_szFile 
= *(argv + 4);
    
if( act[1== L'i' )
        
return IMPORT_STREAM;

    
if( act[1== L'e' )
        
return EXPORT_STREAM;

    
return SHOW_USAGE;
}



int ShowUsage()
{
    wprintf( L
"USAGE: "
        L
"nsvw file.a              :  view streams in file.a "
        L
"nsvw file.a -d s1 s2  :  delete stream s1, s2 and  from file.a "
        L
"nsvw file.a -i s1 file.b :  copy the content of file.b to stream s1 in file.a "
        L
"nsvw file.a -e s1 file.c :  copy the content of stream s1 in file.a to file.c "
        );
    
return 0;
}



int ShowStreams()
{
    HANDLE hFile 
= CreateFile( g_szObj, GENERIC_READ, FILE_SHARE_READ, NULL,
            OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
    
if( hFile == INVALID_HANDLE_VALUE )
    
{
        wprintf( L
"Unable to open object "%s" ", g_szObj );
        
return static_cast<int>( GetLastError() );
    }


    WIN32_STREAM_ID wsi 
= {0};
    WCHAR szStrmName[MAX_PATH];
    LPVOID pContext 
= NULL;

    BOOL bOk 
= TRUE;
    
int nCount = 0;
    
while( bOk )
    
{
        DWORD dwBytes 
= 0;
        LPBYTE buf 
= reinterpret_cast<LPBYTE>&wsi );
        DWORD dwSize 
= static_cast<DWORD>(offsetof(WIN32_STREAM_ID, cStreamName));
        bOk 
= BackupRead( hFile, buf, dwSize, &dwBytes, FALSE, FALSE, &pContext );
        
if!bOk || dwBytes == 0 )
            
break;
        
if( wsi.dwStreamNameSize > 0 )
        
{
            buf 
= reinterpret_cast<LPBYTE>( szStrmName );
            dwSize 
= wsi.dwStreamNameSize;
            BackupRead( hFile, buf, dwSize, 
&dwBytes, FALSE, FALSE, &pContext );
            szStrmName[dwSize 
/ sizeof(WCHAR)] = 0;
            wprintf( L
"NAME: "%s" SIZE: %I64d ", szStrmName, wsi.Size.QuadPart );
            
++nCount;
        }

        DWORD dw1, dw2;
        BackupSeek( hFile, wsi.Size.LowPart, wsi.Size.HighPart, 
&dw1, &dw2, &pContext );
    }


    DWORD dwError 
= GetLastError();
    ::BackupRead( hFile, NULL, 
0, NULL, TRUE, FALSE, &pContext );
    ::CloseHandle( hFile );

    wprintf( L
"Total %d stream(s). ", nCount );

    
return static_cast<int>( dwError );
}




void BuildStreamName( LPCWSTR szStrm, LPWSTR buf, size_t size )
{
    _snwprintf( buf, size, L
"%s:%s", g_szObj, szStrm );
    buf[size 
- 1= 0;
}




int DeleteStreams( int count, LPWSTR* streams ) 
{
    
const int nSize = MAX_PATH * 2;
    WCHAR szStrmName[nSize];
    size_t size 
= sizeof(szStrmName) / sizeof(WCHAR);

    
forint i = 0; i < count; ++i )
    
{
        BuildStreamName( 
*(streams + i), szStrmName, nSize );
        
if( ::DeleteFileW( szStrmName ) )
            wprintf( L
"stream %s was deleted. "*(streams + i) );
        
else
            wprintf( L
"unable to delete stream %s. "*(streams + i) );
    }


    
return 0;
}



int CopyStream( LPCWSTR szSrc, LPCWSTR szDst )
{
    
int nRet = 0;
    HANDLE hSrc 
= INVALID_HANDLE_VALUE, hDst = INVALID_HANDLE_VALUE;
    HANDLE hSrcFm 
= NULL, hDstFm = NULL;
    PVOID pSrc 
= NULL, pDst = NULL;

    __try
    
{
        hSrc 
= ::CreateFile( szSrc, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
        
if( hSrc == INVALID_HANDLE_VALUE )
            __leave;

        DWORD dwSize 
= ::GetFileSize( hSrc, NULL );
        
if( dwSize > 0 )
        
{
            hSrcFm 
= ::CreateFileMapping( hSrc, NULL, PAGE_READONLY, 00, NULL );
            
if( hSrcFm == NULL )
                __leave;

            pSrc 
= ::MapViewOfFile( hSrcFm, FILE_MAP_READ, 00, dwSize );
            
if( pSrc == NULL )
                __leave;
        }


        hDst 
= ::CreateFile( szDst, FILE_ALL_ACCESS, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
        
if( hDst == INVALID_HANDLE_VALUE )
            __leave;

        
if( dwSize > 0 )
        
{
            hDstFm 
= ::CreateFileMapping( hDst, NULL, PAGE_READWRITE, 0, dwSize, NULL );
            
if( hDstFm == NULL )
                __leave;

            pDst 
= ::MapViewOfFile( hDstFm, FILE_MAP_WRITE, 00, dwSize );
            
if( pDst == NULL )
                __leave;

            memcpy( pDst, pSrc, dwSize );
        }

        
else
        
{
            ::SetFilePointer( hDst, 
00, FILE_BEGIN );
            ::SetEndOfFile( hDst );
        }

    }

    __finally
    
{
        nRet 
= static_cast<int>( ::GetLastError() );
    }


    
if( pDst != NULL )
        ::UnmapViewOfFile( pDst );
    
if( pSrc != NULL )
        ::UnmapViewOfFile( pSrc );
    
if( hDstFm != NULL )
        ::CloseHandle( hDstFm );
    
if( hSrcFm != NULL )
        ::CloseHandle( hSrcFm );
    
if( hDst != INVALID_HANDLE_VALUE )
        ::CloseHandle( hDst );
    
if( hSrc != INVALID_HANDLE_VALUE )
        ::CloseHandle( hSrc );

    
return nRet;
}



int ImportStream()
{
    
const int nSize = MAX_PATH * 2;
    WCHAR szStrmName[nSize];
    size_t size 
= sizeof(szStrmName) / sizeof(WCHAR);
    BuildStreamName( g_szStrm, szStrmName, nSize );
    
int nRes = CopyStream( g_szFile, szStrmName );
    
if( nRes != 0 )
        wprintf( L
"Import failed. " );
    
else
        wprintf( L
"Import completed. " );
    
return nRes;
}



int ExportStream()
{
    
const int nSize = MAX_PATH * 2;
    WCHAR szStrmName[nSize];
    size_t size 
= sizeof(szStrmName) / sizeof(WCHAR);
    BuildStreamName( g_szStrm, szStrmName, nSize );
    
int nRes = CopyStream( szStrmName, g_szFile );
    
if( nRes != 0 )
        wprintf( L
"Export failed. " );
    
else
        wprintf( L
"Export completed. " );
    
return nRes;

}




int __cdecl wmain( int argc, LPWSTR* argv )
{
    
int nRetCode = 0;

    _wsetlocale( LC_ALL, L
".OCP" );
    wprintf( L
"NTFS Stream Viewer        VERSION 1.0 " );

    
switch( ParseArgs( argc, argv ) )
    
{
    
case SHOW_USAGE:
        nRetCode 
= ShowUsage();
        
break;

    
case SHOW_STREAMS:
        nRetCode 
= ShowStreams();
        
break;

    
case DELETE_STREAMS:
        nRetCode 
= DeleteStreams( argc - 3, argv + 3 );
        
break;

    
case IMPORT_STREAM:
        nRetCode 
= ImportStream();
        
break;

    
case EXPORT_STREAM:
        nRetCode 
= ExportStream();
        
break;

    
default:
        wprintf( L
"internel error! " );
        nRetCode 
= -1;
        
break;
    }


    
return nRetCode;
}