/* chmodowncore.cc
 * This file belongs to Worker, a file manager for UN*X/X11.
 * Copyright (C) 2013-2023 Ralf Hoffmann.
 * You can contact me at: ralf@boomerangsworld.de
 *   or http://www.boomerangsworld.de/worker
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "chmodowncore.hh"
#include "worker_locale.h"
#include "nmcopyopdir.hh"
#include "chmodorder.hh"
#include "chownorder.hh"
#include "verzeichnis.hh"
#include "aguix/lowlevelfunc.h"
#include "aguix/util.h"
#include "worker.h"
#include "aguix/request.h"
#include "aguix/choosebutton.h"
#include "aguix/button.h"
#include "aguix/cyclebutton.h"
#include "aguix/awindow.h"
#include "aguix/acontainer.h"
#include "aguix/fieldlistview.h"
#include "aguix/stringgadget.h"
#include "simplelist.hh"

ChModOwnCore::ChModOwnCore( struct NM_chmodorder *chmodorder,
                            struct NM_chownorder *chownorder,
                            AGUIX *aguix ) :
    m_chmodorder( chmodorder ),
    m_chownorder( chownorder ),
    m_cancel( false ),
    m_aguix( aguix ),
    m_chown_user_list( NULL ),
    m_chown_group_list( NULL )
{
}

ChModOwnCore::~ChModOwnCore()
{
}

bool ChModOwnCore::getCancel() const
{
    return m_cancel;
}

void ChModOwnCore::setCancel( bool nv )
{
    // disallow clearing the flag
    if ( nv == true ) {
        m_cancel = nv;
    }
}

void ChModOwnCore::registerFEToChange( const FileEntry &fe,
                                       int row )
{
    m_change_list.push_back( change_base_entry( fe, row ) );
}

ChModOwnCore::change_base_entry::change_base_entry( const FileEntry &fe,
                                                    int row ) :
    m_row( row ),
    m_cod( NULL ),
    m_fe( fe )
{
}

ChModOwnCore::change_base_entry::~change_base_entry()
{
    delete m_cod;
}

int ChModOwnCore::executeChMod()
{
    changemod_info_t cminfo;

    if ( m_chmodorder ) {
        switch ( m_chmodorder->apply_mode ) {
            case CHMOD_SET_PERMISSIONS:
                cminfo.op = changemod_info_t::CHMOD_SET;
                cminfo.forAll = true;
                cminfo.mode = m_chmodorder->permissions;
                break;
            case CHMOD_ADD_PERMISSIONS:
                cminfo.op = changemod_info_t::CHMOD_ADD;
                cminfo.forAll = true;
                cminfo.mode = m_chmodorder->permissions;
                break;
            case CHMOD_REMOVE_PERMISSIONS:
                cminfo.op = changemod_info_t::CHMOD_REM;
                cminfo.forAll = true;
                cminfo.mode = m_chmodorder->permissions;
                break;
            default:
                break;
        }
    }

    for ( auto &cbe : m_change_list ) {
        if ( m_cancel ) break;

        if ( worker_changemod( cbe, &cminfo ) != 0 ) {
            break;
        }
        Worker::pushCommandLog( catalog.getLocale( 1417 ),
                                cbe.entry().fullname,
                                "",
                                cbe.entry().fullname );
    }

    return 0;
}

int ChModOwnCore::executeChOwn()
{
    changeown_info_t coinfo;

    buildOwnerRequestInfos();

    for ( auto &cbe : m_change_list ) {
        if ( m_cancel ) break;

        if ( worker_changeown( cbe, &coinfo ) != 0 ) {
            break;
        }
        Worker::pushCommandLog( "change file owner",
                                cbe.entry().fullname,
                                "",
                                cbe.entry().fullname );
    }

    freeOwnerRequestInfos();

    return 0;
}

void ChModOwnCore::setPostChangeCallback( std::function< void( const FileEntry &fe, int row,
                                                               nm_change_t res ) > cb )
{
    m_post_cb = cb;
}

int ChModOwnCore::worker_changemod( const change_base_entry &ss1,
                                    changemod_info_t *cminfo )
{
    bool enter, dontChange, skip, changemodFirst;
    int erg;
    changemod_info_t newmode;
    FileEntry *subfe;
    //NM_specialsourceInt *ss2;
    NM_CopyOp_Dir *cod;
  
    if ( ( cminfo == NULL ) || ( m_chmodorder == NULL ) ) return -1;

    newmode.mode = 0700; //default
    newmode.op = changemod_info::CHMOD_SET;

    // do we have to enter this entry?
    enter = false;
    if ( ( ss1.entry().isDir() == true ) && ( m_chmodorder->recursive == true ) ) {
        if ( ss1.entry().isLink == false ) enter = true;
    }
  
    // check operation applies to this entry
    dontChange = false;
    if ( ( ss1.entry().isDir() == true ) && ( m_chmodorder->ondirs == false ) ) dontChange = true;
    else if ( ( ss1.entry().isDir() == false ) && ( m_chmodorder->onfiles == false ) ) dontChange = true;

    /* skip means skip entry AND all sub entries
     * cancel is clear
     * dontchange means to normally handle this entry
     *   but actually don't change mod
     */
    skip = false;

    if ( dontChange == false ) {
        // ask for new mode
        if ( cminfo->forAll == true ) {
            newmode = *cminfo;
        } else {
            erg = requestNewMode( ss1.entry(), &newmode );
            if ( erg == 1 ) {
                *cminfo = newmode;
                cminfo->forAll = true;
            } else if ( erg == 2 ) {
                skip = true;
            } else if ( erg == 3 ) {
                setCancel( true );
            }
        }
    }
    if ( skip == true ) return 0;
    if ( getCancel() == true ) return 1;

    if ( ss1.entry().checkAccess( R_OK|W_OK|X_OK ) != true ) {
        // cannot access directory to change mod so apply
        // newmode first
        changemodFirst = true;
    } else {
        changemodFirst = false;
    }

    if ( ( dontChange == false ) && ( changemodFirst == true ) ) {
        if ( applyNewMode( ss1, newmode ) != 0 ) setCancel( true );
    }
    if ( ( enter == true ) && ( getCancel() == false ) ) {
        cod = new NM_CopyOp_Dir( &ss1.entry() );
        if ( cod->user_abort == true ) {
            setCancel( true );
        } else if ( cod->ok == true ) {
            for ( Verzeichnis::verz_it subfe_it1 = cod->verz->begin();
                  subfe_it1 != cod->verz->end() && getCancel() == false;
                  subfe_it1++ ) {
                subfe = *subfe_it1;
                if ( strcmp( subfe->name, ".." ) != 0 ) {
                    change_base_entry ss2( *subfe, -1 );
                    if ( worker_changemod( ss2, cminfo ) != 0 ) {
                        setCancel( true );
                    }
                }
            }
        }
        delete cod;
    }
    if ( ( dontChange == false ) && ( changemodFirst == false ) ) {
        if ( applyNewMode( ss1, newmode ) != 0 ) setCancel( true );
    }
    return ( getCancel() == true ) ? 1 : 0;
}

