/*
 * mounts.c - Minimal linked list set of mount points
 * Copyright (c) 2019 Red Hat Inc.
 * All Rights Reserved.
 *
 * This software may be freely redistributed and/or modified under the
 * terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2, 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; see the file COPYING. If not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor
 * Boston, MA 02110-1335, USA.
 *
 * Authors:
 *   Steve Grubb <sgrubb@redhat.com>
 */

#include "config.h"
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <errno.h>
#include <sys/stat.h>
#include "mounts.h"

void mlist_create(mlist *m)
{
        m->head = NULL;
        m->cur = NULL;
}

static void mlist_last(mlist *m)
{
	register mnode* window;

	if (m->head == NULL)
		return;

	window = m->head;
	while (window->next)
		window = window->next;

	m->cur = window;
}

// Returns 0 on success and 1 on error
int mlist_append(mlist *m, const char *p)
{
        mnode* newnode;

	if (m && p) {
		newnode = malloc(sizeof(mnode));
		if (newnode == NULL)
			return 1;
		newnode->path = strdup(p);
		if (newnode->path == NULL) {
			free(newnode);
			return 1;
		}
		newnode->status = MNT_ADD;
	} else
		return 1;

	newnode->next = NULL;
	mlist_last(m);

	// if we are at top, fix this up
	if (m->head == NULL)
		m->head = newnode;
	else    // Otherwise add pointer to newnode
		m->cur->next = newnode;

	// make newnode current
	m->cur = newnode;

	return 0;
}

/*
 * mlist_reconcile - merge a complete replacement list into the active list.
 * @m: active mount list whose status fields drive fanotify updates.
 * @next: fully parsed mount list that transfers new node ownership to @m.
 *
 * Parsing into @next first keeps @m unchanged if reading /proc/mounts or an
 * allocation fails. Matching paths retain their marks, missing paths become
 * deletions, and new nodes are moved without another allocation.
 *
 * Returns nothing.
 */
void mlist_reconcile(mlist *m, mlist *next)
{
	mnode *node;

	if (m == NULL || next == NULL)
		return;

	mlist_mark_all_deleted(m);
	node = next->head;
	while (node) {
		mnode *following = node->next;

		node->next = NULL;
		if (mlist_find(m, node->path)) {
			m->cur->status = MNT_NO_CHANGE;
			free((void *)node->path);
			free(node);
		} else {
			mlist_last(m);
			if (m->head == NULL)
				m->head = node;
			else
				m->cur->next = node;
			m->cur = node;
		}
		node = following;
	}
	next->head = NULL;
	next->cur = NULL;
	m->cur = m->head;
}

const char *mlist_first(mlist *m)
{
	m->cur = m->head;

	if (m->cur == NULL)
		return NULL;
	return m->cur->path;
}

const char *mlist_next(mlist *m)
{
	if (m->cur == NULL)
		return NULL;

	m->cur = m->cur->next;
	if (m->cur == NULL)
		return NULL;
	return m->cur->path;
}

void mlist_mark_all_deleted(mlist *m)
{
	register mnode *n = m->head;
	while (n) {
		n->status = MNT_DELETE;
		n = n->next;
	}
}

int mlist_find(mlist *m, const char *p)
{
	register mnode *n = m->head;
	while (n) {
		if (strcmp(p, n->path) == 0) {
			m->cur = n;
			return 1;
		}
		n = n->next;
	}
	return 0;
}

void mlist_clear(mlist *m)
{
	mnode* nextnode;
	register mnode* current;

	current = m->head;
	while (current) {
		nextnode=current->next;
		free((void *)current->path);
		free((void *)current);
		current=nextnode;
	}
	m->head = NULL;
	m->cur = NULL;
}
