#!/usr/bin/perl
package autofixer2::rebuild_fstab;

# cpanel - autofixer2/rebuild-fstab               Copyright(c) 2016 cPanel, Inc.
#                                                           All rights Reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited

# Do not run this autofixer by default.  It's better than no /etc/fstab, but
# it's not perfect and you could end up unhappy with its results.

use strict;

BEGIN { unshift @INC, '/usr/local/cpanel'; }

# This is part of core Perl and will be backwards compatible to all versions.
# Cpanel::Fcntl::Constants will not be, and memory is not a concern here.
use Fcntl;

our $FSTAB = '/etc/fstab';

sub is_affected {
    return !-e $FSTAB;
}

sub parse_proc_mounts {
    my @entries;
    if ( open( my $fh, '<', '/proc/mounts' ) ) {
        while ( my $line = <$fh> ) {
            my @items = split /\s+/, $line;
            next unless scalar @items == 6;
            my %entry;
            @entry{qw/device mountpoint type options frequency pass/} = @items;
            push @entries, \%entry;
        }
    }
    return @entries;
}

sub find_swap {

    # When using device mapper (e.g. LVM), all we get is the dm device, not the
    # name we passed to swapon(8).  If the system devices come up in a different
    # order, we could be left without swap, as the kernel would detect that the
    # device with that name wasn't a swap device.  It should not cause data loss
    # (writing over existing non-swap partitions), though.
    my @entries;
    if ( open( my $fh, '<', '/proc/swaps' ) ) {
        while ( my $line = <$fh> ) {
            next if $line =~ /^Filename/;
            my ($device) = split /\s+/, $line;
            my %entry;
            @entry{qw/device mountpoint type options frequency pass/} = ( $device, qw/swap swap defaults 0 0/ );
            push @entries, \%entry;
        }
    }
    return @entries;
}

sub add_entries {
    my (@entries) = @_;

    push @entries, find_swap();

    # We're not fixing CentOS 5, and CentOS 7 doesn't need these entries.
    return @entries unless eval {
        require Cpanel::GenSysInfo;
        Cpanel::GenSysInfo::get_rpm_distro_version() == 6;
    };
    my %mountpoints = map { $_->{'mountpoint'} => 1 } @entries;
    my @possible = (
        {
            device     => 'proc',
            mountpoint => '/proc',
            type       => 'proc',
            options    => 'defaults',
            frequency  => 0,
            pass       => 0,
        },
        {
            device     => 'devpts',
            mountpoint => '/dev/pts',
            type       => 'devpts',
            options    => 'gid=5,mode=620',
            frequency  => 0,
            pass       => 0,
        },
        {
            device     => 'sysfs',
            mountpoint => '/sys',
            type       => 'sysfs',
            options    => 'defaults',
            frequency  => 0,
            pass       => 0,
        },
        {
            device     => 'tmpfs',
            mountpoint => '/dev/shm',
            type       => 'tmpfs',
            options    => 'defaults',
            frequency  => 0,
            pass       => 0,
        },
    );
    foreach my $entry (@possible) {
        push @entries, $entry unless $mountpoints{ $entry->{'mountpoint'} };
    }
    return @entries;
}

sub entry_wanted {
    my ($entry) = @_;
    return 0 unless $entry->{'device'} =~ m{^/dev/};
    return 0 if $entry->{'mountpoint'} =~ m{^/home[^/]*/(?:virt|cage)fs};
    return 0 if $entry->{'mountpoint'} =~ m{^/usr/share/cagefs};
    return 1;
}

sub fix_up_fields {
    my ($entry) = @_;

    # /proc/mounts never fills in these fields, so fix them up.
    if ( $entry->{'mountpoint'} eq '/' ) {
        $entry->{'pass'} = $entry->{'frequency'} = 1;
    }
    else {
        $entry->{'pass'} = 2;
    }
    return $entry;
}

sub write_file {
    my (@entries) = @_;

    # Use O_EXCL here to prevent overwriting an existing file.
    return unless sysopen( my $fh, $FSTAB, O_WRONLY | O_CREAT | O_EXCL, 0644 );
    foreach my $entry (@entries) {
        print {$fh} join( "\t", @{$entry}{qw/device mountpoint type options frequency pass/} ), "\n";
    }
    close($fh);
    return;
}

sub fix {
    my @entries = map { fix_up_fields($_) } grep { entry_wanted($_) } parse_proc_mounts();
    write_file( add_entries(@entries) );
    return !is_affected();
}

sub script {
    return 0 unless is_affected();

    my $is_fixed = fix();
    print "fstab was rebuilt.\n";
    print "Please check and correct all entries, especially swap devices.\n";
    return !$is_fixed;
}

exit script() unless caller;