int ChModOwnCore::worker_changeown( const change_base_entry &ss1,
                                    changeown_info_t *coinfo )
{
    bool enter, dontChange, skip, cancel;
    int erg;
    uid_t newuid = (uid_t)-1;
    gid_t newgid = (gid_t)-1;
    FileEntry *subfe;
    NM_CopyOp_Dir *cod;
  
    if ( coinfo == NULL || m_chownorder == NULL ) return -1;

    // do we have to enter this entry?
    enter = false;
    if ( ( ss1.entry().isDir() == true ) && ( m_chownorder->recursive == true ) ) {
        if ( ss1.entry().isLink == false ) enter = true;
    }
  
    // check operation applies to this entry
    dontChange = false;
    if ( ( ss1.entry().isDir() == true ) && ( m_chownorder->ondirs == false ) ) dontChange = true;
    else if ( ( ss1.entry().isDir() == false ) && ( m_chownorder->onfiles == false ) ) dontChange = true;

    /* skip means skip entry AND all sub entries
     * cancel is clear
     * dontchange means to normally handle this entry
     *   but actually don't change mod
     */
    skip = cancel = false;
    if ( dontChange == false ) {
        // ask for new owner
        if ( coinfo->forAll == true ) {
            newuid = coinfo->newuid;
            newgid = coinfo->newgid;
        } else {
            erg = requestNewOwner( ss1.entry(), &newuid, &newgid );
            if ( erg == 1 ) {
                coinfo->forAll = true;
                coinfo->newuid = newuid;
                coinfo->newgid = newgid;
            } else if ( erg == 2 ) {
                skip = true;
            } else if ( erg == 3 ) {
                setCancel( true );
            }
        }
    }
    if ( skip == true ) return 0;
    if ( getCancel() == true ) return 1;

    if ( dontChange == false ) {
        if ( applyNewOwner( ss1, newuid, newgid ) != 0 ) setCancel( true );
    }
    if ( ( enter == true ) && ( getCancel() == false ) ) {
        cod = new NM_CopyOp_Dir( &ss1.entry() );
        if ( cod->user_abort == true ) {
            setCancel( true );
        } else if ( cod->ok == true ) {
            for ( Verzeichnis::verz_it subfe_it1 = cod->verz->begin();
                  subfe_it1 != cod->verz->end() && getCancel() == false;
                  subfe_it1++ ) {
                subfe = *subfe_it1;
                if ( strcmp( subfe->name, ".." ) != 0 ) {
                    change_base_entry ss2( *subfe, -1 );
                    if ( worker_changeown( ss2, coinfo ) != 0 ) {
                        setCancel( true );
                    }
                }
            }
        }
        delete cod;
    }
    return ( getCancel() == true ) ? 1 : 0;
}

ChModOwnCore::changemod_info::changemod_info()
{
    forAll = false;
    mode = 0;
    op = CHMOD_SET;
}

int ChModOwnCore::applyNewMode( const change_base_entry &ss1, changemod_info_t newmode )
{
    char *textstr, *buttonstr;
    int erg;
  
    mode_t apply_mode = ( ss1.entry().isLink == true &&
                          ss1.entry().isCorrupt == false ) ? ss1.entry().dmode() : ss1.entry().mode();
    if ( newmode.op == changemod_info::CHMOD_SET ) {
        apply_mode = newmode.mode;
    } else if ( newmode.op == changemod_info::CHMOD_ADD ) {
        apply_mode |= newmode.mode;
    } else if ( newmode.op == changemod_info::CHMOD_REM ) {
        apply_mode &= ~newmode.mode;
    } else {
        return -1;
    }

    if ( worker_chmod( ss1.entry().fullname, apply_mode ) != 0 ) {
        // error
        buttonstr = (char*)_allocsafe( strlen( catalog.getLocale( 11 ) ) + 1 +
                                       strlen( catalog.getLocale( 8 ) ) + 1 );
        sprintf( buttonstr, "%s|%s", catalog.getLocale( 11 ),
                 catalog.getLocale( 8 ) );
        textstr = (char*)_allocsafe( strlen( catalog.getLocale( 226 ) ) + strlen( ss1.entry().fullname ) + 1 );
        sprintf( textstr, catalog.getLocale( 226 ), ss1.entry().fullname );
        erg = Worker::getRequester()->request( catalog.getLocale( 347 ), textstr, buttonstr );
        _freesafe( buttonstr );
        _freesafe( textstr );
        if ( erg == 1 ) setCancel( true );
    } else {
        if ( ss1.m_row >= 0 && m_post_cb ) {
            m_post_cb( ss1.entry(), ss1.m_row,
                       NM_CHANGE_OK );
        }
    }
    return ( getCancel() == true ) ? 1 : 0;
}

