• Great code

    From Bonita Montero@3:633/10 to All on Mon Jun 15 11:10:46 2026
    I've written a tool called "bg" to combine Windows' runas with start.
    One parameter is the affinity-mask for the processors which the star-
    ted program is attached to. The below code is the parsing function.
    The code wouldn't apply if this function has to be performance since
    I work with regexes. But having this this way makes the code rather
    short for what is required - imagine you'd have to do that in C.

    bool params::state::checkAffinity( params &pms )
    {
    if( _wcsicmp( m_todo.front(), L"--affinity" ) != 0 )
    return false;
    if( m_todo.size() < 2 )
    {
    m_todo = {};
    m_errs.emplace_back( L"please specify affinity" );
    cerr << "" << endl;
    return false;
    };
    DWORD_PTR dwpAff = 0;
    match_results<wstring_view::iterator> match;
    defer chop2( [&] { m_todo = m_todo.subspan( 2 ); } );
    wstring_view svPattern( m_todo[1] );
    constexpr const wchar_t
    *RxHex = L"^0x([0-9a-f]+)$",
    *RxBin = sizeof( DWORD_PTR ) == 8 ? L"^b((?:[01]+){1,64})$" : L"^b((?:[01]+){1,32})$",
    *RxRange = L"^([0-9]+)(?:-([0-9]+))?(,)?";
    if( wregex rxHex( RxHex, regex_constants::icase ); regex_match( svPattern.begin(), svPattern.end(), match, rxHex ) )
    wistringstream( wstring( svPattern ) ) >> hex >> dwpAff;
    else if( wregex rxBinary( RxBin, regex_constants::icase );
    regex_match( svPattern.begin(), svPattern.end(), match, rxBinary ) )
    for( wchar_t c : wstring_view( match[1].first, match[1].second ) )
    dwpAff = dwpAff << 1 | (c - '0');
    else
    {
    defer affZero( [&] { dwpAff = 0; } );
    auto it = svPattern.begin();
    wregex rx( RxRange );
    match_results<wstring_view::iterator> match;
    for( ; regex_search( it, svPattern.end(), match, rx ); it = match[0].second )
    {
    if( match[3].matched && match[3].second == svPattern.end() )
    break;
    unsigned from, to;
    wistringstream( wstring( match[1].first, match[1].second )
    ) >> from;
    if( match[2].matched )
    {
    wistringstream( wstring( match[2].first,
    match[2].second ) ) >> to;
    ++to;
    }
    else
    to = from + 1;
    constexpr unsigned BITS = sizeof(ULONG_PTR) * 8;
    if( from >= to || from >= BITS || to > BITS )
    break;
    constexpr DWORD_PTR ALL = -1;
    DWORD_PTR dwpBits = ALL << from;
    if( to < BITS )
    dwpBits &= ~(ALL << to);
    dwpAff |= dwpBits;
    }
    if( it == svPattern.end() )
    affZero.disable();
    }
    if( !dwpAff )
    {
    wostringstream woss;
    woss << "invalid affinity pattern: " << svPattern;
    m_errs.emplace_back( woss.str() );
    return true;
    }
    DWORD_PTR dwpProc, dwpSys;
    GetProcessAffinityMask( GetCurrentProcess(), &dwpProc, &dwpSys );
    if( (dwpAff &= dwpProc) )
    pms.dwpAffinity = dwpAff;
    return true;
    }

    --- PyGate Linux v1.5.16
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Lawrence D?Oliveiro@3:633/10 to All on Tue Jun 16 07:39:56 2026
    On Mon, 15 Jun 2026 11:10:46 +0200, Bonita Montero wrote:

    I've written a tool called "bg" to combine Windows' runas with
    start.

    Sort of a Windows answer to systemd? Somebody is already working on that <https://github.com/Jamesits/SvcGuest>.

    One parameter is the affinity-mask for the processors which the
    started program is attached to.

    They haven?t yet added that capability (which systemd has). Perhaps
    you could contribute code ...

    --- PyGate Linux v1.5.17
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bonita Montero@3:633/10 to All on Tue Jun 16 09:57:06 2026
    Am 16.06.2026 um 09:39 schrieb Lawrence D?Oliveiro:

    On Mon, 15 Jun 2026 11:10:46 +0200, Bonita Montero wrote:

    I've written a tool called "bg" to combine Windows' runas with
    start.

    Sort of a Windows answer to systemd? Somebody is already working on that <https://github.com/Jamesits/SvcGuest>.

    No, my tool has nothing to do with making arbitrary executables
    runnable as services. It's a combination of Windows' runas, start
    and Unix' time.

    Here are the commandline options of my tool:

    static const char usage[] =
    "--console create new console\n"
    "--detach start detached / hidden\n"
    "--idle idle priority\n"
    "--below below normal priority\n"
    "--above above normal priority\n"
    "--high high priority\n"
    "--realtime realtime priority\n"
    "--affintiy f CPU-affinity as hex (0x*), binary (b*) or list (e.g. 1,5-9)\n"
    "--group g primary processor group\n"
    "--wait wait for program to terminate\n"
    "--logon u d p create process under 1. username, 2. domain and 3. password\n"
    "--no-profile don't load registry for --logon, just network-credentials\n"
    "--dir d set startup directory\n"
    "--times show process times (includes --wait)";

    Here's the main source:

    #include <Windows.h>
    #include <iostream>
    #include <iomanip>
    #include <vector>
    #include <string>
    #include <cwchar>
    #include <cstddef>
    #include <system_error>
    #include <cassert>
    #include <sstream>
    #include <functional>
    #include <array>
    #include <type_traits>
    #include <cstdio>
    #include <bit>
    #include <span>
    #include <regex>
    #include <expected>
    #include "defer.hpp"

    #pragma warning(disable: 4554) // operator prececence
    #pragma warning(disable: 26812) // prefer scoped enums

    using namespace std;

    struct params
    {
    wstring userName, domain, password;
    bool noProfile = false;
    wstring startupDir;
    DWORD
    dwPrioFlag = NORMAL_PRIORITY_CLASS,
    dwConsoleFlag = 0;
    DWORD_PTR dwpAffinity = -1;
    WORD wGroup = -1;
    bool wait = false;
    wstring cmdLine;
    bool times = false;
    bool parse( int argc, const wchar_t *const *argv );
    private:
    struct state
    {
    using check_fn = bool (state::*)( params & );
    static const check_fn g_checkFns[9];
    span<const wchar_t *const> m_todo;
    vector<wstring> m_errs;
    bool checkNoProfile( params &pms );
    bool checkLogon( params &pms );
    bool checkDir( params &pms );
    bool checkConsole( params &pms );
    bool checkPriority( params &pms );
    bool checkAffinity( params &pms );
    bool checkGroup( params &pms );
    bool checkWait( params &pms );
    bool checkTimes( params &pms );
    bool parse( params &pms, int argc, const wchar_t *const *argv );
    };
    };

    static expected<wstring, DWORD> inputPassword();
    static string formatTime( uint64_t t );
    static string formatDotted( uintmax_t value );

    int wmain( int argc, const wchar_t *const *argv )
    {
    try
    {
    params pms;
    if( !pms.parse( argc, argv ) )
    return EXIT_FAILURE;
    const wchar_t *curDir = pms.startupDir.length() ? pms.startupDir.c_str() : nullptr;
    if( pms.userName.length() && !pms.password.length() )
    {
    cout << "password: ";
    expected<wstring, DWORD> xptPass = inputPassword();
    if( !xptPass )
    throw exception( "error while password input" );
    pms.password = *xptPass;
    cout << endl;
    }
    BOOL cpRet;
    bool suspended = pms.dwpAffinity != -1 || pms.wGroup != (WORD)-1;
    DWORD dwCreationFlags = pms.dwPrioFlag | pms.dwConsoleFlag | (!suspended ? 0 : CREATE_SUSPENDED);
    STARTUPINFOW si { sizeof si };
    PROCESS_INFORMATION pi;
    if( !pms.userName.length() )
    cpRet = CreateProcessW(
    nullptr, (LPWSTR)pms.cmdLine.c_str(), nullptr, nullptr,
    FALSE, dwCreationFlags, nullptr, curDir, &si, &pi );
    else
    cpRet = CreateProcessWithLogonW(
    pms.userName.c_str(),
    pms.domain.c_str(),
    pms.password.c_str(),
    pms.noProfile ? LOGON_NETCREDENTIALS_ONLY : LOGON_WITH_PROFILE,
    nullptr, (LPWSTR)pms.cmdLine.c_str(),
    dwCreationFlags, nullptr, curDir, &si, &pi );
    if( !cpRet )
    throw exception( "creating process failed" );
    defer closeHandles( [&] { CloseHandle( pi.hProcess ); CloseHandle(
    pi.hThread ); } );
    defer terminate( [&] { TerminateProcess( pi.hProcess, EXIT_FAILURE ); } );
    if( suspended )
    {
    bool threadAssigned = false;
    if( pms.wGroup != (WORD)-1 )
    {
    GROUP_AFFINITY aff {};
    DWORD dwNCPUs = GetActiveProcessorCount( pms.wGroup );
    if( !dwNCPUs )
    throw exception( "can't determine the number of processors in the
    destination group" );
    if( dwNCPUs < sizeof(KAFFINITY) * 8 )
    aff.Mask = ((KAFFINITY)1 << dwNCPUs) - 1;
    else
    aff.Mask = -1;
    aff.Group = pms.wGroup;
    if( !SetThreadGroupAffinity( GetCurrentThread(), &aff, nullptr ) )
    throw exception( "can't set primary thread's group affinity" );
    threadAssigned = true;
    }
    if( pms.dwpAffinity != -1 )
    if( !SetProcessAffinityMask( pi.hProcess, pms.dwpAffinity ) )
    throw exception( "can't set process affinity mask" );
    else if( !threadAssigned && !SetThreadAffinityMask( pi.hThread,
    pms.dwpAffinity ) )
    throw exception( "can't set primary thread's affinity mask" );
    if( ResumeThread( pi.hThread ) == -1 )
    throw exception( "can't resume main thread of process" );
    }
    terminate.disable();
    if( !pms.wait )
    return EXIT_SUCCESS;
    if( WaitForSingleObject( pi.hProcess, INFINITE ) != WAIT_OBJECT_0 )
    throw exception( "can't wait for end of program" );
    DWORD dwExitCode;
    if( !GetExitCodeProcess( pi.hProcess, &dwExitCode ) )
    throw exception( "can't get process exit code" );
    if( !pms.times )
    return dwExitCode;
    FILETIME creationTime, exitTime, userFTime, sysFTime;
    if( !GetProcessTimes( pi.hProcess, &creationTime, &exitTime, &sysFTime, &userFTime ) )
    throw exception( "can't get process times" );
    ULONG64 processCycles;
    if( !QueryProcessCycleTime( pi.hProcess, &processCycles ) )
    throw exception( "can't get process cycles" );
    auto ft2ts = []( const FILETIME &ft ) static -> uint64_t { return
    (uint64_t)ft.dwHighDateTime << 32 | ft.dwLowDateTime; };
    uint64_t
    realTime = ft2ts( exitTime ) - ft2ts( creationTime ),
    userTime = ft2ts( userFTime ),
    sysTime = ft2ts( sysFTime );
    cerr
    << "real " << formatTime( realTime ) << endl
    << "user " << formatTime( userTime ) << endl
    << "sys " << formatTime( sysTime ) << endl
    << "cylces " << formatDotted( processCycles ) << endl;
    return dwExitCode;
    }
    catch( const exception &exc )
    {
    cerr << exc.what() << endl;
    }
    }

    static const char usage[] =
    "--console create new console\n"
    "--detach start detached / hidden\n"
    "--idle idle priority\n"
    "--below below normal priority\n"
    "--above above normal priority\n"
    "--high high priority\n"
    "--realtime realtime priority\n"
    "--affintiy f CPU-affinity as hex (0x*), binary (b*) or list (e.g. 1,5-9)\n"
    "--group g primary processor group\n"
    "--wait wait for program to terminate\n"
    "--logon u d p create process under 1. username, 2. domain and 3. password\n"
    "--no-profile don't load registry for --logon, just network-credentials\n"
    "--dir d set startup directory\n"
    "--times show process times (includes --wait)";

    const params::state::check_fn params::state::g_checkFns[9] =
    {
    &checkNoProfile,
    &checkLogon,
    &checkDir,
    &checkConsole,
    &checkPriority,
    &checkAffinity,
    &checkGroup,
    &checkWait,
    &checkTimes
    };

    bool params::parse( int argc, const wchar_t *const *argv )
    {
    state st;
    return st.parse( *this, argc, argv );
    }

    bool params::state::checkNoProfile( params &pms )
    {
    if( _wcsicmp( m_todo.front(), L"--no-profile" ) != 0 )
    return false;
    pms.noProfile = true;
    m_todo = m_todo.subspan( 1 );
    return true;
    }

    bool params::state::checkLogon( params &pms )
    {
    if( _wcsicmp( m_todo.front(), L"--logon" ) != 0 )
    return false;
    if( m_todo.size() < 4 )
    {
    m_todo = {};
    m_errs.emplace_back( L"please specify username, domain and password" );
    return true;
    };
    pms.userName = m_todo[1];
    pms.domain = m_todo[2];
    pms.password = m_todo[3];
    m_todo = m_todo.subspan( 4 );
    return true;
    }

    bool params::state::checkDir( params &pms )
    {
    if( _wcsicmp( m_todo.front(), L"--dir" ) != 0 )
    return false;
    if( m_todo.size() < 2 )
    {
    m_todo = {};
    m_errs.emplace_back( L"please supply startup direcotry" );
    return true;
    };
    pms.startupDir = m_todo[1];
    m_todo = m_todo.subspan( 2 );
    return true;
    }

    bool params::state::checkConsole( params &pms )
    {
    if( _wcsicmp( m_todo.front(), L"--console" ) == 0 )
    pms.dwConsoleFlag = CREATE_NEW_CONSOLE;
    else if( _wcsicmp( m_todo.front(), L"--detach" ) == 0 )
    pms.dwConsoleFlag = DETACHED_PROCESS;
    else
    return false;
    m_todo = m_todo.subspan( 1 );
    return true;
    }

    bool params::state::checkPriority( params &pms )
    {
    using opt_t = pair<const wchar_t *, DWORD> ;
    static const opt_t opts[] =
    {
    { L"--idle", IDLE_PRIORITY_CLASS },
    { L"--below", BELOW_NORMAL_PRIORITY_CLASS },
    { L"--above", ABOVE_NORMAL_PRIORITY_CLASS },
    { L"--high", HIGH_PRIORITY_CLASS },
    { L"--realtime", REALTIME_PRIORITY_CLASS }
    };
    DWORD dwPrio = 0;
    for( const opt_t &opt : opts )
    if( _wcsicmp( m_todo.front(), opt.first ) == 0 )
    {
    dwPrio = opt.second;
    break;
    }
    if( !dwPrio )
    return false;
    m_todo = m_todo.subspan( 1 );
    pms.dwPrioFlag = dwPrio;
    return true;
    }

    bool params::state::checkAffinity( params &pms )
    {
    if( _wcsicmp( m_todo.front(), L"--affinity" ) != 0 )
    return false;
    if( m_todo.size() < 2 )
    {
    m_todo = {};
    m_errs.emplace_back( L"please specify affinity" );
    cerr << "" << endl;
    return false;
    };
    DWORD_PTR dwpAff = 0;
    match_results<wstring_view::iterator> match;
    defer chop2( [&] { m_todo = m_todo.subspan( 2 ); } );
    wstring_view svPattern( m_todo[1] );
    constexpr const wchar_t
    *RxHex = L"^0x([0-9a-f]+)$",
    *RxBin = sizeof( DWORD_PTR ) == 8 ? L"^b((?:[01]+){1,64})$" : L"^b((?:[01]+){1,32})$",
    *RxRange = L"^([0-9]+)(?:-([0-9]+))?(,)?";
    if( wregex rxHex( RxHex, regex_constants::icase ); regex_match( svPattern.begin(), svPattern.end(), match, rxHex ) )
    wistringstream( wstring( svPattern ) ) >> hex >> dwpAff;
    else if( wregex rxBinary( RxBin, regex_constants::icase ); regex_match(
    svPattern.begin(), svPattern.end(), match, rxBinary ) )
    for( wchar_t c : wstring_view( match[1].first, match[1].second ) )
    dwpAff = dwpAff << 1 | (c - '0');
    else
    {
    defer affZero( [&] { dwpAff = 0; } );
    auto it = svPattern.begin();
    wregex rx( RxRange );
    match_results<wstring_view::iterator> match;
    for( ; regex_search( it, svPattern.end(), match, rx ); it = match[0].second )
    {
    if( match[3].matched && match[3].second == svPattern.end() )
    break;
    unsigned from, to;
    wistringstream( wstring( match[1].first, match[1].second ) ) >> from;
    if( match[2].matched )
    {
    wistringstream( wstring( match[2].first, match[2].second ) ) >> to;
    ++to;
    }
    else
    to = from + 1;
    constexpr unsigned BITS = sizeof(ULONG_PTR) * 8;
    if( from >= to || from >= BITS || to > BITS )
    break;
    constexpr DWORD_PTR ALL = -1;
    DWORD_PTR dwpBits = ALL << from;
    if( to < BITS )
    dwpBits &= ~(ALL << to);
    dwpAff |= dwpBits;
    }
    if( it == svPattern.end() )
    affZero.disable();
    }
    if( !dwpAff )
    {
    wostringstream woss;
    woss << "invalid affinity pattern: " << svPattern;
    m_errs.emplace_back( woss.str() );
    return true;
    }
    DWORD_PTR dwpProc, dwpSys;
    GetProcessAffinityMask( GetCurrentProcess(), &dwpProc, &dwpSys );
    if( (dwpAff &= dwpProc) )
    pms.dwpAffinity = dwpAff;
    return true;
    }

    bool params::state::checkGroup( params &pms )
    {
    if( _wcsicmp( m_todo.front(), L"--group" ) != 0 )
    return false;
    if( m_todo.size() < 2 )
    {
    m_todo = {};
    m_errs.emplace_back( L"please specify processor group" );
    return false;
    };
    defer finish( [&] { m_todo = m_todo.subspan( 2 ); } );
    WORD wGroups = GetActiveProcessorGroupCount();
    if( !wGroups )
    {
    m_errs.emplace_back( L"unable to determine the active processor groups" );
    return true;
    }
    defer invalid( [&] { m_errs.emplace_back( L"please specify valid processor group" ); } );
    wstring_view svGroup( m_todo[1] );
    match_results<wstring_view::iterator> match;
    wregex rxGroup( L"^[0-9]+$" );
    if( !regex_match( svGroup.begin(), svGroup.end(), match, rxGroup ) )
    return false;
    WORD group;
    wistringstream( wstring( m_todo[1] ) ) >> group;
    if( group >= wGroups )
    return false;
    pms.wGroup = group;
    invalid.disable();
    return true;
    }

    bool params::state::checkWait( params &pms )
    {
    if( _wcsicmp( m_todo.front(), L"--wait" ) != 0 )
    return false;
    pms.wait = true;
    m_todo = m_todo.subspan( 1 );
    return true;
    }

    bool params::state::checkTimes( params &pms )
    {
    if( _wcsicmp( m_todo.front(), L"--times" ) != 0 )
    return false;
    pms.wait = true;
    pms.times = true;
    m_todo = m_todo.subspan( 1 );
    return true;
    }

    // might throw bad_alloc

    static optional<WORD> setConsoleCols( WORD wAttrs );
    static wstring quot( wstring_view str );

    bool params::state::parse( params &pms, int argc, const wchar_t *const
    *argv )
    {
    m_todo = span<const wchar_t *const>( argv + 1, argc - 1 );
    while( m_todo.size() )
    {
    bool checked = false;
    for( check_fn check : g_checkFns )
    if( !m_todo.size() || (checked = (this->*check)( pms )) )
    break;
    if( !checked )
    break;
    }
    if( m_errs.size() )
    {
    optional<WORD> wOldAtrs( setConsoleCols( FOREGROUND_RED ) );
    defer oldCols( [&] { if( wOldAtrs ) setConsoleCols( *wOldAtrs ); } );
    for( const wstring &err : m_errs )
    wcout << err << endl;
    return false;
    }
    if( !m_todo.size() )
    {
    cerr << endl << ::usage << endl;
    return false;
    }
    for( const wchar_t *const &part : m_todo )
    {
    if( &part != &m_todo.front() )
    pms.cmdLine += L" ";
    pms.cmdLine += L"\"";
    pms.cmdLine += part;
    pms.cmdLine += L"\"";
    }
    return true;
    }

    static optional<WORD> setConsoleCols( WORD wAttrs )
    {
    constexpr WORD FG_CLR_MASK = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY;
    HANDLE hStdErr = GetStdHandle( STD_ERROR_HANDLE );
    if( !hStdErr || hStdErr == INVALID_HANDLE_VALUE )
    return nullopt;
    if( GetFileType( hStdErr ) != FILE_TYPE_CHAR )
    return nullopt;
    CONSOLE_SCREEN_BUFFER_INFO csbi {};
    if( !GetConsoleScreenBufferInfo( hStdErr, &csbi ) )
    return nullopt;
    if( wAttrs == (WORD)-1 )
    return csbi.wAttributes;
    wAttrs = (csbi.wAttributes & ~FG_CLR_MASK) | (wAttrs & FG_CLR_MASK);
    if( !SetConsoleTextAttribute( hStdErr, wAttrs ) )
    return nullopt;
    return csbi.wAttributes;

    }

    static expected<wstring, DWORD> inputPassword()
    {
    cout.flush(), wcout.flush();
    constexpr auto *err = +[] static { return unexpected( GetLastError() ); };
    HANDLE hStdIn, hStdOut;
    if( !(hStdIn = GetStdHandle( STD_INPUT_HANDLE )) )
    return err();
    if( !(hStdOut = GetStdHandle( STD_OUTPUT_HANDLE )) )
    return err();
    DWORD dwOldConsoleMode;
    if( !GetConsoleMode( hStdIn, &dwOldConsoleMode ) )
    return err();
    if( !SetConsoleMode( hStdIn, dwOldConsoleMode & ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT) ) )
    return err();
    defer revert( [&] { SetConsoleMode( hStdIn, dwOldConsoleMode ); } );
    vector<COORD> coords;
    wstring pass;
    for( ; ; )
    {
    wchar_t ch;
    if( DWORD dwRead; !ReadConsoleW( hStdIn, &ch, 1, &dwRead, nullptr ) ||
    !dwRead )
    return err();
    if( ch == L'\r' )
    return pass;
    auto writeConsole = [&]( wchar_t ch )
    {
    CONSOLE_SCREEN_BUFFER_INFO csbi;
    if( !GetConsoleScreenBufferInfo( hStdOut, &csbi ) )
    return false;
    coords.emplace_back( csbi.dwCursorPosition );
    DWORD dwWritten;
    return WriteConsoleW( hStdOut, &ch, 1, &dwWritten, nullptr ) &&
    dwWritten == 1;
    };
    if( ch != L'\b' )
    {
    pass += ch;
    if( !writeConsole( L'*' ) )
    return err();
    continue;
    }
    if( !pass.length() )
    continue;
    pass.pop_back();
    auto back = [&]()
    {
    if( !SetConsoleCursorPosition( hStdOut, coords.back() ) )
    return false;
    coords.pop_back();
    return true;
    };
    if( !back() || !writeConsole( L' ' ) || !back() )
    return err();
    }
    return pass;
    }

    static string formatTime( uint64_t t )
    {
    constexpr auto MS = 10'000u;
    t = (t + MS / 2) / MS;
    uint16_t ms = t % 1'000; t /= 1'000;
    uint8_t secs = t % 60; t /= 60;
    uint8_t mins = t % 60; t /= 60;
    uint32_t hours = (uint32_t)t;
    ostringstream oss;
    if( hours )
    oss << hours << "h" << ":";
    if( hours || mins )
    oss << mins << "m" << ":";
    oss << (int)secs;
    if( ms )
    oss << "." << setw( 3 ) << setfill( '0' ) << ms;
    oss << "s";
    return oss.str();
    }

    static string formatDotted( uintmax_t value )
    {
    ostringstream oss;
    oss << value;
    string_view svDigits( oss.view() );
    auto pDigits = svDigits.begin();
    size_t
    digLen = svDigits.length(),
    dots = (digLen - 1) / 3,
    head = digLen - 3 * dots;
    string dotted( digLen + dots, 0 );
    auto pDotted = dotted.begin();
    pDotted = copy( pDigits, pDigits + head, pDotted );
    for( ; pDotted != dotted.end(); pDigits += 3 )
    {
    *pDotted++ = L'.';
    pDotted = copy( pDigits, pDigits + 3, pDotted );
    }
    return dotted;
    }

    --- PyGate Linux v1.5.17
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)