int ChModOwnCore::applyNewOwner( const change_base_entry &ss1, uid_t newuid, gid_t newgid )
{
    char *textstr, *buttonstr;
    int erg;

    if ( worker_chown( ss1.entry().fullname, newuid, newgid ) != 0 ) {
        if ( errno == EPERM ) {
            buttonstr = (char*)_allocsafe( strlen( catalog.getLocale( 11 ) ) + 1 +
                                           strlen( catalog.getLocale( 8 ) ) + 1 );
            sprintf( buttonstr, "%s|%s", catalog.getLocale( 11 ),
                     catalog.getLocale( 8 ) );
            textstr = (char*)_allocsafe( strlen( catalog.getLocale( 446 ) ) + strlen( ss1.entry().fullname ) + 1 );
            sprintf( textstr, catalog.getLocale( 446 ), ss1.entry().fullname );
            erg = Worker::getRequester()->request( catalog.getLocale( 347 ), textstr, buttonstr );
            _freesafe( buttonstr );
            _freesafe( textstr );
            if ( erg == 1 ) setCancel( true );
        } else {
            // error
            buttonstr = (char*)_allocsafe( strlen( catalog.getLocale( 11 ) ) + 1 +
                                           strlen( catalog.getLocale( 8 ) ) + 1 );
            sprintf( buttonstr, "%s|%s", catalog.getLocale( 11 ),
                     catalog.getLocale( 8 ) );
            textstr = (char*)_allocsafe( strlen( catalog.getLocale( 447 ) ) + strlen( ss1.entry().fullname ) + 1 );
            sprintf( textstr, catalog.getLocale( 447 ), ss1.entry().fullname );
            erg = Worker::getRequester()->request( catalog.getLocale( 347 ), textstr, buttonstr );
            _freesafe( buttonstr );
            _freesafe( textstr );
            if ( erg == 1 ) setCancel( true );
        }
    } else {
        if ( ss1.m_row >= 0 && m_post_cb ) {
            m_post_cb( ss1.entry(), ss1.m_row,
                       NM_CHANGE_OK );
        }
    }
    return ( getCancel() == true ) ? 1 : 0;
}

static void setChooseButtonMatrixForMode( ChooseButton **mcb, mode_t mode )
{
    mcb[0]->setState( ( ( mode & S_IRUSR ) == 0 ) ? false : true );
    mcb[1]->setState( ( ( mode & S_IWUSR ) == 0 ) ? false : true );
    mcb[2]->setState( ( ( mode & S_IXUSR ) == 0 ) ? false : true );
    mcb[3]->setState( ( ( mode & S_IRGRP ) == 0 ) ? false : true );
    mcb[4]->setState( ( ( mode & S_IWGRP ) == 0 ) ? false : true );
    mcb[5]->setState( ( ( mode & S_IXGRP ) == 0 ) ? false : true );
    mcb[6]->setState( ( ( mode & S_IROTH ) == 0 ) ? false : true );
    mcb[7]->setState( ( ( mode & S_IWOTH ) == 0 ) ? false : true );
    mcb[8]->setState( ( ( mode & S_IXOTH ) == 0 ) ? false : true );
    mcb[9]->setState( ( ( mode & S_ISUID ) == 0 ) ? false : true );
    mcb[10]->setState( ( ( mode & S_ISGID ) == 0 ) ? false : true );
    mcb[11]->setState( ( ( mode & S_ISVTX ) == 0 ) ? false : true );
}

static mode_t getChooseButtonMatrixMode( ChooseButton **mcb )
{
    mode_t newmode = 0;
    newmode |= ( mcb[0]->getState() == true ) ? S_IRUSR : 0;
    newmode |= ( mcb[1]->getState() == true ) ? S_IWUSR : 0;
    newmode |= ( mcb[2]->getState() == true ) ? S_IXUSR : 0;
    newmode |= ( mcb[3]->getState() == true ) ? S_IRGRP : 0;
    newmode |= ( mcb[4]->getState() == true ) ? S_IWGRP : 0;
    newmode |= ( mcb[5]->getState() == true ) ? S_IXGRP : 0;
    newmode |= ( mcb[6]->getState() == true ) ? S_IROTH : 0;
    newmode |= ( mcb[7]->getState() == true ) ? S_IWOTH : 0;
    newmode |= ( mcb[8]->getState() == true ) ? S_IXOTH : 0;
    newmode |= ( mcb[9]->getState() == true ) ? S_ISUID : 0;
    newmode |= ( mcb[10]->getState() == true ) ? S_ISGID : 0;
    newmode |= ( mcb[11]->getState() == true ) ? S_ISVTX : 0;

    return newmode;
}

static void setOctalSGForMode( StringGadget *octal_sg, mode_t permissions )
{
    std::string s = AGUIXUtils::formatStringToString( "%o", permissions & 07777 );

    octal_sg->setText( s.c_str() );
}

static mode_t getModeFromOctalSG( StringGadget *octal_sg )
{
    unsigned int mode;

    if ( sscanf( octal_sg->getText(), "%o", &mode ) == 1 ) {
        return (mode_t)mode;
    }
    return 0;
}

int ChModOwnCore::requestNewMode( const FileEntry &fe, changemod_info_t *return_mode )
{
    mode_t tmode, newmode;
    AWindow *win;
    ChooseButton *mcb[12];
    AGMessage *msg;
    int endmode = -1;
    char *tstr;
    
    tmode = ( ( fe.isLink == true ) && ( fe.isCorrupt == false ) ) ? fe.dmode() : fe.mode();
    win = new AWindow( m_aguix, 10, 10, 10, 10, catalog.getLocale( 1279 ), AWindow::AWINDOW_DIALOG );
    win->create();
    
    AContainer *ac1 = win->setContainer( new AContainer( win, 1, 5 ), true );
    ac1->setMinSpace( 5 );
    ac1->setMaxSpace( 5 );
    
    tstr=(char*)_allocsafe(strlen(catalog.getLocale(213))+strlen(fe.fullname)+1);
    sprintf(tstr,catalog.getLocale(213),fe.fullname);
    
    ac1->add( new Text( m_aguix, 0, 0, tstr ), 0, 0, AContainer::CO_INCWNR );
    _freesafe(tstr);
    
    AContainer *ac1_1 = ac1->add( new AContainer( win, 2, 1 ), 0, 1 );
    ac1_1->setMinSpace( 5 );
    ac1_1->setMaxSpace( 5 );

    ac1_1->add( new Text( m_aguix, 0, 0, catalog.getLocale( 780 ) ), 0, 0, AContainer::CO_FIX );
    CycleButton *apply_mode_cb = static_cast<CycleButton*>( ac1_1->add( new CycleButton( m_aguix,
                                                                                         0, 0,
                                                                                         10,
                                                                                         0 ),
                                                                        1, 0, AContainer::CO_FIX ) );
    apply_mode_cb->addOption( catalog.getLocale( 781 ) );
    apply_mode_cb->addOption( catalog.getLocale( 782 ) );
    apply_mode_cb->addOption( catalog.getLocale( 783 ) );
    apply_mode_cb->setOption( 0 );
    apply_mode_cb->resize( apply_mode_cb->getMaxSize(), apply_mode_cb->getHeight() );
    ac1_1->readLimits();
    
    AContainer *ac1_2 = ac1->add( new AContainer( win, 4, 6 ), 0, 2 );
    ac1_2->setMinSpace( 5 );
    ac1_2->setMaxSpace( 5 );
    
    ac1_2->add( new Text( m_aguix, 0, 0, catalog.getLocale( 218 ) ), 1, 0, AContainer::CO_FIXNR + AContainer::ACONT_CENTER );
    ac1_2->add( new Text( m_aguix, 0, 0, catalog.getLocale( 219 ) ), 2, 0, AContainer::CO_FIXNR + AContainer::ACONT_CENTER );
    ac1_2->add( new Text( m_aguix, 0, 0, catalog.getLocale( 220 ) ), 3, 0, AContainer::CO_FIXNR + AContainer::ACONT_CENTER );
    
    ac1_2->add( new Text( m_aguix, 0, 0, catalog.getLocale( 214 ) ), 0, 1, AContainer::CO_FIX );
    ac1_2->add( new Text( m_aguix, 0, 0, catalog.getLocale( 215 ) ), 0, 2, AContainer::CO_FIX );
    ac1_2->add( new Text( m_aguix, 0, 0, catalog.getLocale( 216 ) ), 0, 3, AContainer::CO_FIX );
    ac1_2->add( new Text( m_aguix, 0, 0, catalog.getLocale( 217 ) ), 0, 5, AContainer::CO_FIX );
    
    ac1_2->add( new Text( m_aguix, 0, 0, catalog.getLocale( 221 ) ), 1, 4, AContainer::CO_FIXNR + AContainer::ACONT_CENTER );
    ac1_2->add( new Text( m_aguix, 0, 0, catalog.getLocale( 222 ) ), 2, 4, AContainer::CO_FIXNR + AContainer::ACONT_CENTER );
    ac1_2->add( new Text( m_aguix, 0, 0, catalog.getLocale( 223 ) ), 3, 4, AContainer::CO_FIXNR + AContainer::ACONT_CENTER );
    
    mcb[0] = (ChooseButton*)ac1_2->add( new ChooseButton( m_aguix, 0, 0,
                                                          ( ( tmode & S_IRUSR ) == 0 ) ? 0 : 1,
                                                          "", LABEL_RIGHT, 0 ), 1, 1, AContainer::CO_FIXNR + AContainer::ACONT_CENTER );
    mcb[1] = (ChooseButton*)ac1_2->add( new ChooseButton( m_aguix, 0, 0,
                                                          ( ( tmode & S_IWUSR ) == 0 ) ? 0 : 1,
                                                          "", LABEL_RIGHT, 0 ), 2, 1, AContainer::CO_FIXNR + AContainer::ACONT_CENTER );
    mcb[2] = (ChooseButton*)ac1_2->add( new ChooseButton( m_aguix, 0, 0,
                                                          ( ( tmode & S_IXUSR ) == 0 ) ? 0 : 1,
                                                          "", LABEL_RIGHT, 0 ), 3, 1, AContainer::CO_FIXNR + AContainer::ACONT_CENTER );
    mcb[3] = (ChooseButton*)ac1_2->add( new ChooseButton( m_aguix, 0, 0,
                                                          ( ( tmode & S_IRGRP ) == 0 ) ? 0 : 1,
                                                          "", LABEL_RIGHT, 0 ), 1, 2, AContainer::CO_FIXNR + AContainer::ACONT_CENTER );
    mcb[4] = (ChooseButton*)ac1_2->add( new ChooseButton( m_aguix, 0, 0,
                                                          ( ( tmode & S_IWGRP ) == 0 ) ? 0 : 1,
                                                          "", LABEL_RIGHT, 0 ), 2, 2, AContainer::CO_FIXNR + AContainer::ACONT_CENTER );
    mcb[5] = (ChooseButton*)ac1_2->add( new ChooseButton( m_aguix, 0, 0,
                                                          ( ( tmode & S_IXGRP ) == 0 ) ? 0 : 1,
                                                          "", LABEL_RIGHT, 0 ), 3, 2, AContainer::CO_FIXNR + AContainer::ACONT_CENTER );
    mcb[6] = (ChooseButton*)ac1_2->add( new ChooseButton( m_aguix, 0, 0,
                                                          ( ( tmode & S_IROTH ) == 0 ) ? 0 : 1,
                                                          "", LABEL_RIGHT, 0 ), 1, 3, AContainer::CO_FIXNR + AContainer::ACONT_CENTER );
    mcb[7] = (ChooseButton*)ac1_2->add( new ChooseButton( m_aguix, 0, 0,
                                                          ( ( tmode & S_IWOTH ) == 0 ) ? 0 : 1,
                                                          "", LABEL_RIGHT, 0 ), 2, 3, AContainer::CO_FIXNR + AContainer::ACONT_CENTER );
    mcb[8] = (ChooseButton*)ac1_2->add( new ChooseButton( m_aguix, 0, 0,
                                                          ( ( tmode & S_IXOTH ) == 0 ) ? 0 : 1,
                                                          "", LABEL_RIGHT, 0 ), 3, 3, AContainer::CO_FIXNR + AContainer::ACONT_CENTER );
    mcb[9] = (ChooseButton*)ac1_2->add( new ChooseButton( m_aguix, 0, 0,
                                                          ( ( tmode & S_ISUID ) == 0 ) ? 0 : 1,
                                                          "", LABEL_RIGHT, 0 ), 1, 5, AContainer::CO_FIXNR + AContainer::ACONT_CENTER );
    mcb[10] = (ChooseButton*)ac1_2->add( new ChooseButton( m_aguix, 0, 0,
                                                           ( ( tmode & S_ISGID ) == 0 ) ? 0 : 1,
                                                           "", LABEL_RIGHT, 0 ), 2, 5, AContainer::CO_FIXNR + AContainer::ACONT_CENTER );
    mcb[11] = (ChooseButton*)ac1_2->add( new ChooseButton( m_aguix, 0, 0,
                                                           ( ( tmode & S_ISVTX ) == 0 ) ? 0 : 1,
                                                           "", LABEL_RIGHT, 0 ), 3, 5, AContainer::CO_FIXNR + AContainer::ACONT_CENTER );

    AContainer *ac1_4 = ac1->add( new AContainer( win, 2, 1 ), 0, 3 );
    ac1_4->setMinSpace( 5 );
    ac1_4->setMaxSpace( 5 );

    ac1_4->add( new Text( m_aguix, 0, 0, catalog.getLocale( 1405 ) ), 0, 0, AContainer::CO_FIX );
    auto *octal_sg = ac1_4->addWidget( new StringGadget( m_aguix, 0, 0, 50, "", 0 ),
                                       1, 0, AContainer::CO_INCW );

    setChooseButtonMatrixForMode( mcb, tmode );
    setOctalSGForMode( octal_sg, tmode );
    
    AContainer *ac1_3 = ac1->add( new AContainer( win, 4, 1 ), 0, 4 );
    ac1_3->setMinSpace( 5 );
    ac1_3->setMaxSpace( -1 );
    ac1_3->setBorderWidth( 0 );
    Button *okb =(Button*)ac1_3->add( new Button( m_aguix,
                                                  0,
                                                  0,
                                                  catalog.getLocale( 11 ),
                                                  0 ), 0, 0, AContainer::CO_FIX );
    Button *ok2allb = (Button*)ac1_3->add( new Button( m_aguix,
                                                       0,
                                                       0,
                                                       catalog.getLocale( 224 ),
                                                       0 ), 1, 0, AContainer::CO_FIX );
    Button *skipb = (Button*)ac1_3->add( new Button( m_aguix,
                                                     0,
                                                     0,
                                                     catalog.getLocale( 225 ),
                                                     0 ), 2, 0, AContainer::CO_FIX );
    Button *cb = (Button*)ac1_3->add( new Button( m_aguix,
                                                  0,
                                                  0,
                                                  catalog.getLocale( 8 ),
                                                  0 ), 3, 0, AContainer::CO_FIX );
    win->setDoTabCycling( true );
    win->contMaximize( true, true );
    win->show();
  
    for( ; endmode == -1; ) {
        msg = m_aguix->WaitMessage( win );
        if ( msg != NULL ) {
            switch ( msg->type ) {
                case AG_CLOSEWINDOW:
                    if ( msg->closewindow.window == win->getWindow() ) endmode = 3;
                    break;
                case AG_BUTTONCLICKED:
                    if ( msg->button.button == okb ) endmode = 0;
                    else if ( msg->button.button == ok2allb ) endmode = 1;
                    else if ( msg->button.button == skipb ) endmode = 2;
                    else if ( msg->button.button == cb ) endmode = 3;
                    break;
                case AG_KEYPRESSED:
                    if ( win->isParent( msg->key.window, false ) == true ) {
                        switch ( msg->key.key ) {
                            case XK_1:
                            case XK_KP_1:
                            case XK_KP_End:
                                mcb[6]->setState( ( mcb[6]->getState() == true ) ? false : true );
                                setOctalSGForMode( octal_sg, getChooseButtonMatrixMode( mcb ) );
                                break;
                            case XK_2:
                            case XK_KP_2:
                            case XK_KP_Down:
                                mcb[7]->setState( ( mcb[7]->getState() == true ) ? false : true );
                                setOctalSGForMode( octal_sg, getChooseButtonMatrixMode( mcb ) );
                                break;
                            case XK_3:
                            case XK_KP_3:
                            case XK_KP_Next:
                                mcb[8]->setState( ( mcb[8]->getState() == true ) ? false : true );
                                setOctalSGForMode( octal_sg, getChooseButtonMatrixMode( mcb ) );
                                break;
                            case XK_4:
                            case XK_KP_4:
                            case XK_KP_Left:
                                mcb[3]->setState( ( mcb[3]->getState() == true ) ? false : true );
                                setOctalSGForMode( octal_sg, getChooseButtonMatrixMode( mcb ) );
                                break;
                            case XK_5:
                            case XK_KP_5:
                            case XK_KP_Begin:
                                mcb[4]->setState( ( mcb[4]->getState() == true ) ? false : true );
                                setOctalSGForMode( octal_sg, getChooseButtonMatrixMode( mcb ) );
                                break;
                            case XK_6:
                            case XK_KP_6:
                            case XK_KP_Right:
                                mcb[5]->setState( ( mcb[5]->getState() == true ) ? false : true );
                                setOctalSGForMode( octal_sg, getChooseButtonMatrixMode( mcb ) );
                                break;
                            case XK_7:
                            case XK_KP_7:
                            case XK_KP_Home:
                                mcb[0]->setState( ( mcb[0]->getState() == true ) ? false : true );
                                setOctalSGForMode( octal_sg, getChooseButtonMatrixMode( mcb ) );
                                break;
                            case XK_8:
                            case XK_KP_8:
                            case XK_KP_Up:
                                mcb[1]->setState( ( mcb[1]->getState() == true ) ? false : true );
                                setOctalSGForMode( octal_sg, getChooseButtonMatrixMode( mcb ) );
                                break;
                            case XK_9:
                            case XK_KP_9:
                            case XK_KP_Prior:
                                mcb[2]->setState( ( mcb[2]->getState() == true ) ? false : true );
                                setOctalSGForMode( octal_sg, getChooseButtonMatrixMode( mcb ) );
                                break;
                            case XK_Return:
                            case XK_KP_Enter:
                                if ( ok2allb->getHasFocus() == false &&
                                     skipb->getHasFocus() == false &&
                                     cb->getHasFocus() == false &&
                                     octal_sg->isActive() == false ) {
                                    endmode = 0;
                                }
                                break;
                            case XK_F1:
                                endmode = 0;
                                break;
                            case XK_Escape:
                                if ( octal_sg->isActive() == false ) {
                                    endmode = 3;
                                }
                                break;
                            case XK_F4:
                                endmode = 3;
                                break;
                            case XK_F2:
                                endmode = 1;
                                break;
                            case XK_F3:
                                endmode = 2;
                                break;
                        }
                    }
                    break;
                case AG_CYCLEBUTTONCLICKED:
                    if ( msg->cyclebutton.cyclebutton == apply_mode_cb ) {
                        switch ( msg->cyclebutton.option ) {
                            case 2:
                            case 1:
                                setChooseButtonMatrixForMode( mcb, 0 );
                                break;
                            default:
                                setChooseButtonMatrixForMode( mcb, tmode );
                                break;
                        }
                    }
                    break;
                case AG_STRINGGADGET_OK:
                    if ( msg->stringgadget.sg == octal_sg ) {
                        mode_t m = getModeFromOctalSG( octal_sg );
                        setChooseButtonMatrixForMode( mcb, m );
                    }
                    break;
                case AG_STRINGGADGET_CANCEL:
                    if ( msg->stringgadget.sg == octal_sg ) {
                        mode_t m = getChooseButtonMatrixMode( mcb );
                        setOctalSGForMode( octal_sg, m );
                    }
                    break;
                case AG_CHOOSECLICKED:
                    setOctalSGForMode( octal_sg, getChooseButtonMatrixMode( mcb ) );
                    break;
            }
            m_aguix->ReplyMessage( msg );
        }
    }
    
    if ( endmode == 0 || endmode == 1 ) {
        // ok
        newmode = 0;
        newmode |= ( mcb[0]->getState() == true ) ? S_IRUSR : 0;
        newmode |= ( mcb[1]->getState() == true ) ? S_IWUSR : 0;
        newmode |= ( mcb[2]->getState() == true ) ? S_IXUSR : 0;
        newmode |= ( mcb[3]->getState() == true ) ? S_IRGRP : 0;
        newmode |= ( mcb[4]->getState() == true ) ? S_IWGRP : 0;
        newmode |= ( mcb[5]->getState() == true ) ? S_IXGRP : 0;
        newmode |= ( mcb[6]->getState() == true ) ? S_IROTH : 0;
        newmode |= ( mcb[7]->getState() == true ) ? S_IWOTH : 0;
        newmode |= ( mcb[8]->getState() == true ) ? S_IXOTH : 0;
        newmode |= ( mcb[9]->getState() == true ) ? S_ISUID : 0;
        newmode |= ( mcb[10]->getState() == true ) ? S_ISGID : 0;
        newmode |= ( mcb[11]->getState() == true ) ? S_ISVTX : 0;
        return_mode->mode = newmode;

        switch ( apply_mode_cb->getSelectedOption() ) {
            case 2:
                return_mode->op = changemod_info::CHMOD_REM;
                break;
            case 1:
                return_mode->op = changemod_info::CHMOD_ADD;
                break;
            default:
                return_mode->op = changemod_info::CHMOD_SET;
                break;
        }
    }
    
    delete win;
    
    return endmode;
}

int ChModOwnCore::requestNewOwner( const FileEntry &fe, uid_t *return_owner, gid_t *return_group )
{
    uid_t towner;
    gid_t tgroup;
    Button *okb, *cb, *ok2allb, *skipb;
    AWindow *win;
    Text *ttext, *utext, *gtext;
    int tw, ttw, tth, ttx, tty;
    AGMessage *msg;
    int endmode = -1;
    char *tstr;
    GUIElement *ba[4];
    FieldListView *lvu, *lvg;
    chownrequest_id_name_t *elem;
    int row, pos, i;
    int w;
  
    if ( ( m_chown_user_list == NULL ) || ( m_chown_group_list == NULL ) )
        buildOwnerRequestInfos();

    towner = ( ( fe.isLink == true ) && ( fe.isCorrupt == false ) ) ? fe.duserid() : fe.userid();
    tgroup = ( ( fe.isLink == true ) && ( fe.isCorrupt == false ) ) ? fe.dgroupid() : fe.groupid();

    ttw = tth = 10;
    ttx = tty = 5;
    win = new AWindow( m_aguix, 10, 10, ttw, tth, catalog.getLocale( 1284 ), AWindow::AWINDOW_DIALOG );
    win->create();
    tstr = (char*)_allocsafe( strlen( catalog.getLocale( 448 ) ) + strlen( fe.fullname ) + 1 );
    sprintf( tstr, catalog.getLocale( 448 ), fe.fullname );

    ttext = (Text*)win->add( new Text( m_aguix, ttx, tty, tstr ) );
    _freesafe( tstr );
  
    tty += ttext->getHeight() + 5;
  
    utext = (Text*)win->add( new Text( m_aguix, ttx, tty, catalog.getLocale( 214 ) ) );
    gtext = (Text*)win->add( new Text( m_aguix, ttx, tty, catalog.getLocale( 215 ) ) );
    tty += utext->getHeight() + 5;

    lvu = (FieldListView*)win->add( new FieldListView( m_aguix, ttx, tty, 50, 10 * m_aguix->getCharHeight(), 0 ) );
    lvg = (FieldListView*)win->add( new FieldListView( m_aguix, ttx, tty, 50, 10 * m_aguix->getCharHeight(), 0 ) );
  
    lvu->setNrOfFields( 1 );
    lvg->setNrOfFields( 1 );

    elem = (chownrequest_id_name_t*)m_chown_user_list->getFirstElement();
    pos = 0;
    while ( elem != NULL ) {
        row = lvu->addRow();
        lvu->setText( row, 0, elem->name );
        lvu->setData( row, pos );
        lvu->setPreColors( row, FieldListView::PRECOLOR_ONLYACTIVE );
        if ( towner == elem->id.uid ) {
            lvu->setActiveRow( row );
        }
        elem = (chownrequest_id_name_t*)m_chown_user_list->getNextElement();
        pos++;
    }
    elem = (chownrequest_id_name_t*)m_chown_group_list->getFirstElement();
    pos = 0;
    while ( elem != NULL ) {
        row = lvg->addRow();
        lvg->setText( row, 0, elem->name );
        lvg->setData( row, pos );
        lvg->setPreColors( row, FieldListView::PRECOLOR_ONLYACTIVE );
        if ( tgroup == elem->id.gid ) {
            lvg->setActiveRow( row );
        }
        elem = (chownrequest_id_name_t*)m_chown_group_list->getNextElement();
        pos++;
    }
  
    lvu->setVBarState( 2 );
    lvu->setHBarState( 0 );
    lvg->setVBarState( 2 );
    lvg->setHBarState( 0 );
    lvu->maximizeX();
    lvg->maximizeX();
    lvu->setDisplayFocus( true );
    lvg->setDisplayFocus( true );
    lvu->setAcceptFocus( true );
    lvg->setAcceptFocus( true );
  
    tw = a_max( utext->getWidth(), lvu->getWidth() );
    lvu->resize( a_max( tw, 20 * m_aguix->getTextWidth( "x" ) ), lvu->getHeight() );
    tw = a_max( gtext->getWidth(), lvg->getWidth() );
    lvg->resize( a_max( tw, 20 * m_aguix->getTextWidth( "x" ) ), lvg->getHeight() );
    lvg->move( lvu->getX() + lvu->getWidth() + 10, lvg->getY() );
    lvu->showActive();
    lvg->showActive();
  
    gtext->move( lvg->getX(), gtext->getY() );
  
    lvu->takeFocus();

    tty += lvu->getHeight() +5;
  
    win->maximizeX();
    w = win->getWidth();

    okb = (Button*)win->add( new Button( m_aguix,
                                         5,
                                         tty,
                                         catalog.getLocale( 11 ),
                                         0 ) );
    ok2allb = (Button*)win->add( new Button( m_aguix,
                                             0,
                                             tty,
                                             catalog.getLocale( 224 ),
                                             0 ) );
    skipb = (Button*)win->add( new Button( m_aguix,
                                           0,
                                           tty,
                                           catalog.getLocale( 225 ),
                                           0 ) );
    cb = (Button*)win->add( new Button( m_aguix,
                                        0,
                                        tty,
                                        catalog.getLocale( 8 ),
                                        0 ) );

    ba[0] = okb;
    ba[1] = ok2allb;
    ba[2] = skipb;
    ba[3] = cb;
    tw = AGUIX::scaleElementsW( w, 5, 5, -1, false, false, ba, NULL, 4 );
    if ( tw > w ) {
        w = tw;
        win->resize( w, win->getHeight() );
    }
  
    tty += okb->getHeight() + 5;
  
    for ( i = 0; i < 4; i++ ) {
        ba[i]->setAcceptFocus( true );
    }
  
    tth = tty;
    okb->takeFocus();
    win->setDoTabCycling( true );
    win->resize( w, tth );
    win->setMaxSize( w, tth );
    win->setMinSize( w, tth );
    win->show();
    for( ; endmode == -1; ) {
        msg = m_aguix->WaitMessage( win );
        if ( msg != NULL ) {
            switch ( msg->type ) {
                case AG_CLOSEWINDOW:
                    if ( msg->closewindow.window == win->getWindow() ) endmode = 3;
                    break;
                case AG_BUTTONCLICKED:
                    if ( msg->button.button == okb ) endmode = 0;
                    else if ( msg->button.button == ok2allb ) endmode = 1;
                    else if ( msg->button.button == skipb ) endmode = 2;
                    else if ( msg->button.button == cb ) endmode = 3;
                    break;
                case AG_KEYPRESSED:
                    if ( win->isParent( msg->key.window, false ) == true ) {
                        switch ( msg->key.key ) {
                            case XK_Return:
                            case XK_KP_Enter:
                                if ( ( ok2allb->getHasFocus() == false ) &&
                                     ( skipb->getHasFocus() == false ) &&
                                     ( cb->getHasFocus() == false ) ) {
                                    endmode = 0;
                                }
                                break;
                            case XK_F1:
                                endmode = 0;
                                break;
                            case XK_Escape:
                            case XK_F4:
                                endmode = 3;
                                break;
                            case XK_F2:
                                endmode = 1;
                                break;
                            case XK_F3:
                                endmode = 2;
                                break;
                        }
                    }
                    break;
                case AG_FIELDLV_PRESSED:
                    if ( msg->fieldlv.lv == lvu ) {
                        lvu->takeFocus();
                    } else if ( msg->fieldlv.lv == lvg ) {
                        lvg->takeFocus();
                    }
            }
            m_aguix->ReplyMessage( msg );
        }
    }
  
    if ( ( endmode == 0 ) || ( endmode == 1 ) ) {
        // ok
        if ( return_owner != NULL )
            *return_owner = (uid_t)-1;  // -1 means no change for chown
        if ( return_group != NULL )
            *return_group = (gid_t)-1;

        row = lvu->getActiveRow();
        if ( lvu->isValidRow( row ) == true ) {
            elem = (chownrequest_id_name_t*)m_chown_user_list->getElementAt( lvu->getData( row, 0 ) );
            if ( elem != NULL ) {
                if ( return_owner != NULL )
                    *return_owner = elem->id.uid;
            }
        }

        row = lvg->getActiveRow();
        if ( lvg->isValidRow( row ) == true ) {
            elem = (chownrequest_id_name_t*)m_chown_group_list->getElementAt( lvg->getData( row, 0 ) );
            if ( elem != NULL ) {
                if ( return_group != NULL )
                    *return_group = elem->id.gid;
            }
        }
    }
  
    delete win;

    return endmode;
}

void ChModOwnCore::freeOwnerRequestInfos()
{
    chownrequest_id_name_t *elem;

    if ( m_chown_user_list != NULL ) {
        elem = (chownrequest_id_name_t*)m_chown_user_list->getFirstElement();
        while ( elem != NULL ) {
            _freesafe( elem->name );
            _freesafe( elem );
            m_chown_user_list->removeFirstElement();
            elem = (chownrequest_id_name_t*)m_chown_user_list->getFirstElement();
        }
        delete m_chown_user_list;
        m_chown_user_list = NULL;
    }
  
    if ( m_chown_group_list != NULL ) {
        elem = (chownrequest_id_name_t*)m_chown_group_list->getFirstElement();
        while ( elem != NULL ) {
            _freesafe( elem->name );
            _freesafe( elem );
            m_chown_group_list->removeFirstElement();
            elem = (chownrequest_id_name_t*)m_chown_group_list->getFirstElement();
        }
        delete m_chown_group_list;
        m_chown_group_list = NULL;
    }
}

void ChModOwnCore::buildOwnerRequestInfos()
{
    struct passwd *pwdp;
    struct group *grpp;
    chownrequest_id_name_t *elem;

    freeOwnerRequestInfos();

    m_chown_user_list = new List();
    m_chown_group_list = new List();
  
    //setpwent();   // is this needed?
    pwdp = getpwent();
    while ( pwdp != NULL ) {
        elem = (chownrequest_id_name_t*)_allocsafe( sizeof( chownrequest_id_name_t ) );
        elem->name = dupstring( pwdp->pw_name );
        elem->id.uid = pwdp->pw_uid;
        m_chown_user_list->addElement( elem );
        pwdp = getpwent();
    }
    endpwent();

    //setgrent();  // is this needed?
    grpp = getgrent();
    while ( grpp != NULL ) {
        elem = (chownrequest_id_name_t*)_allocsafe( sizeof( chownrequest_id_name_t ) );
        elem->name = dupstring( grpp->gr_name );
        elem->id.gid = grpp->gr_gid;
        m_chown_group_list->addElement( elem );
        grpp = getgrent();
    }
    endgrent();
}

ChModOwnCore::changeown_info::changeown_info()
{
    forAll = false;
    newuid = (uid_t)-1;
    newgid = (gid_t)-1;
}
