From 15a053894d117641f4d0e98614db1c343c9a4a1c Mon Sep 17 00:00:00 2001 From: Per Cederqvist <ceder@lysator.liu.se> Date: Mon, 20 May 1991 21:25:23 +0000 Subject: [PATCH] Initial RCS checkin. --- src/server/send-async.c | 458 ++++++++ src/server/send-async.h | 43 + src/server/session.c | 405 +++++++ src/server/simple-cache.c | 1674 +++++++++++++++++++++++++++++ src/server/text-garb.c | 125 +++ src/server/text-garb.h | 3 + src/server/text.c | 2103 +++++++++++++++++++++++++++++++++++++ 7 files changed, 4811 insertions(+) create mode 100644 src/server/send-async.c create mode 100644 src/server/send-async.h create mode 100644 src/server/session.c create mode 100644 src/server/simple-cache.c create mode 100644 src/server/text-garb.c create mode 100644 src/server/text-garb.h create mode 100644 src/server/text.c diff --git a/src/server/send-async.c b/src/server/send-async.c new file mode 100644 index 000000000..b1de21f74 --- /dev/null +++ b/src/server/send-async.c @@ -0,0 +1,458 @@ +/* + * send-async.c -- Send messages about events to all connected clients. + * + * Written by Per Cederqvist 1990-07-22--23 + */ + +#include <stdio.h> +#include "lyskomd.h" +#include <kom-types.h> +#include <services.h> +#include "async.h" +#include "manipulate.h" +#include "com.h" +#include "connections.h" +#include "send-async.h" +#include "prot-a-send-async.h" +#include "prot-a-output.h" +#include "log.h" +#include "internal-connections.h" + + +extern Bool send_async_messages; + + +/* + * Check if person is a member in any of the recipients or cc_recipients + * of text_s + */ +static Bool +is_member_in_recpt(Person * person, + Text_stat * text_s) +{ + int i; + + for ( i = 0; i < text_s->no_of_misc; i++ ) + { + switch(text_s->misc_items[ i ].type) + { + case recpt: + if ( locate_membership(text_s->misc_items[ i ].datum.recipient, + person) != NULL ) + { + return TRUE; + } + break; + + case cc_recpt: + if ( locate_membership(text_s->misc_items[ i ].datum.cc_recipient, + person) != NULL ) + { + return TRUE; + } + break; + + case comm_to: + case comm_in: + case footn_to: + case footn_in: + case loc_no: + case rec_time: + case sent_by: + case sent_at: + break; + +#ifndef COMPILE_CHECKS + default: + log(__FILE__ ": is_member_in_recpt(): bad misc_item.\n"); + break; +#endif + } + } + + return FALSE; +} + +void +async_new_text(Text_no text_no, + Text_stat *text_s) +{ + Connection *cptr; + Session_no i = 0; + + if (!send_async_messages) + return; + + while ( (i = traverse_connections(i)) != 0 ) + { + cptr = get_conn_by_number(i); + + if ( cptr->person != NULL + && is_member_in_recpt( cptr->person, text_s ) ) + { + switch(cptr->protocol) + { + case 0: + break; + case 'A': + prot_a_async_new_text(cptr, text_no, text_s); + break; + default: + restart_kom("async_new_text(): bad protocol.\n"); + break; + } + + } + } +} + + + + +void +async_i_am_on(Who_info info) +{ + Connection *cptr; + Session_no i = 0; + + if (!send_async_messages) + return; + + while ( (i = traverse_connections(i)) != 0 ) + { + cptr = get_conn_by_number(i); + + switch(cptr->protocol) + { + case 0: /* Not yet logged on. */ + break; + case 'A': + prot_a_async_i_am_on(cptr, info); + break; + default: + restart_kom("async_i_am_on(): bad protocol.\n"); + break; + } + } +} + + +void +async_i_am_off(Pers_no pers_no) +{ + Connection *cptr; + Session_no i = 0; + + if (!send_async_messages) + return; + + while ( (i = traverse_connections(i)) != 0 ) + { + cptr = get_conn_by_number(i); + + if ( cptr == NULL ) + { + log("async_i_am_off(): cptr == NULL\n"); + return; + } + + switch(cptr->protocol) + { + case 0: + break; + case 'A': + prot_a_async_i_am_off(cptr, pers_no); + break; + default: + restart_kom("async_i_am_off(): bad protocol.\n"); + break; + } + } +} + +void +async_logout(Pers_no pers_no, + Session_no session_no) +{ + Connection *cptr; + Session_no i = 0; + + if (!send_async_messages) + return; + + while ( (i = traverse_connections(i)) != 0) + { + cptr = get_conn_by_number(i); + + if ( cptr == NULL ) + { + log("async_logout(): cptr == NULL\n"); + return; + } + + switch(cptr->protocol) + { + case 0: + break; + case 'A': + prot_a_async_logout(cptr, pers_no, session_no); + break; + default: + restart_kom("async_logout(): bad protocol.\n"); + break; + } + } +} + + + +void +async_new_name(Conf_no conf_no, + const String old_name, + const String new_name) +{ + Connection *cptr; + Session_no i = 0; + + if (!send_async_messages) + return; + + while ( (i = traverse_connections(i)) != 0) + { + cptr = get_conn_by_number(i); + + if ( cptr == NULL ) + { + log("async_new_name(): cptr == NULL\n"); + return; + } + + switch(cptr->protocol) + { + case 0: + break; + case 'A': + prot_a_async_new_name(cptr, conf_no, old_name, new_name); + break; + + default: + restart_kom("async_new_name(): bad protocol.\n"); + break; + } + } +} + + + + +#if 0 +conf_deleted +conf_created +#endif + +void +async_sync_db(void) +{ + Connection *cptr; + Session_no i = 0; + + if (!send_async_messages) + return; + + while ( (i = traverse_connections(i)) != 0 ) + { + cptr = get_conn_by_number(i); + + if ( cptr == NULL ) + { + log("async_sync_db(): cptr == NULL\n"); + return; + } + + switch(cptr->protocol) + { + case 0: + break; + case 'A': + prot_a_async_sync_db(cptr); + break; + default: + restart_kom("async_sync_db(): bad protocol.\n"); + break; + } + } +} + + +extern void +async_forced_leave_conf (struct connection *cptr, + Conf_no conf_no) +{ + if (!send_async_messages) + return; + + switch(cptr->protocol) + { + case 0: + break; + case 'A': + prot_a_async_forced_leave_conf(cptr, conf_no); + break; + default: + restart_kom("async_forced_leave_conf(): bad protocol.\n"); + break; + } +} + +void +async_login(Pers_no pers_no, + int client_no) +{ + Connection *cptr; + Session_no i = 0; + + if (!send_async_messages) + return; + + while ( (i = traverse_connections(i)) != 0) + { + cptr = get_conn_by_number(i); + + if ( cptr == NULL ) + { + log("async_login(): cptr == NULL\n"); + return; + } + + switch(cptr->protocol) + { + case 0: + break; + case 'A': + prot_a_async_login(cptr, pers_no, client_no); + break; + + default: + restart_kom("async_login(): bad protocol.\n"); + break; + } + } +} + +void +async_broadcast(Pers_no pers_no, + String message) +{ + Connection *cptr; + Session_no i = 0; + + if (!send_async_messages) + return; + + while ( (i = traverse_connections(i)) != 0) + { + cptr = get_conn_by_number(i); + + if ( cptr == NULL ) + { + log("async_broadcast(): cptr == NULL\n"); + return; + } + + switch(cptr->protocol) + { + case 0: + break; + case 'A': + prot_a_async_broadcast(cptr, pers_no, message); + break; + + default: + restart_kom("async_broadcast(): bad protocol.\n"); + break; + } + } +} + + +void +async_rejected_connection(void) +{ + Connection *cptr; + Session_no i = 0; + + if (!send_async_messages) + return; + + while ( (i = traverse_connections(i)) != 0) + { + cptr = get_conn_by_number(i); + + if ( cptr == NULL ) + { + log("async_rejected_connections(): cptr == NULL\n"); + return; + } + + switch(cptr->protocol) + { + case 0: + break; + case 'A': + prot_a_async_rejected_connection(cptr); + break; + + default: + restart_kom("async_rejected_connection(): bad protocol.\n"); + break; + } + } +} + + +/* + * Returns failure if no message was sent. + */ +Success +async_send_message(Pers_no recipient, + Pers_no sender, + String message) +{ + Connection *cptr; + Session_no i = 0; + Success retval = FAILURE; + + if (!send_async_messages) + return FAILURE; + + while ( (i = traverse_connections(i)) != 0) + { + cptr = get_conn_by_number(i); + + if ( cptr == NULL ) + { + log("async_send_message(): cptr == NULL\n"); + return FAILURE; + } + + switch(cptr->protocol) + { + case 0: + break; + case 'A': + if ( recipient == 0 || + (recipient == cptr->pers_no && recipient != 0 )) + { + prot_a_async_send_message(cptr, recipient, sender, message); + retval = OK; + } + break; + + default: + restart_kom("async_send_message(): bad protocol.\n"); + break; + } + } + + return retval; +} diff --git a/src/server/send-async.h b/src/server/send-async.h new file mode 100644 index 000000000..a412a6374 --- /dev/null +++ b/src/server/send-async.h @@ -0,0 +1,43 @@ +extern void +async_new_text(Text_no text_no, + Text_stat *text_s); + +extern void +async_i_am_on(Who_info info); + +extern void +async_i_am_off(Pers_no person); + +extern void +async_logout(Pers_no pers_no, + Session_no session_no); + +extern void +async_new_name(Conf_no conf_no, + const String old_name, + const String new_name); + +extern void +async_sync_db(void); + + +extern void +async_forced_leave_conf (Connection *cptr, + Conf_no conf_no); + + +extern void +async_login(Pers_no pers_no, + int client_no); + +extern void +async_broadcast(Pers_no pers_no, + String message); + +extern void +async_rejected_connection(void); + +extern Success +async_send_message(Pers_no recipient, + Pers_no sender, + String message); diff --git a/src/server/session.c b/src/server/session.c new file mode 100644 index 000000000..5d2d38520 --- /dev/null +++ b/src/server/session.c @@ -0,0 +1,405 @@ +/* + * session.c + * + * Session control and miscellaneous. + */ + +#include <time.h> +#include <stdlib.h> +#include "lyskomd.h" +#include <kom-types.h> +#include <services.h> +#include "manipulate.h" +#include "cache.h" +#include "com.h" +#include "connections.h" +#include "send-async.h" +#include "smalloc.h" +#include "log.h" +#include <kom-errno.h> +#include "config.h" +#include "internal-connections.h" + +/* + * This function is called whenever a person leaves a conf, + * i e when he pepsi():s or logout():s. + */ + +extern void +leave_conf(void) +{ + Membership * mship; + + if (active_connection->cwc != 0 ) + { + if ((mship = locate_membership( active_connection->cwc, ACT_P )) + != NULL ) + { + time(&mship->last_time_read); + mark_person_as_changed (active_connection->pers_no); + } + else + { + log("ERROR: leave_conf(): Can't find membership of cwc.\n"); + } + + cached_unlock_conf( active_connection->cwc ); + active_connection->cwc = 0; + } +} + +/* + * Log in as user pers_no. If ACTPERS is a supervisor of pers_no the login + * will succeed regardless of the passwd. An logout() will automatically + * be performed if ACTPERS is logged in. + */ +extern Success +login (Pers_no pers_no, + const String passwd) +{ + Person *pers_p; + + GET_P_STAT(pers_p, pers_no, FAILURE); + +#if 0 + if ( !logins_allowed && !pers_p->privileges.wheel) + { + kom_errno = KOM_LOGIN_DISALLOWED; + return FAILURE; + } +#endif + + if ( !is_supervisor(pers_no, NULL) + && chk_passwd(pers_p->pwd, passwd) == FAILURE ) + { + kom_errno = KOM_PWD; + return FAILURE; + } + + logout(); /*+++ How many tries are allowed before disconnection? */ + + ACTPERS = pers_no; + ACT_P = pers_p; + + cached_lock_person(pers_no); + + pers_p->last_login = time(&active_connection->session_start); + ++pers_p->sessions; + + s_strcpy(&pers_p->username, active_connection->username); + + mark_person_as_changed( pers_no ); + + async_login(ACTPERS, active_connection->session_no); + + return OK; +} + + +/* + * Log out. Does not disconnect the client. The person is also automatically + * logged out if the connection is closed. Takes no action if noone is logged + * in on this line. Never fails. + */ +extern Success +logout( void ) +{ + if ( ACTPERS != 0 ) /* Is he logged in? Then log him out. */ + { + async_i_am_off( ACTPERS ); + async_logout( ACTPERS, active_connection->session_no ); + + leave_conf(); + ACT_P->total_time_present += + difftime(time(&ACT_P->last_login), + active_connection->session_start); + + cached_unlock_person( ACTPERS ); + mark_person_as_changed( ACTPERS ); + } + + + s_clear(&active_connection -> what_am_i_doing); + + active_connection->person = NULL; + active_connection->cwc = 0; + active_connection->pers_no = 0; + active_connection->ena_level = 0; + + return OK; +} + + +/* + * Change Conference. + * + * You are not allowed to change to a conference unless you are a + * member in the conference. + * + * You can pepsi(0) to indicate that you are no longer in a certain + * conference. + * + * There are two reasons to use this call: + * 1) Other persons want to know what you are doing. + * 2) When the server checks that you have permission to + * read a certain text it first checks if you current working + * conference is a recipient of the text, since that is a + * cheap test. If that test fails it will have to read in the + * conference structs for the recipients of the text until it + * finds an open one. + * + * In the future the server might read in the conference in advance when + * someone pepsi's to it to allow faster response times. + */ +extern Success +pepsi (Conf_no conference) +{ + Who_info info = EMPTY_WHO_INFO; + + CHK_LOGIN(FAILURE); + if ( conference != 0 ) + { + CHK_EXIST(conference, FAILURE); + + if ( locate_membership( conference, ACT_P) == NULL ) + { + kom_errno = KOM_NOT_MEMBER; + return FAILURE; + } + } + + leave_conf(); + + active_connection->cwc = conference; + if ( conference != 0 ) + cached_lock_conf( conference ); + + info.person = ACTPERS; + info.what_am_i_doing = active_connection->what_am_i_doing; + info.username = active_connection->username; + info.working_conference = conference; + info.session_no = active_connection->session_no; + + async_i_am_on(info); + + return OK; +} + + +/* + * Tell the server what you are doing. This string is sent to anyone + * who does a 'who_is_on()'. + */ +extern Success +change_what_i_am_doing (String what_am_i_doing) +{ + Who_info info = EMPTY_WHO_INFO; + + if ( s_strlen( what_am_i_doing ) > WHAT_DO_LEN ) + { + s_clear ( &what_am_i_doing ); + kom_errno = KOM_LONG_STR; + return FAILURE; + } + + s_clear ( &active_connection->what_am_i_doing ); + active_connection->what_am_i_doing = what_am_i_doing; + + info.person = ACTPERS; + info.what_am_i_doing = active_connection->what_am_i_doing; + info.username = active_connection->username; + info.working_conference = active_connection->cwc; + info.session_no = active_connection->session_no; + + async_i_am_on(info); + + return OK; +} + +/* + * Get info about what all the currently logged in persons are doing. + */ +extern Success +who_is_on( Who_info_list *result ) +{ + Connection *cptr; + int no_of_clients = 0; + int i; + Session_no session; + + cptr = active_connection; + + for ( session = 0; (session = traverse_connections(session)) != 0; ) + { + cptr = get_conn_by_number(session); + + if ( cptr->person != NULL ) + ++no_of_clients; + } + + result->no_of_persons = no_of_clients; + result->info = tmp_alloc ( no_of_clients * sizeof(Who_info)); + + for ( session = 0, i = 0; + i < no_of_clients && (session = traverse_connections(session)) != 0; ) + { + cptr = get_conn_by_number(session); + + if ( cptr->person != NULL ) + { + result->info[ i ] = EMPTY_WHO_INFO; + result->info[ i ].person = cptr->pers_no; + result->info[ i ].what_am_i_doing = cptr->what_am_i_doing; + result->info[ i ].username = cptr->username; + result->info[ i ].working_conference = cptr->cwc; + result->info[ i ].session_no = cptr->session_no; + ++i; + } + } + + if ( i != no_of_clients ) + log("who_is_on: i == %d, no_of_clients == %d\n", + i, no_of_clients); + + return OK; +} + + +extern Success +get_session_info (Session_no session_no, + Session_info *result) +{ + Connection *cptr; + + CHK_LOGIN(FAILURE); + + cptr = get_conn_by_number(session_no); + + if ( cptr != NULL ) + { + *result = EMPTY_SESSION_INFO; + result->person = cptr->pers_no; + result->what_am_i_doing = cptr->what_am_i_doing; + result->username = cptr->username; + result->working_conference = cptr->cwc; + result->session = cptr->session_no; + result->connection_time = cptr->session_start; + result->idle_time = difftime(time(NULL), + cptr->last_request); + + return OK; + } + else + { + kom_errno = KOM_UNDEF_SESSION; + return FAILURE; + } +} + +extern Success +who_am_i (Session_no *session_no) +{ + *session_no = active_connection->session_no; + return OK; +} + + + +extern Success +disconnect (Session_no session_no) +{ + Connection *cptr; + + if ( session_no != active_connection->session_no ) + CHK_LOGIN(FAILURE); + + cptr = get_conn_by_number(session_no); + + if ( cptr != NULL ) + { + if ( is_supervisor(cptr->pers_no, NULL) + || session_no == active_connection->session_no ) + { + cptr->kill_me = TRUE; + return OK; + } + else + { + kom_errno = KOM_PERM; + return FAILURE; + } + } + else + { + kom_errno = KOM_UNDEF_SESSION; + return FAILURE; + } +} + + +/* Get less info */ +extern Success +who_is_on_old( Who_info_list_old *result ) +{ + Connection *cptr; + int no_of_clients = 0; + int i; + Session_no session = 0; + + + while ( (session = traverse_connections(session)) != 0) + { + cptr = get_conn_by_number(session); + + if ( cptr->person != NULL ) + ++no_of_clients; + } + + result->no_of_persons = no_of_clients; + result->info = tmp_alloc ( no_of_clients * sizeof(Who_info)); + + for (session = 0, i = 0; + i < no_of_clients && (session = traverse_connections(session)) != 0;) + { + cptr = get_conn_by_number(session); + + if ( cptr->person != NULL ) + { + result->info[ i ] = EMPTY_WHO_INFO_OLD; + result->info[ i ].person = cptr->pers_no; + result->info[ i ].what_am_i_doing = cptr->what_am_i_doing; + result->info[ i ].working_conference = cptr->cwc; + ++i; + } + } + + if ( i != no_of_clients ) + log("who_is_on: i == %d, no_of_clients == %d\n", + i, no_of_clients); + + return OK; +} + +/* + * Ask the server what it thinks the time is. + */ +extern Success +get_time( time_t *clock ) +{ + time(clock); + + return OK; +} + + + +/* + * Set ena_level. 0 means don't use any privileges. /// + */ +extern Success +enable (u_char ena_level) +{ + CHK_LOGIN(FAILURE); + active_connection->ena_level = ena_level; + return OK; +} diff --git a/src/server/simple-cache.c b/src/server/simple-cache.c new file mode 100644 index 000000000..d5ab99a80 --- /dev/null +++ b/src/server/simple-cache.c @@ -0,0 +1,1674 @@ +/* + * This module contains some simple simulations of the routines in + * cache.c. + * + * Extracted from ram-cache.c and rewritten by ceder. + * + * New database format with texts in their own file by Inge Wallin. + * + * New save algorithm by ceder. + */ + +/* + * Things to implemet a.s.a.p: + * + * +++ Limit on lru. + * +++ Better allocation scheme of Cache-nodes. + */ + +/* + * Possible improvements: +++ + * When there are consecutive items in file A that shall be copied + * to file B, copy them in one transfer (up to a certain limit). + * + * In pre_sync: compute size of, and allocate disk space for file B. + */ + +/* + * All functions that can fail sets kom_errno to a suitable value + * if they fail. + */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <sys/file.h> + +#include <kom-errno.h> +#include <kom-types.h> +#include "s-collat-tables.h" +#include "smalloc.h" +#include "cache.h" +#include <debug.h> +#include "lyskomd.h" +#include "parser.h" +#include "ram-parse.h" +#include "log.h" +#include "com.h" +#include "isc.h" +#include "ram-output.h" +#include "connections.h" +#include "prot-a-output.h" +#include "send-async.h" +#include "memory.h" +#include "exp.h" + +#include "limits.h" + +typedef struct cache_node { + struct { + u_int exists : 1; + u_int exists_b : 1; + u_int dirty : 1; /* [r *ptr modifierad? */ + } s; + void *snap_shot; /* Dirty data to be written to file B. */ + /* (Dirty relative to file A). */ + void *ptr; /* In-core data. */ + long pos; /* Position to element in file A. */ + long size; /* Size on disk. */ + long pos_b; /* Position to element in file B. */ + long size_b; /* Size in file B. */ + struct cache_node *lru; + struct cache_node *mru; + int lock_cnt; +} Cache_node; + +const Cache_node EMPTY_CACHE_NODE = + ((Cache_node){{ 0, 0, 0}, NULL, NULL, 0, 0, 0, 0, NULL, NULL, 0}); + +static Cache_node *pers_mru = NULL; +static Cache_node *pers_lru = NULL; +static Cache_node *conf_mru = NULL; +static Cache_node *conf_lru = NULL; +static Cache_node *text_mru = NULL; +static Cache_node *text_lru = NULL; + +static Cache_node * pers_arr[ MAX_CONF ]; +static Cache_node * conf_arr[ MAX_CONF ]; +static Small_conf * small_conf_arr[ MAX_CONF ]; +static int next_free_num = 1; + +static Cache_node * text_arr[ MAX_TEXT ]; +static int next_text_num = 1; + +/* Defined in ramkomd.c */ +extern char datafilename[1024]; +extern char backupfilename[1024]; +extern char textfilename[1024]; + +/* + * The elements in the following lists with same index refers to the same + * conference. + */ +static int no_of_match_info; +EXPORT Matching_info *match_table = NULL; +EXPORT Conf_no *conf_table = NULL; + + +static FILE *text_file= NULL; +static FILE *file_a = NULL; /* Current file. */ +static FILE *file_b = NULL; /* File under construction. */ + +/* + * Four state variables for the background save. + */ +static enum { + sync_save_conf, + sync_save_pers, + sync_save_text, + sync_error, + sync_wait, + sync_ready +} sync_state; + +static long sync_next; + +static Conf_no highest_conf_no; +static Text_no highest_text_no; + +BUGDECL; + + +/* Macros */ + +#define TRACE2(format, arg) if ( buglevel > 2 ) printf(format, arg) +#define TRACE1(format) if ( buglevel > 2 ) printf(format) +#define TRACESTR(str) if ( buglevel > 2 ) s_puts(str) + + +static Person * +read_person(FILE *fp, + long pos, + long size) +{ + Person *p; + + p = alloc_person(); + fseek(fp, pos+1, SEEK_SET); /* Skip '+' */ + if ( fparse_person(fp, p) != OK ) + { + free_person(p); + return NULL; + } + else + return p; +} + + + +static Conference * +read_conference(FILE *fp, + long pos, + long size) +{ + Conference *c; + + c = alloc_conference(); + fseek(fp, pos+1, SEEK_SET); /* Skip '+' */ + if ( fparse_conference(fp, c) != OK ) + { + free_conference(c); + return NULL; + } + else + return c; +} + + +static Text_stat * +read_text_stat(FILE *fp, + long pos, + long size) +{ + Text_stat *t; + + t = alloc_text_stat(); + fseek(fp, pos+1, SEEK_SET); /* Skip '+' */ + if ( fparse_text_stat(fp, t) != OK ) + { + free_text_stat(t); + return NULL; + } + else + return t; +} + + +static void +unlink_lru(Cache_node *node, + Cache_node **lru, + Cache_node **mru) +{ + Cache_node *link; + + link = node->lru; + + if ( node->lru != NULL ) + node->lru->mru = node->mru; + else if (*lru == node) + *lru = node->mru; + + if ( node->mru != NULL ) + node->mru->lru = link; + else if (*mru == node) + *mru = link; +} + +static void +insert_mru(Cache_node *node, + Cache_node **lru, + Cache_node **mru) +{ + node->mru = NULL; + node->lru = *mru; + *mru = node; + if ( *lru == NULL ) + *lru = node; + + if ( node->lru != NULL ) + node->lru->mru = node; +} + +static void +pers_set_mru(Pers_no pers_no) +{ + Cache_node *node = pers_arr[ pers_no ]; + + if (node == NULL) + restart_kom("pers_set_mru(%d): nonexistent.\n", pers_no); + + unlink_lru(node, &pers_lru, &pers_mru); + insert_mru(node, &pers_lru, &pers_mru); +} + +static void +text_set_mru(Text_no text_no) +{ + Cache_node *node = text_arr[ text_no ]; + + if ( node == NULL ) + restart_kom("text_set_mru(%d): nonexistent.\n", text_no); + + unlink_lru(node, &text_lru, &text_mru); + insert_mru(node, &text_lru, &text_mru); +} + + +static void +conf_set_mru(Conf_no conf_no) +{ + Cache_node *node = conf_arr[ conf_no ]; + + if ( node == NULL ) + restart_kom("conf_set_mru(%d): nonexistent.\n", conf_no); + + unlink_lru(node, &conf_lru, &conf_mru); + insert_mru(node, &conf_lru, &conf_mru); +} + + +/* + * Name caching routines + */ + +/* + * change_name changes the cached conference name. It is only called when + * a conference name is changed or a conference is deleted. + */ +void +cached_change_name( Conf_no name_num, + String new_name ) +{ + if ( name_num < 1 || name_num >= next_free_num ) + restart_kom("cached_change_name(%d, ----): next_free_num==%d", + name_num, next_free_num); + + s_clear( &small_conf_arr[name_num]->name ); + s_strcpy( &small_conf_arr[name_num]->name, new_name); + build_matching_info(); +} + + +extern void +cached_set_conf_type (Conf_no conf_no, + Conf_type type) +{ + if ( conf_no < 1 || conf_no >= next_free_num ) + restart_kom("cached_set_conf_type(%d, ----): next_free_num==%d", + conf_no, next_free_num); + + small_conf_arr[ conf_no ]->type = type; +} + + +extern Conf_type +cached_get_conf_type (Conf_no conf_no) +{ + if ( conf_no < 1 || conf_no >= next_free_num ) + restart_kom("cached_get_conf_type(%d): next_free_num==%d", + conf_no, next_free_num); + + return small_conf_arr [ conf_no ]->type; +} + + + +/* + * Various function calls to tell the cache that something is changed. + */ + +void +mark_person_as_changed(Pers_no pers_no) +{ + TRACE2("Person %d is changed\n", pers_no); + if ( pers_arr[ pers_no ] == NULL || pers_arr[ pers_no ]->s.exists == 0) + restart_kom("mark_person_as_changed(%d): nonexistent.\n", pers_no); + + pers_arr[ pers_no ]->s.dirty = 1; + pers_set_mru( pers_no ); +} + + +void +mark_conference_as_changed(Conf_no conf_no) +{ + TRACE2("Conf. %d is changed\n", conf_no); + if ( conf_arr[ conf_no ] == NULL || conf_arr[ conf_no ]->s.exists == 0) + restart_kom("mark_conference_as_changed(%d): nonexistent.\n", conf_no); + + conf_arr[ conf_no ]->s.dirty = 1; + conf_set_mru( conf_no ); + small_conf_arr[ conf_no ]->highest_local_no + = (((Conference *)conf_arr [ conf_no ]->ptr)->texts.first_local_no - 1 + + ((Conference *)conf_arr [ conf_no ]->ptr)->texts.no_of_texts ); +} + +void +mark_text_as_changed( Text_no text_no ) +{ + TRACE2("Text %d is changed.\n", text_no); + if ( text_no < 1 || text_no >= next_text_num + || text_arr[ text_no ] == NULL || text_arr[ text_no ]->s.exists == 0) + { + restart_kom("mark_text_as_changed(%d): nonexistent.\n", text_no); + } + + text_arr[ text_no ]->s.dirty = 1; + text_set_mru( text_no ); +} + + +static Cache_node * +alloc_cache_node(void) +{ + Cache_node *c; + + c = smalloc(sizeof(Cache_node)); + *c = EMPTY_CACHE_NODE; + return c; +} + +/* + * Person-related calls + */ + + +extern Success +cached_create_person( Pers_no person ) +{ + TRACE2("Person %d is being created.\n", person); + + if ( person < 1 || person >= next_free_num ) + { + restart_kom("cached_create_person(%d): next_free_num == %d.\n", + person, next_free_num); + } + + if ( pers_arr[ person ] != NULL ) + { + restart_kom("cached_create_person(%d): Person existed.\n", + person); + } + + pers_arr[ person ] = alloc_cache_node(); + pers_arr[ person ]->ptr = alloc_person(); + pers_arr[ person ]->s.dirty = 1; + pers_arr[ person ]->s.exists = 1; + pers_set_mru( person ); + return OK; +} + + +extern Person * +cached_get_person_stat( Pers_no person ) +{ + TRACE2("cached_get_person_stat %d\n", person); + + if ( person == 0 ) + { + kom_errno = KOM_CONF_ZERO; + return NULL; + } + + if ( person >= next_free_num || pers_arr[ person ] == NULL + || pers_arr[ person ]->s.exists == 0 ) + { + kom_errno = KOM_UNDEF_PERS; + return NULL; + } + + if ( pers_arr[ person ]->ptr != NULL ) + { + pers_set_mru( person ); + return pers_arr[ person ]->ptr; + } + + if ( pers_arr[ person ]->snap_shot != NULL ) + { + pers_arr[ person ]->ptr = copy_person(pers_arr[ person ]->snap_shot); + pers_set_mru (person); + return pers_arr[ person ]->ptr; + } + + pers_arr[ person ]->ptr = read_person(file_a, + pers_arr[ person ]->pos, + pers_arr[ person ]->size); + + pers_set_mru (person); + return pers_arr[ person ]->ptr; +} + + +/* + * Conference-related calls + */ +extern Conf_no /* Also cache the name */ +cached_create_conf (String name) +{ + Conference * conf_c; + Conf_no conf_no; + + TRACE1("cached_create_conf( "); + TRACESTR(name); + TRACE1(" )\n"); + + if ( next_free_num >= MAX_CONF ) + { + kom_errno = KOM_INDEX_OUT_OF_RANGE; + return 0; + } + + conf_no = next_free_num++; + + conf_arr[ conf_no ] = alloc_cache_node(); + conf_arr[ conf_no ]->s.exists = 1; + conf_arr[ conf_no ]->s.dirty = 1; + conf_arr[ conf_no ]->ptr = conf_c = alloc_conference(); + conf_set_mru(conf_no); + + pers_arr[ conf_no ] = NULL; + small_conf_arr[ conf_no ] = alloc_small_conf(); + + conf_c->name = EMPTY_STRING; + s_strcpy(&conf_c->name, name); + cached_change_name( conf_no, name); + + TRACE2(" number %d\n", conf_no); + + return conf_no; +} + + +extern Success +cached_delete_conf( Conf_no conf ) +{ + if ( conf == 0 ) + { + kom_errno = KOM_CONF_ZERO; + return FAILURE; + } + + if ( conf >= next_free_num || conf_arr[ conf ] == NULL + || conf_arr[ conf ]->s.exists == 0 ) + { + kom_errno = KOM_UNDEF_CONF; + return FAILURE; + } + + if ( conf_arr[ conf ]->lock_cnt > 0 ) + log("cached_delete_conf(%d): lock_cnt === %d\n", + conf, conf_arr[ conf ]->lock_cnt); + + free_conference(conf_arr[ conf ]->ptr); + conf_arr[ conf ]->ptr = NULL; + conf_arr[ conf ]->s.exists = 0; + + return OK; +} + +Success +cached_delete_person(Pers_no pers) +{ + if ( pers == 0 ) + { + kom_errno = KOM_CONF_ZERO; + return FAILURE; + } + + if ( pers >= next_free_num || pers_arr[ pers ] == NULL + || pers_arr[ pers ]->s.exists == 0 ) + { + log("cached_delete_person(): attempt to" + " delete non-existing person.\n"); + kom_errno = KOM_UNDEF_PERS; + return FAILURE; + } + + if ( pers_arr[ pers ]->lock_cnt > 0 ) + log("cached_delete_pers(%d): lock_cnt === %d\n", + pers, pers_arr[ pers ]->lock_cnt); + + free_person(pers_arr[ pers ]->ptr); + pers_arr[ pers ]->ptr = NULL; + pers_arr[ pers ]->s.exists = 0; + return OK; +} + +Success +cached_delete_text(Text_no text) +{ + if ( text == 0 ) + { + kom_errno = KOM_TEXT_ZERO; + return FAILURE; + } + + if ( text >= next_text_num || text_arr[ text ] == NULL + || text_arr[ text ]->s.exists == 0 ) + { + log("cached_delete_text(): attempt to" + " delete non-existing text %d.\n", text); + kom_errno = KOM_NO_SUCH_TEXT; + return FAILURE; + } + + if ( text_arr[ text ]->lock_cnt > 0 ) + log("cached_delete_text(%d): lock_cnt === %d\n", + text, text_arr[ text ]->lock_cnt); + + + free_text_stat(text_arr[ text ]->ptr); + text_arr[ text ]->ptr = NULL; + text_arr[ text ]->s.exists = 0; + + return OK; +} + + +extern Conference * +cached_get_conf_stat( Conf_no conf_no ) +{ + TRACE2("cached_get_conf_stat %d\n", conf_no); + + if ( conf_no == 0 ) + { + kom_errno = KOM_CONF_ZERO; + return NULL; + } + + if ( conf_no >= next_free_num || conf_arr[ conf_no ] == NULL + || conf_arr[ conf_no ]->s.exists == 0 ) + { + kom_errno = KOM_UNDEF_CONF; + return NULL; + } + + if ( conf_arr[ conf_no ]->ptr != NULL ) + { + conf_set_mru( conf_no ); + return conf_arr[ conf_no ]->ptr; + } + + if ( conf_arr[ conf_no ]->snap_shot != NULL ) + { + conf_arr[ conf_no ]->ptr = copy_conf(conf_arr[ conf_no ]->snap_shot); + conf_set_mru (conf_no); + return conf_arr[ conf_no ]->ptr; + } + + conf_arr[ conf_no ]->ptr + = read_conference(file_a, conf_arr[ conf_no ]->pos, + conf_arr[ conf_no ]->size); + + conf_set_mru (conf_no); + return conf_arr[ conf_no ]->ptr; +} + +/* + * Return TRUE if conf_no exists. + */ +Bool +cached_conf_exists(Conf_no conf_no) +{ + return (conf_no > 0 && conf_no < next_free_num + && conf_arr[ conf_no ]->s.exists != 0) ? TRUE : FALSE; +} + + +/* + * Calls to handle texts + */ + +/* + * +++ Should return Success. + */ +extern String +cached_get_text( Text_no text ) +{ + String the_string; + Text_stat *t_stat; + + TRACE2("cached_get_text %d\n", text); + + if ( (t_stat = cached_get_text_stat (text)) == NULL ) + return EMPTY_STRING; + else + { + the_string.string = tmp_alloc( t_stat->no_of_chars ); + the_string.len = t_stat->no_of_chars; + fseek(text_file, t_stat->file_pos, SEEK_SET); + + if ( fread(the_string.string, sizeof(char), the_string.len, text_file) + != the_string.len ) + { + log("WARNING: cached_get_text: " + "couldn't read enough characters of text %d\n", + text); + return EMPTY_STRING; + } + + return the_string; + } +} + + +extern Text_stat * /* NULL on error */ +cached_get_text_stat( Text_no text ) +{ + TRACE2("cached_get_text_stat(%d); next_text_num == ", text); + TRACE2("%d\n", next_text_num); + + if ( text == 0 ) + { + kom_errno = KOM_TEXT_ZERO; + return NULL; + } + + if ( text >= next_text_num || text_arr[ text ] == NULL + || text_arr[ text ]->s.exists == 0 ) + { + TRACE1("cached_get_text_stat: no such text.\n"); + kom_errno = KOM_NO_SUCH_TEXT; + return NULL; + } + + if ( text_arr[ text ]->ptr != NULL ) + { + TRACE1("Found in ptr.\n"); + text_set_mru( text ); + return text_arr[ text ]->ptr; + } + + if ( text_arr[ text ]->snap_shot != NULL ) + { + TRACE1("Found in snap_shot\n"); + text_arr[ text ]->ptr = copy_text_stat(text_arr[ text ]->snap_shot); + text_set_mru (text); + return text_arr[ text ]->ptr; + } + + TRACE1("Found in file A.\n"); + text_arr[ text ]->ptr + = read_text_stat(file_a, text_arr[ text ]->pos, + text_arr[ text ]->size); + + text_set_mru (text); + return text_arr[ text ]->ptr; +} + + + +/* + * The text is set up with an empty misc-field. The misc field is + * than initialized by create_text. + */ + +extern Text_no +cached_create_text( String message) +{ + Text_no tno; + + tno = next_text_num++; + + TRACE2("cached_create_text (len=%d)\n", message.len); + + if ( tno >= MAX_TEXT ) + { + kom_errno = KOM_INDEX_OUT_OF_RANGE; + next_text_num = MAX_TEXT; + + return 0; + } + + text_arr[ tno ] = alloc_cache_node(); + + text_arr[ tno ]->s.exists = 1; + text_arr[ tno ]->s.dirty = 1; + text_arr[ tno ]->ptr = alloc_text_stat(); + ((Text_stat *)text_arr[ tno ]->ptr)->no_of_misc = 0; + ((Text_stat *)text_arr[ tno ]->ptr)->misc_items = NULL; + ((Text_stat *)text_arr[ tno ]->ptr)->no_of_marks = 0; + ((Text_stat *)text_arr[ tno ]->ptr)->no_of_lines = 0; + ((Text_stat *)text_arr[ tno ]->ptr)->no_of_chars = 0; + fseek(text_file, 0, SEEK_END); + ((Text_stat *)text_arr[ tno ]->ptr)->file_pos = ftell(text_file); + + text_set_mru( tno ); + + if ( fwrite(message.string, sizeof(char), message.len, text_file) + != message.len ) { + log("WARNING: cached_create_text: Couldn't write the text %d\n", + tno); + } + + fflush(text_file); + + TRACE2("cached_create_text -> %d\n", tno); + + return tno; +} + + +Text_no +traverse_text(Text_no seed) +{ + seed++; + + while ( seed < next_text_num + && (text_arr[ seed ] == NULL + || text_arr[ seed ]->s.exists == 0 ) ) + { + seed++; + } + + return (seed >= next_text_num) ? 0 : seed ; +} + +Pers_no +traverse_person(Pers_no seed) +{ + seed++; + + while ( seed < next_free_num + && (pers_arr[ seed ] == NULL + | pers_arr[ seed ]->s.exists == 0 )) + { + seed++; + } + + return (seed >= next_free_num) ? 0 : seed ; +} + +Conf_no +traverse_conference(Conf_no seed) +{ + seed++; + + while ( seed < next_free_num + && (conf_arr[ seed ] == NULL + || conf_arr[ seed ]->s.exists == 0 )) + { + seed++; + } + + return (seed >= next_free_num) ? 0 : seed ; +} + +extern Garb_nice +cached_get_garb_nice (Conf_no conf_no) +{ + return small_conf_arr [ conf_no ]->nice; +} + + +extern Local_text_no +cached_get_highest_local_no (Conf_no conf_no) +{ + return small_conf_arr[ conf_no ]->highest_local_no; +} + +/* Lock a person struct in memory. Increase a referenc count. */ +void +cached_lock_person(Pers_no pers_no) +{ + if ( pers_arr[ pers_no ]->s.exists == 0 ) + restart_kom("cached_lock_person(%d): nonexistent.\n", pers_no); + + if ( pers_arr[ pers_no ]->ptr == NULL ) + { + Person *ptr; + + ptr = cached_get_person_stat( pers_no ); + + if ( ptr == NULL ) + restart_kom("cached_lock_person(%d): couldn't read in person.\n", + pers_no); + + if ( ptr != pers_arr[ pers_no ]->ptr ) + restart_kom("cached_lock_person(%d): ptr == %d, pers_arr[pers_no]" + "->ptr == %d.\n", pers_no, ptr, + pers_arr[ pers_no ]->ptr); + } + + pers_arr[ pers_no ]->lock_cnt++; +} + +/* Decrease reference count. If zero, unlock person. */ +void +cached_unlock_person(Pers_no pers_no) +{ + if ( pers_arr[ pers_no ]->lock_cnt <= 0 ) + { + log("cached_unlock_person(%d): lock_cnt == %d.\n", + pers_no, pers_arr[ pers_no ]->lock_cnt); + + pers_arr[ pers_no ]->lock_cnt = 0; + } + else + pers_arr[ pers_no ]->lock_cnt--; +} + + +/* Lock a conf struct in memory. Increase a referenc count. */ +void +cached_lock_conf(Conf_no conf_no) +{ + if ( conf_arr[ conf_no ]->s.exists == 0 ) + restart_kom("cached_lock_conf(%d): nonexistent.\n", conf_no); + + if ( conf_arr[ conf_no ]->ptr == NULL ) + { + Conference *ptr; + + ptr = cached_get_conf_stat( conf_no ); + + if ( ptr == NULL ) + restart_kom("cached_lock_conf(%d): couldn't read in conf.\n", + conf_no); + + if ( ptr != conf_arr[ conf_no ]->ptr ) + restart_kom("cached_lock_conf(%d): ptr == %d, conf_arr[conf_no]" + "->ptr == %d.\n", conf_no, ptr, + conf_arr[ conf_no ]->ptr); + } + + conf_arr[ conf_no ]->lock_cnt++; +} + +/* Decrease reference count. If zero, unlock conf. */ +void +cached_unlock_conf(Conf_no conf_no) +{ + if ( conf_arr[ conf_no ]->lock_cnt <= 0 ) + { + log("cached_unlock_conf(%d): lock_cnt == %d.\n", + conf_no, conf_arr[ conf_no ]->lock_cnt); + + conf_arr[ conf_no ]->lock_cnt = 0; + } + else + conf_arr[ conf_no ]->lock_cnt--; +} + + + +/* + * And here comes some functions to deal with lookup_names. + */ + +/* Free the _contents_ of a match_table. The table itself i _not_ freed. */ + +static void +free_match_table(Matching_info *match) +{ + if ( match == NULL ) + return; + + while ( ! s_empty( match->name )) + { + free_tokens( match->tokens ); + /* match->name is not freed since it points into conf_arr[]. */ + + ++match; + } +} + +extern Success +build_matching_info(void) +{ + Conf_no i; + Matching_info *match; + Conf_no *conf; + + free_match_table(match_table); + + match_table = srealloc(match_table, next_free_num * sizeof(Matching_info)); + conf_table = srealloc(conf_table, next_free_num * sizeof(Conf_no)); + + no_of_match_info = 0; + + match = match_table; + conf = conf_table; + + for ( i = 1; i < next_free_num; i++ ) + { + if ( small_conf_arr[ i ] != NULL + && ! s_empty ( small_conf_arr[ i ]->name ) ) + { + match->name = small_conf_arr[ i ]->name; + match->tokens = tokenize(match->name, s_fcrea_str(" \t")); + match->priority = 7; + *conf = i; + ++match; + ++conf; + ++no_of_match_info; + } + } + + match->name = EMPTY_STRING; + match->tokens = NULL; + match->priority = 0; + *conf = 0; + + return OK; +} + + + +/* Map conference name to number */ +extern Success +cached_lookup_name(const String name, + Conf_list_old *result) +{ + Parse_info tmp; + int i; + + tmp = parse(name, match_table, FALSE, FALSE, + s_fcrea_str(" \t"), DEFAULT_COLLAT_TAB); + + if ( tmp.no_of_matches == -1 ) + return FAILURE; + + if ( tmp.no_of_matches == 1 && tmp.indexes[ 0 ] == -1 ) + { + /* Return the entire list. */ + result->no_of_conf_nos = no_of_match_info; + result->conf_nos = tmp_alloc(no_of_match_info * sizeof(Conf_no)); + result->type_of_conf = tmp_alloc(no_of_match_info * sizeof(Conf_type)); + + for ( i = 0; i < no_of_match_info; i++ ) + { + result->conf_nos[ i ] = conf_table[ i ]; + result->type_of_conf[ i ] = small_conf_arr[ conf_table[i] ]->type; + } + } + else + { + /* Return the conferences whose conf_nos are in indexes[]. */ + result->no_of_conf_nos = tmp.no_of_matches; + result->conf_nos = tmp_alloc(tmp.no_of_matches * sizeof(Conf_no)); + result->type_of_conf = tmp_alloc(tmp.no_of_matches + * sizeof(Conf_type)); + + for ( i = 0; i < tmp.no_of_matches; i++ ) + { + result->conf_nos[ i ] = conf_table[ tmp.indexes[ i ] ]; + result->type_of_conf[ i ] + = small_conf_arr[ conf_table[ tmp.indexes[ i ] ] ]->type; + } + } + sfree(tmp.indexes); + return OK; +} + + +static Bool +is_clean(const char *fn) +{ + FILE *fp; + + if ( (fp = fopen(fn, "rb")) == NULL ) + return FALSE; + + if ( getc(fp) == 'C' && getc(fp) == 'L' && getc(fp) == 'E' + && getc(fp) == 'A' && getc(fp) == 'N' ) + { + fclose(fp); + return TRUE; + } + else + { + fclose(fp); + return FALSE; + } +} + + +extern void +pre_sync(void) +{ + long i; + Cache_node *node; + + async_sync_db(); + + /* Mark up what to save.*/ + + BUG(("Sync starting\n")); + + highest_text_no = next_text_num; + highest_conf_no = next_free_num; + + for ( i = 1; i < highest_conf_no; i++ ) + { + node = conf_arr[i]; + + if ( node != NULL ) + { + node->s.exists_b = node->s.exists; + + if ( node->s.dirty != 0 && node->s.exists != 0) + { + free_conference(node->snap_shot); + + if ( node->lock_cnt == 0 ) + { + unlink_lru(node, &conf_lru, &conf_mru); + node->snap_shot = node->ptr; + node->ptr = NULL; + } + else + { + node->snap_shot = copy_conf(node->ptr); + } + + node->s.dirty = 0; + } + } + } + + for ( i = 1; i < highest_conf_no; i++ ) + { + node = pers_arr[i]; + + if ( node != NULL ) + { + node->s.exists_b = node->s.exists; + + if ( node->s.dirty != 0 && node->s.exists != 0) + { + free_person(node->snap_shot); + + if ( node->lock_cnt == 0 ) + { + unlink_lru(node, &pers_lru, &pers_mru); + node->snap_shot = node->ptr; + node->ptr = NULL; + } + else + { + node->snap_shot + = copy_person(node->ptr); + } + + node->s.dirty = 0; + } + } + } + + for ( i = 1; i < highest_text_no; i++ ) + { + node = text_arr[i]; + + if ( node != NULL ) + { + node->s.exists_b = node->s.exists; + + if ( node->s.dirty != 0 && node->s.exists != 0) + { + free_text_stat(node->snap_shot); + + if ( node->lock_cnt == 0 ) + { + unlink_lru(node, &text_lru, &text_mru); + node->snap_shot = node->ptr; + node->ptr = NULL; + } + else + { + node->snap_shot + = copy_text_stat(node->ptr); + } + + node->s.dirty = 0; + } + } + } + + /* All marking is done. Now open file B. */ + + if ( is_clean(datafilename) ) + { + if ( rename(datafilename, backupfilename) != 0 ) + restart_kom("pre_sync: can't backup.\n"); + } + else + log("pre_sync: datafile not clean. No backup taken.\n"); + + + if ( (file_b=fopen(datafilename, "wb") ) == NULL ) + { + log("WARNING: pre_sync: can't open file to save in.\n"); + sync_state = sync_wait; + + return; + } + + fprintf(file_b, "DIRTY\n"); /* DIRTY-FLAG */ + + fprintf(file_b, "%d\n", highest_conf_no); /* NEXT_FREE_NUM */ + sync_state = sync_save_conf; + sync_next = 1; +} + +static void +copy_file(FILE *from, + FILE *to, + long from_pos, + long len) +{ + char *buf; + long result; + + buf = smalloc(len); + if ( fseek(from, from_pos, SEEK_SET) == -1 ) + { + sync_state = sync_error; + log("sync: copy_file(): fseek failed.\n"); + return; + } + + if ( (result = fread(buf, 1, len, from)) != len ) + { + log("sync: copy_file(): fread failed.\n" + "from_pos = %d, len = %d, result = %d\n", + from_pos, len, result); + + sync_state = sync_error; + return; + } + + if ( fseek(to, 0, SEEK_END) == -1 ) + { + sync_state = sync_error; + log("sync: copy_file(): second fseek failed.\n"); + return; + } + + if ( fwrite(buf, 1, len, to) != len ) + { + sync_state = sync_error; + log("sync: copy_file(): fwrite failed.\n"); + return; + } + + sfree(buf); +} + +static void +save_one_conf(void) +{ + Cache_node *cn; + + if (sync_next < highest_conf_no) + { + cn = conf_arr[ sync_next ]; + + if ( cn == NULL ) + { + putc('@', file_b); + putc('\n', file_b); + } + else + { + cn->pos_b = ftell(file_b); + + if ( cn->s.exists_b == 0 ) + putc('@', file_b); + else if ( cn->snap_shot != NULL ) + { + fprintf(file_b, "+ "); + foutput_conference(file_b, cn->snap_shot); + } + else if ( cn->s.dirty == 0 && cn->ptr != NULL ) + { + fprintf(file_b, "+ "); + foutput_conference(file_b, cn->ptr); + } + else + { + copy_file(file_a, file_b, cn->pos, cn->size - 1); + } + + putc('\n', file_b); + cn->size_b = ftell(file_b) - cn->pos_b; + } + sync_next++; + } + else /* All conferences are written. */ + { + sync_next = 1; + sync_state = sync_save_pers; + } +} + +static void +write_pers(FILE *fp, + Person *p) +{ + fprintf(fp, "+ %dH", PASSWD_LEN); + fwrite(p->pwd, PASSWD_LEN, 1, fp); + foutput_person(fp, p); +} + +static void +save_one_pers(void) +{ + Cache_node *cn; + + if (sync_next < highest_conf_no) + { + cn = pers_arr[ sync_next ]; + + if ( cn == NULL ) + { + putc('@', file_b); + putc('\n', file_b); + } + else + { + cn->pos_b = ftell(file_b); + + if ( cn->s.exists_b == 0 ) + putc('@', file_b); + else if ( cn->snap_shot != NULL ) + { + write_pers(file_b, cn->snap_shot); + } + else if ( cn->s.dirty == 0 && cn->ptr != NULL ) + { + write_pers(file_b, cn->ptr); + } + else + { + copy_file(file_a, file_b, cn->pos, cn->size - 1); + } + + putc('\n', file_b); + cn->size_b = ftell(file_b) - cn->pos_b; + } + sync_next++; + } + else /* All persons are written. */ + { + fprintf(file_b, "%d\n", highest_text_no); /* NEXT_TEXT_NUM */ + sync_next = 1; + sync_state = sync_save_text; + } +} + +static void +post_sync(void) +{ + int i; + Cache_node *node; + + async_sync_db(); + + if ( file_a == NULL ) + log("WARNING: post_sync(): file_a == NULL. This is only normal " + "if this is the first sync ever on this data file.\n"); + else + fclose(file_a); + + if ( ( file_a = fopen(datafilename, "rb") ) == NULL ) + { + log("post_sync: can't open the file I just saved.\n"); + sync_state = sync_wait; + return; + } + + for ( i = 1; i < highest_conf_no; i++ ) + { + node = conf_arr[i]; + if ( node != NULL ) + { + node->pos = node->pos_b; + node->size = node->size_b; + free_conference(node->snap_shot); + node->snap_shot = NULL; + } + } + + for ( i = 1; i < highest_conf_no; i++ ) + { + node = pers_arr[i]; + if ( node != NULL ) + { + node->pos = node->pos_b; + node->size = node->size_b; + free_person(node->snap_shot); + node->snap_shot = NULL; + } + } + + for ( i = 1; i < highest_text_no; i++ ) + { + node = text_arr[i]; + if ( node != NULL ) + { + node->pos = node->pos_b; + node->size = node->size_b; + free_text_stat(node->snap_shot); + node->snap_shot = NULL; + } + } +} + + + +static void +save_one_text(void) +{ + Cache_node *cn; + + if (sync_next < highest_text_no) + { + cn = text_arr[ sync_next ]; + + if ( cn == NULL ) + { + putc('@', file_b); + putc('\n', file_b); + } + else + { + cn->pos_b = ftell(file_b); + + if ( cn->s.exists_b == 0 ) + putc('@', file_b); + else if ( cn->snap_shot != NULL ) + { + fprintf(file_b, "+ "); + foutput_text_stat(file_b, cn->snap_shot); + } + else if ( cn->s.dirty == 0 && cn->ptr != NULL ) + { + fprintf(file_b, "+ "); + foutput_text_stat(file_b, cn->ptr); + } + else + { + copy_file(file_a, file_b, cn->pos, cn->size - 1); + } + + putc('\n', file_b); + cn->size_b = ftell(file_b) - cn->pos_b; + } + sync_next++; + } + else /* All texts are written. */ + { + if ( ferror(file_b) != 0 ) + { + sync_state = sync_error; + return; + } + + rewind(file_b); + if ( ferror(file_b) != 0 ) + { + sync_state = sync_error; + return; + } + + fprintf(file_b, "CLEAN"); + if ( ferror(file_b) != 0 ) + { + sync_state = sync_error; + return; + } + + if ( fclose(file_b) != 0 ) + { + file_b = NULL; + + log("Sync: fclose() failed in save_one_text. Deleting " + "file and retrying.\n"); + remove(datafilename); + sync_state = sync_wait; + return; + } + + sync_state = sync_ready; + + BUG(("Sync ready\n")); + post_sync(); + } +} + +/* + * Sync_part() should be called often as long as it returns FALSE. + * Sync_part() returns TRUE when everything is written to a file. + * Sync_part() should be called once in a while even when it returns + * TRUE (because sync() checks to see if it is time to start yet + * another save.) + */ +EXPORT Bool +sync_part(void) +{ + static time_t last_sync_start = NO_TIME; + + if ( last_sync_start == NO_TIME ) + { + last_sync_start = time(NULL); + sync_state = sync_ready; + } + + switch(sync_state) + { + case sync_save_conf: + save_one_conf(); + break; + + case sync_save_pers: + save_one_pers(); + break; + + case sync_save_text: + save_one_text(); + break; + + case sync_ready: + if ( difftime(time(NULL), last_sync_start) < 60 * SYNC_INTERVAL ) + return TRUE; + + last_sync_start = time(NULL); + + pre_sync(); + break; + + case sync_wait: + if ( difftime(time(NULL), last_sync_start) < 60 * SYNC_RETRY_INTERVAL ) + return TRUE; + + last_sync_start = time(NULL); + + pre_sync(); + break; + + case sync_error: + log("sync: Error saving new file. Discarding file and " + "trying again.\n"); + fclose(file_b); + file_b = NULL; + remove(datafilename); + sync_state = sync_wait; + break; + + default: + restart_kom("sync(): sync_state==%d", sync_state); + } + + if ( ferror(file_b) != 0) + sync_state = sync_error; + + return FALSE; +} + +static Small_conf * +alloc_small_conf(void) +{ + Small_conf *s; + s = smalloc(sizeof(Small_conf)); + *s = EMPTY_SMALL_CONF; + + return s; +} + +static void +setup_small_conf(Conf_no conf_no, + Conference *conf_c) +{ + small_conf_arr[ conf_no ] = alloc_small_conf(); + s_strcpy(&small_conf_arr[ conf_no ]->name, conf_c->name); + small_conf_arr[ conf_no ]->type = conf_c->type; + small_conf_arr[ conf_no ]->highest_local_no + = (conf_c->texts.first_local_no - 1 + + conf_c->texts.no_of_texts ); + small_conf_arr[ conf_no ]->nice = conf_c->nice; +} + + + +extern Success +init_cache(void) +{ + int i; + Cache_node *node; + Conference tmp_conf = EMPTY_CONFERENCE; + Person tmp_pers = EMPTY_PERSON; + Text_stat tmp_text = EMPTY_TEXT_STAT; + + if ( (text_file = fopen(textfilename, "a+b")) == NULL ) + { + restart_kom("ERROR: init_cache: can't open text file.\n"); + } + + if ( is_clean(datafilename) ) + { + if ( (file_a = fopen(datafilename, "rb")) == NULL ) + { + log("WARNING: init_cache: can't open datafile.\n"); + return FAILURE; + } + log("MSG: init_cache: using datafile.\n"); + } + else if ( is_clean(backupfilename) ) + { + if ( (file_a = fopen(backupfilename, "rb")) == NULL ) + { + log("WARNING: init_cache: can't open backupfile.\n"); + return FAILURE; + } + log("MSG: init_cache: using backup file.\n"); + } + else + { + log("WARNING: init_cache: can't find old data base.\n"); + return FAILURE; + } + + fseek(file_a, 6, SEEK_SET); /* skip clean/dirty flag. */ + + next_free_num = fparse_long(file_a); /* NEXT_FREE_NUM */ + + for ( i = 1; i < next_free_num; i++ ) /* CONFS */ + { + fskipwhite(file_a); + switch(getc(file_a)) + { + case '@': + conf_arr[ i ] = NULL; + break; + + case '+': + node = conf_arr[ i ] = alloc_cache_node(); + node->s.exists = 1; + + node->pos = ftell(file_a) - 1; /* Don't forget the '+' */ + + if ( fparse_conference(file_a, &tmp_conf) != OK ) + restart_kom("init_cache(): fparse_conference failed. " + "i == %d\n", i); + + node->size = ftell(file_a) - node->pos; + setup_small_conf(i, &tmp_conf); + clear_conference(&tmp_conf); + + break; + } + } + + build_matching_info(); + + for ( i = 1; i < next_free_num; i++ ) /* PERSONS */ + { + fskipwhite(file_a); + switch(getc(file_a)) + { + case '@': + pers_arr[ i ] = NULL; + break; + + case '+': + node = pers_arr[ i ] = alloc_cache_node(); + node->s.exists = 1; + node->pos = ftell(file_a) - 1; /* Don't forget the '+' */ + if ( fparse_person(file_a, &tmp_pers) != OK ) + restart_kom("init_cache: fparse_person failed. i == %d\n", i); + + node->size = ftell(file_a) - node->pos; + clear_person(&tmp_pers); + + break; + } + } + + next_text_num = fparse_long(file_a); /* NEXT_TEXT_NUM */ + + for ( i = 1; i < next_text_num; i++ ) /* TEXT_STATS */ + { + fskipwhite(file_a); + switch(getc(file_a)) + { + case '@': + text_arr[ i ] = NULL; + break; + + case '+': + node = text_arr[ i ] = alloc_cache_node(); + node->s.exists = 1; + + node->pos = ftell(file_a) - 1; /* Don't forget the '+' */ + + if ( fparse_text_stat(file_a, &tmp_text) != OK ) + restart_kom("init_cache(): fparse_text_stat failed. i == %d\n", + i); + + node->size = ftell(file_a) - node->pos; + clear_text_stat(&tmp_text); + + break; + } + } + + log("Read %d confs/persons and %d texts\n", + next_free_num, next_text_num); + + return OK; +} + +/* ramkomd compatibility: */ +extern void +cache_sync(void) +{ +} + +extern void +cache_sync_all(void) +{ + pre_sync(); + while ( sync_part() != TRUE ) + ; +} diff --git a/src/server/text-garb.c b/src/server/text-garb.c new file mode 100644 index 000000000..113ed41af --- /dev/null +++ b/src/server/text-garb.c @@ -0,0 +1,125 @@ +/* + * This file contains the functions that deletes old texts. + * + * Author: Per Cederqvist. + */ + +#include <time.h> +#include <kom-types.h> +#include "cache.h" +#include "log.h" +#include "internal-services.h" +#include "lyskomd.h" +#include <debug.h> + + +/* + * Delete old texts. + * + * Return value is TRUE if there is nothing more to do right now, + * FALSE if this function should be called again as soon as the + * server has some time over. + */ + +Bool +garb_text(void) +{ + static Text_no last_checked = 0; + static time_t last_start = NO_TIME; + static long deleted_texts = 0; + BUGDECL; + + Text_stat *text_s; + Misc_info *misc; + u_short nmisc; + double age; /* How many seconds is this text? */ + double limit = 24 * 3600; + + if ( last_checked == 0 ) + { + if ( last_start != NO_TIME + && difftime(time(NULL), last_start) < GARB_INTERVAL * 60 ) + { + return TRUE; + } + + log("MSG: garb started.\n"); + time( &last_start ); + } + + last_checked = traverse_text( last_checked ); + + if ( last_checked == 0 ) + { + log("MSG: garb ready. %lu texts deleted.\n", (u_long)deleted_texts); + return FALSE; + } + + if ( (text_s = cached_get_text_stat( last_checked )) == NULL ) + { + log("ERROR: garb_text(): Can't get text-stat.\n"); + return FALSE; + } + + age = difftime( time(NULL), text_s->creation_time ); + + if ( text_s->no_of_marks > 0 || age < 24 * 3600 ) + { + return FALSE; + } + + for (nmisc = text_s->no_of_misc, misc = text_s->misc_items; + nmisc > 0; + --nmisc, ++misc ) + { + switch ( misc->type ) + { + case recpt: + limit = 24 * 3600 * cached_get_garb_nice( misc->datum.recipient ); + if ( age < limit ) + return FALSE; + + break; + + case cc_recpt: + limit = ( 24 * 3600 + * cached_get_garb_nice( misc->datum.cc_recipient )); + if ( age < limit ) + return FALSE; + + break; + + case comm_to: + case comm_in: + case footn_to: + case footn_in: + limit = 24 * 3600; + break; + + case loc_no: + case rec_time: + case sent_by: + break; + + case sent_at: + if ( difftime ( time(NULL), misc->datum.sent_at ) + < limit ) + { + return FALSE; + } + + break; + +#ifndef COMPILE_CHECKS + default: + restart_kom("garb_text(): Illegal misc-item.\n"); +#endif + } + } + + VBUG(("garb_text: deleting %d\n", last_checked)); + do_delete_text ( last_checked, text_s ); + deleted_texts++; + return FALSE; +} + diff --git a/src/server/text-garb.h b/src/server/text-garb.h new file mode 100644 index 000000000..a2a9d880d --- /dev/null +++ b/src/server/text-garb.h @@ -0,0 +1,3 @@ +extern Bool +garb_text(void); + diff --git a/src/server/text.c b/src/server/text.c new file mode 100644 index 000000000..e8f519f9f --- /dev/null +++ b/src/server/text.c @@ -0,0 +1,2103 @@ +/* + * text.c + * + * All atomic calls that deals with texts. + */ +#include <time.h> +#include <stdlib.h> +#include "lyskomd.h" +#include <kom-types.h> +#include <services.h> +#include "manipulate.h" +#include <kom-errno.h> +#include "smalloc.h" +#include "cache.h" +#include "log.h" +#include "minmax.h" +#include <config.h> +#include "com.h" +#include "isc.h" +#include "connections.h" +#include "send-async.h" +#include "admin.h" + +/* + * Static functions + */ + + +/* + * Add text_no to the list of texts in a conference. Return the local number + * the text gets in this conference. + */ + +static Local_text_no +add_text_in_conf(Conference * conf_c, + Text_no text_no) +{ + Text_list * t; + + time( & conf_c->last_written ); + + /* Add number last on the text_list */ + t = &(conf_c->texts); + t->texts = srealloc(t->texts, ++(t->no_of_texts) * sizeof(Text_no)); + t->texts[ t->no_of_texts - 1 ] = text_no; + + return t->first_local_no + t->no_of_texts - 1; +} + + +/* + * Set the global text_no of a certain local text_no if that local text_no + * exists in the conf. No action is taken if the local text_no doesn't exist. + * This function is probably only used to set the number to zero when a text + * is deleted. + */ + +static void +set_loc_no (Conference * conf_c, + Local_text_no loc_no, + Text_no text_no) +{ + if ( loc_no < conf_c->texts.first_local_no + || loc_no > conf_c->texts.first_local_no + conf_c->texts.no_of_texts ) + { + return; /* Doesn't exist in conf.stat. */ + } + + conf_c->texts.texts[ loc_no - conf_c->texts.first_local_no ] = text_no; + + return; +} +/* + * Count how many recipients and cc_recipients a text has. + */ + +static int +count_recipients( Text_stat *t_stat ) +{ + int n = 0; + Misc_info * misc; + + for ( misc = t_stat->misc_items; + misc < t_stat->misc_items + t_stat->no_of_misc; misc++ ) + { + if ( misc->type == recpt || misc->type == cc_recpt ) + { + n++; + } + } + + return n; +} + +/* + * Count how many footnotes a text has. + */ + +static int +count_footn( Text_stat *t_stat ) +{ + int n = 0; + Misc_info * misc; + + for ( misc = t_stat->misc_items; + misc < t_stat->misc_items + t_stat->no_of_misc; misc++ ) + { + if ( misc->type == footn_in ) + { + n++; + } + } + + return n; +} + +/* + * Count how many commments a text has. + */ + +static int +count_comment( Text_stat *t_stat ) +{ + int n = 0; + Misc_info * misc; + + for ( misc = t_stat->misc_items; + misc < t_stat->misc_items + t_stat->no_of_misc; misc++ ) + { + if ( misc->type == comm_in ) + { + n++; + } + } + + return n; +} + +/* + * Check if ACTPERS is allowed to read this text. + * Returns TRUE if he is allowed to read it. + */ + + +/* + * Check if CONF_NO is a recipient of the text whose text_stat is given. + */ + +static Bool +is_recipient(Conf_no conf_no, + Text_stat * t_stat) +{ + int i; + + for ( i = 0; i < t_stat->no_of_misc; i++ ) + { + switch( t_stat->misc_items[ i ].type ) + { + case recpt: + if ( t_stat->misc_items[ i ].datum.recipient == conf_no ) + { + return TRUE; + } + break; + + case cc_recpt: + if ( t_stat->misc_items[ i ].datum.cc_recipient == conf_no ) + { + return TRUE; + } + break; + + case rec_time: + case comm_to: + case comm_in: + case footn_to: + case footn_in: + case sent_by: + case sent_at: + case loc_no: + break; + +#ifndef COMPILE_CHECKS + default: + restart_kom("is_recipient(): illegal misc_item\n"); +#endif + } + } + + return FALSE; +} + + +/* + * Check if comment is a comment to parent. + */ +static Bool +is_comment_to(Text_no comment, + Text_stat *parent) +{ + int i; + + for ( i = 0; i < parent->no_of_misc; i++ ) + { + switch( parent->misc_items[ i ].type ) + { + case comm_in: + if ( parent->misc_items[ i ].datum.commented_in == comment ) + return TRUE; + break; + default: + break; + } + } + + return FALSE; +} + +/* + * Check if footnote is a footnote to parent. + */ +static Bool +is_footnote_to(Text_no footnote, + Text_stat *parent) +{ + int i; + + for ( i = 0; i < parent->no_of_misc; i++ ) + { + switch( parent->misc_items[ i ].type ) + { + case footn_in: + if ( parent->misc_items[ i ].datum.footnoted_in == footnote ) + return TRUE; + break; + default: + break; + } + } + + return FALSE; +} +/* + * Return the conference which the text goes to. This is normally conf_no, but + * it may be a super_conf. If ACTPERS is not allowed to submit a text to + * conf_no or a super_conf, return 0. + */ +static Conf_no +submit_to(Conf_no conf_no, /* The conference the user is trying to */ + /* submit a text to. */ + Conference * conf_c) /* May be NULL */ +{ + int i; + Access acc; + + CHK_LOGIN(0); + + if ( conf_c == NULL ) + GET_C_STAT(conf_c, conf_no, 0); + + for ( i=0; i < MAX_SUPER_CONF_LOOP; i++) + { + acc = access_perm( conf_no, conf_c ); + + if ( acc <= none ) + return 0; + + if (conf_c->permitted_submitters == 0 + || acc == unlimited + || locate_membership( conf_c->permitted_submitters, ACT_P) != NULL) + { + return conf_no; + } + + + if ((conf_no = conf_c->super_conf) == 0) + return 0; + + GET_C_STAT(conf_c, conf_no, 0); + } + + return 0; +} + + +/* + * Say that FOOTNOTE is a footnote to TEXT. + */ + +static Success +do_add_footnote(Text_no footnote, + Text_no text) +{ + Text_stat *foot_s, *text_s; + + GET_T_STAT(foot_s, footnote, FAILURE); + GET_T_STAT(text_s, text, FAILURE); + + ADD_MISC(foot_s, footn_to, footnote_to, text); + ADD_MISC(text_s, footn_in, footnoted_in, footnote); + + mark_text_as_changed( footnote ); + mark_text_as_changed( text ); + + return OK; +} + + +/* + * Say that COMMENT is a comment to TEXT. + */ + +static Success +do_add_comment(Text_no comment, + Text_no text) +{ + Text_stat *comm_s, *text_s; + + GET_T_STAT(comm_s, comment, FAILURE); + GET_T_STAT(text_s, text, FAILURE); + + ADD_MISC(comm_s, comm_to, comment_to, text); + ADD_MISC(text_s, comm_in, commented_in, comment); + + mark_text_as_changed( comment ); + mark_text_as_changed( text ); + + return OK; +} + + +/* + * Say that RECEIVER is a recipient of TEXT. + */ +static Success +do_add_recpt(Text_no text, + Text_stat * text_s, /* May be NULL */ + Conf_no receiver) +{ + Conference *rece_c; + + if ( text_s == NULL ) + { + GET_T_STAT(text_s, text, FAILURE); + } + + GET_C_STAT(rece_c, receiver, FAILURE); + + ADD_MISC(text_s, recpt, recipient, receiver); + ADD_MISC(text_s, loc_no, local_no, add_text_in_conf(rece_c, text)); + + mark_text_as_changed(text); + mark_conference_as_changed(receiver); + + return OK; +} + + +static Success +do_add_cc_recpt(Text_no new_text, + Text_stat *text_s, /* May be NULL */ + Conf_no receiver) +{ + Conference *rece_c; + + if ( text_s == NULL ) + GET_T_STAT(text_s, new_text, FAILURE); + + GET_C_STAT(rece_c, receiver, FAILURE); + + ADD_MISC(text_s, cc_recpt, cc_recipient, receiver); + ADD_MISC(text_s, loc_no, local_no, add_text_in_conf(rece_c, new_text)); + + mark_text_as_changed( new_text ); + mark_conference_as_changed( receiver ); + + return OK; +} + + +/* + * Return number of lines in a text + */ + +static u_short +count_lines( String str ) +{ + u_short l = 0; + + while ( str.len-- > 0 ) + { + if( *str.string++ == '\n' ) + { + l++; + } + } + + return l; +} + + +/* + * Delete misc_info at location loc. + * If it is a recpt, cc_recpt, comm_to or footn_to delete any + * loc_no, rec_time, sent_by or sent_at that might follow it. + * + * Note that the Misc_info is not reallocated. + */ + +static void +do_delete_misc (u_short * no_of_misc, + Misc_info * misc, + int loc) +{ + int del = 1; /* Number of items to delete. */ + /* Always delete at least one item. */ + Bool ready; + + /* Check range of loc */ + + if ( loc < 0 || loc >= *no_of_misc ) + { + restart_kom("do_delete_misc() - loc out of range"); + } + + ready = FALSE; + + while ( ready == FALSE && loc + del < *no_of_misc ) + { + switch ( misc[ loc + del ].type ) + { + case loc_no: + case rec_time: + case sent_by: + case sent_at: + del++; + break; + + case recpt: + case cc_recpt: + case footn_to: + case footn_in: + case comm_to: + case comm_in: + ready = TRUE; + break; + +#ifndef COMPILE_CHECKS + default: + restart_kom("do_delete_misc() - illegal misc"); +#endif + } + } + + *no_of_misc -= del; + + /* Move items beyond the deleted ones. */ + + while ( loc < *no_of_misc ) + { + misc[ loc ] = misc[ loc + del ]; + loc++; + } +} + +/* + * Delete leading zeroes from the text_list. + * Returns TRUE if some zeroes was deleted. + */ +static Bool +adjust_text_list(Text_list *text_list) +{ + u_long zeroes; + u_long i; + + for (zeroes = 0; + zeroes < text_list->no_of_texts && text_list->texts[ zeroes ] == 0; + zeroes++) + ; + + if ( zeroes > 0 ) + { + text_list->no_of_texts -= zeroes; + text_list->first_local_no += zeroes; + + for ( i = 0; i < text_list->no_of_texts; i++) + text_list->texts[ i ] = text_list->texts[ i + zeroes ]; + + text_list->texts = srealloc(text_list->texts, + (text_list->no_of_texts + * sizeof(Text_no))); + } + + return zeroes > 0; +} + + +/* + * Delete a recipient from a text. Does not fail if the recipient doesn't + * exist - that is not an error. + */ +static Success +do_sub_recpt (Text_no text_no, + Text_stat * text_s, /* May be NULL */ + Conf_no conf_no, + Conference * conf_s ) /* May be NULL */ +{ + int i; + + if ( text_s == NULL ) + GET_T_STAT(text_s, text_no, FAILURE); + + if ( conf_s == NULL ) + { + conf_s = cached_get_conf_stat( conf_no ); /* Might still be NULL. */ + } + + for ( i = 0; i < text_s->no_of_misc; i++ ) + { + switch ( text_s->misc_items[ i ].type) + { + case recpt: + if ( text_s->misc_items[ i ].datum.recipient == conf_no ) + { + if ( conf_s != NULL ) + { + /* Only if the conference exists: */ + set_loc_no( conf_s, + text_s->misc_items[ i+1 ].datum.local_no, 0 ); + adjust_text_list (&conf_s->texts); + mark_conference_as_changed (conf_no); + } + + do_delete_misc ( &text_s->no_of_misc, text_s->misc_items, i ); + mark_text_as_changed (text_no); + + return OK; + } + break; + + case cc_recpt: + if ( text_s->misc_items[ i ].datum.cc_recipient == conf_no ) + { + if ( conf_s != NULL ) + { + set_loc_no( conf_s, + text_s->misc_items[ i+1 ].datum.local_no, 0 ); + mark_conference_as_changed( conf_no ); + } + + do_delete_misc( &text_s->no_of_misc, text_s->misc_items, i ); + mark_text_as_changed( text_no ); + + return OK; + } + break; + + case comm_to: + case comm_in: + case footn_to: + case footn_in: + case loc_no: + case rec_time: + case sent_by: + case sent_at: + break; + +#ifndef COMPILE_CHECKS + default: + log(__FILE__ ": do_sub_recpt(): bad misc_item.\n"); + break; +#endif + } + } + + kom_errno = KOM_NOT_RECIPIENT; + return FAILURE; +} + +/* + * Delete the link between comment and comment_to. + */ +static void +do_sub_comment (Text_no comment, /* The comment. */ + Text_stat * text_s, + Text_no comment_to, /* The commented. */ + Text_stat * parent_s ) +{ + int i; + Bool ready; + + if ( text_s == NULL ) + GET_T_STAT(text_s, comment, (void)0); + + if ( parent_s == NULL ) + GET_T_STAT(parent_s, comment_to, (void)0); + + for ( ready = FALSE, i = 0; !ready && i < text_s->no_of_misc; i++ ) + { + switch ( text_s->misc_items[ i ].type) + { + case comm_to: + if ( text_s->misc_items[ i ].datum.comment_to == comment_to ) + { + do_delete_misc( &text_s->no_of_misc, text_s->misc_items, i ); + mark_text_as_changed( comment ); + ready = TRUE; + } + break; + + default: + break; + } + } + + if ( !ready ) + restart_kom("do_sub_comment(): part 1 failed.\n"); + + + for ( ready = FALSE, i = 0; !ready && i < parent_s->no_of_misc; i++ ) + { + switch ( parent_s->misc_items[ i ].type) + { + case comm_in: + if ( parent_s->misc_items[ i ].datum.commented_in == comment ) + { + do_delete_misc (&parent_s->no_of_misc, + parent_s->misc_items, i ); + mark_text_as_changed( comment_to ); + ready = TRUE; + } + break; + + default: + break; + } + } + + if ( !ready ) + restart_kom("do_sub_comment(): part 2 failed.\n"); + + return; +} + + +/* + * Delete the link between footnote and footnote_to. + */ +static void +do_sub_footnote (Text_no footnote, + Text_stat * text_s, + Text_no footnote_to, + Text_stat * parent_s ) +{ + int i; + Bool ready; + + if ( text_s == NULL ) + GET_T_STAT(text_s, footnote, (void)0); + + if ( parent_s == NULL ) + GET_T_STAT(parent_s, footnote_to, (void)0); + + for ( ready = FALSE, i = 0; !ready && i < text_s->no_of_misc; i++ ) + { + switch ( text_s->misc_items[ i ].type) + { + case footn_to: + if ( text_s->misc_items[ i ].datum.footnote_to == footnote_to ) + { + do_delete_misc( &text_s->no_of_misc, text_s->misc_items, i ); + mark_text_as_changed( footnote ); + ready = TRUE; + } + break; + + default: + break; + } + } + + if ( !ready ) + restart_kom("do_sub_footnote(): part 1 failed.\n"); + + + for ( ready = FALSE, i = 0; !ready && i < parent_s->no_of_misc; i++ ) + { + switch ( parent_s->misc_items[ i ].type) + { + case footn_in: + if ( parent_s->misc_items[ i ].datum.footnoted_in == footnote ) + { + do_delete_misc (&parent_s->no_of_misc, + parent_s->misc_items, i ); + mark_text_as_changed( footnote_to ); + ready = TRUE; + } + break; + + default: + break; + } + } + + if ( !ready ) + restart_kom("do_sub_footnote(): part 2 failed.\n"); + + return; +} + + +/* + * Who is sender of misc_item I? Returns 0 if there is no sent_by misc_item. + */ +static Pers_no +sender(Text_stat * t_stat, + int i) +{ + for (i++ ; i < t_stat->no_of_misc; i++) + { + switch ( t_stat->misc_items[ i ].type ) + { + case sent_by: + return t_stat->misc_items[ i ].datum.sender; + + case recpt: + case cc_recpt: + case comm_to: + case comm_in: + case footn_to: + case footn_in: + return 0; /* No sender. */ + + case loc_no: + case rec_time: + break; /* These may come before a sent_by. */ + + case sent_at: + log("ERROR: sender(): Misc_item out of order.\n"); + return 0; + +#ifndef COMPILE_CHECKS + default: + log("ERROR: sender(): Illegal misc_item found.\n"); + return 0; +#endif + } + } + return 0; /* No sender. */ +} + + +/* + * Check if ACTPERS has sent this text to conference CONF_NO + */ +static Bool +is_sender(Text_stat * text_s, + Conf_no conf_no) +{ + int i; + + if ( !ACTPERS ) + { + return FALSE; + } + + for ( i = 0; i < text_s->no_of_misc; i++ ) + { + switch( text_s->misc_items[ i ].type ) + { + case recpt: + if ( text_s->misc_items[ i ].datum.recipient == conf_no + && sender ( text_s, i ) == ACTPERS ) + { + return TRUE; + } + break; + + case cc_recpt: + if ( text_s->misc_items[ i ].datum.cc_recipient == conf_no + && sender ( text_s, i ) == ACTPERS ) + { + return TRUE; + } + break; + + case loc_no: + case rec_time: + case comm_to: + case comm_in: + case footn_to: + case footn_in: + case sent_by: + case sent_at: + break; + +#ifndef COMPILE_CHECKS + default: + restart_kom("is_sender(): Illegal misc_item found.\n"); +#endif + } + } + + return FALSE; +} + + +/* + * Check if ACTPERS has sent this text as a comment to parent. + */ +static Bool +is_comm_sender(Text_stat * text_s, + Text_no parent) +{ + int i; + + if ( !ACTPERS ) + { + return FALSE; + } + + for ( i = 0; i < text_s->no_of_misc; i++ ) + { + switch( text_s->misc_items[ i ].type ) + { + case comm_to: + if ( text_s->misc_items[ i ].datum.comment_to == parent + && sender ( text_s, i ) == ACTPERS ) + { + return TRUE; + } + break; + + + case recpt: + case cc_recpt: + case loc_no: + case rec_time: + case comm_in: + case footn_to: + case footn_in: + case sent_by: + case sent_at: + break; + +#ifndef COMPILE_CHECKS + default: + restart_kom("is_comm_sender(): Illegal misc_item found.\n"); +#endif + } + } + + return FALSE; +} + +#if 0 +/* + * Check if ACTPERS has sent this text as a footnote to parent. + */ +static Bool +is_footn_sender(Text_stat * text_s, + Text_no parent) +{ + int i; + + if ( !ACTPERS ) + { + return FALSE; + } + + for ( i = 0; i < text_s->no_of_misc; i++ ) + { + switch( text_s->misc_items[ i ].type ) + { + case footn_to: + if ( text_s->misc_items[ i ].datum.footnote_to == parent + && sender ( text_s, i ) == ACTPERS ) + { + return TRUE; + } + break; + + + case recpt: + case cc_recpt: + case loc_no: + case rec_time: + case comm_in: + case comm_to: + case footn_in: + case sent_by: + case sent_at: + break; + +#ifndef COMPILE_CHECKS + default: + restart_kom("is_footn_sender(): Illegal misc_item found.\n"); +#endif + } + } + + return FALSE; +} +#endif + + +/* + * Check if ACTPERS is allowed to add a footnote to a text. Sets errno if + * there is an error. + */ + +static Success +check_footn(Text_stat * t_stat) +{ + if ( t_stat->author != ACTPERS ) + { + kom_errno = KOM_NOT_AUTHOR; + return FAILURE; + } + + if ( count_footn( t_stat ) >= MAX_FOOT ) + { + kom_errno = KOM_FOOT_LIMIT; + return FAILURE; + } + + return OK; +} + +/* + * Check if ACTPERS is allowed to add a comment to a text. Sets errno if + * there is an error. Note that it is allowed to comment a text even if + * you are not allowed to read it. + */ + +static Success +check_comm(Text_stat * t_stat) +{ + if ( count_comment( t_stat ) >= MAX_COMM ) + { + kom_errno = KOM_COMM_LIMIT; + return FAILURE; + } + + return OK; +} + + +/* + * Return a pointer to a Mark if pers_no has marked the text text_no. + * Otherwise, return NULL. + */ +static Mark * +locate_mark(Pers_no pers_no, + Person *pers_p, /* May be NULL. */ + Text_no text_no) +{ + Mark *mp; + Mark *result = NULL; + int i; + Mark_list mlist; + + if ( pers_p == NULL ) + GET_P_STAT(pers_p, pers_no, NULL); + + mlist = pers_p->marks; + + for ( i = mlist.no_of_marks, mp = mlist.marks; + i > 0 && result == NULL; + i--, mp++ ) + { + if ( mp->text_no == text_no ) + result = mp; + } + + return result; +} + +/* + * Skip one misc-item with all additional data. + * + * This is only used from get_text_stat. + */ +static void +skip_recp (Misc_info ** misc, + Text_stat * text_stat ) +{ + Bool ready = FALSE; + + ++(*misc); + while ( !ready && *misc < text_stat->misc_items + text_stat->no_of_misc ) + { + switch ( (*misc)->type ) + { + case loc_no: + case rec_time: + case sent_by: + case sent_at: + ++(*misc); + break; + + case recpt: + case cc_recpt: + case comm_to: + case comm_in: + case footn_to: + case footn_in: + ready = TRUE; + break; +#ifndef COMPILE_CHECKS + default: + restart_kom("skip_recp() - illegal misc\n"); +#endif + } + } +} + +/* + * End of static functions. + */ + +/* + * Functions that are exported to the rest of the server. + */ + +/* + * Check if ACTPERS is allowed to read this text. + * Returns TRUE if he is allowed to read it. + */ +Bool +text_read_access(Text_no text_no, + Text_stat * text_stat) +{ + int i; + Misc_info *misc; + Conference *recipient; + + /* + * Nope, people who aren't logged in, should NOT be + * allowed to read any texts but motd_of_lyskom! + */ + if ( text_no == kom_info.motd_of_lyskom ) + return TRUE; + + CHK_LOGIN(FALSE); + + if ( text_stat == NULL ) + GET_T_STAT(text_stat, text_no, FALSE); + + if ( ENA(wheel, 10)) + return TRUE; + + if ( ACTPERS ) + { + if ( text_stat->author == ACTPERS ) + return TRUE; + + /* Check if ACTPERS or current working conference is a recipient */ + + for (i = text_stat->no_of_misc, misc = text_stat->misc_items; + i; i--, misc++) + { + if ( misc->type == recpt + && (misc->datum.recipient == active_connection->cwc + || misc->datum.recipient == ACTPERS )) + { + return TRUE; + } + + if ( misc->type == cc_recpt + && (misc->datum.cc_recipient == active_connection->cwc + || misc->datum.cc_recipient == ACTPERS )) + { + return TRUE; + } + } + + /* Check if ACTPERS is member in any of the recipients */ + + for (i = text_stat->no_of_misc, misc = text_stat->misc_items; + i; i--, misc++) + { + if ( misc->type == recpt + && locate_membership( misc->datum.recipient, ACT_P) != NULL) + { + return TRUE; + } + + if ( misc->type == cc_recpt + && locate_membership( misc->datum.cc_recipient, ACT_P) != NULL) + { + return TRUE; + } + } + + if ( locate_mark(ACTPERS, ACT_P, text_no) != NULL ) + { + return TRUE; + } + } + + /* Check if any of the recipients is an open conference, */ + /* or if ACTPERS is a supervisor. (Note: ACTPERS is not */ + /* supervisor of anything if he isn't logged in.) */ + + for (i = text_stat->no_of_misc, misc = text_stat->misc_items; + i; i--, misc++) + { + if ( misc->type == recpt + && (recipient = + cached_get_conf_stat( misc->datum.recipient )) != NULL ) + { + if ( !recipient->type.rd_prot + || is_supervisor(misc->datum.recipient, recipient)) + { + return TRUE; + } + } + + if ( misc->type == cc_recpt + && (recipient = + cached_get_conf_stat( misc->datum.cc_recipient )) != NULL ) + { + if ( !recipient->type.rd_prot + || is_supervisor(misc->datum.cc_recipient, recipient)) + { + return TRUE; + } + } + } + + return FALSE; +} + + +/* + * Delete a text from the database. Deletes all "links" to/from other + * texts/persons/conferences. (e. g. comm_to/comm_in) + */ +Success +do_delete_text(Text_no text_no, + Text_stat *text_s) +{ + Person *author; + Bool found = FALSE; + u_long i; + + if ( text_s == NULL ) + GET_T_STAT(text_s, text_no, FAILURE); + + if ((author = cached_get_person_stat (text_s->author)) != NULL) + { + /* Delete this text from created_texts. */ + + for ( i = 0; i < author->created_texts.no_of_texts; i++ ) + if ( author->created_texts.texts[ i ] == text_no ) + { + author->created_texts.texts[ i ] = 0; + adjust_text_list (&author->created_texts); + mark_person_as_changed (text_s->author); + found = TRUE; + break; + } + + if ( !found ) + log("ERROR: " __FILE__ ": do_delete_text(): text not found " + "in authors created_texts."); + } + + while ( text_s->no_of_misc > 0 ) + { + switch ( text_s->misc_items[ 0 ].type ) + { + case recpt: + if ( do_sub_recpt(text_no, text_s, + text_s->misc_items[ 0 ].datum.recipient, NULL) + != OK ) + restart_kom("do_delete_text(): error pos 1.\n"); + break; + + case cc_recpt: + if ( do_sub_recpt(text_no, text_s, + text_s->misc_items[ 0 ].datum.cc_recipient, NULL) + != OK ) + restart_kom("do_delete_text(): error pos 2.\n"); + break; + + case comm_to: + do_sub_comment(text_no, text_s, + text_s->misc_items[ 0 ].datum.comment_to, NULL); + break; + + case comm_in: + do_sub_comment(text_s->misc_items[ 0 ].datum.commented_in, NULL, + text_no, text_s); + break; + + case footn_to: + do_sub_footnote(text_no, text_s, + text_s->misc_items[ 0 ].datum.footnote_to, NULL); + break; + + case footn_in: + do_sub_footnote(text_s->misc_items[ 0 ].datum.footnoted_in, NULL, + text_no, text_s); + break; + + + case loc_no: + case rec_time: + case sent_by: + case sent_at: + restart_kom("do_delete_text(): Illegal misc-item syntax.\n"); +#ifndef COMPILE_CHECKS + default: + restart_kom("do_delete_text(): Illegal misc-item.\n"); +#endif + } + } + + cached_delete_text(text_no); + return OK; +} + +/* + * Atomic calls. + */ + +/* + * Calls to handle marks. + * + * Marks are secret. No else can know what you have marked. + */ + +/* + * Get text_nos of all marked texts. + */ +extern Success +get_marks( Mark_list *result ) +{ + CHK_LOGIN(FAILURE); + *result = ACT_P->marks; + return OK; +} + + + + +/* + * Get the text. The text will not be marked as read until you + * explicitly mark_as_read() it. start_char = 0 && end_char = END_OF_STRING + * gives the entire text. + */ +extern Success +get_text (Text_no text_no, + String_size start_char, + String_size end_char, + String * result) +{ + Text_stat * text_s; + + GET_T_STAT(text_s, text_no, FAILURE); + + /* Check if ACTPERS has read acess to the text */ + + if ( text_read_access( text_no, text_s ) != TRUE ) + { + kom_errno = KOM_NO_SUCH_TEXT; + err_stat = text_no; + + return FAILURE; + } + + *result = cached_get_text( text_no ); + + if ( start_char > result->len ) + { + kom_errno = KOM_INDEX_OUT_OF_RANGE; + return FAILURE; + } + + result->string += start_char; + result->len = min ( result->len - 1, end_char ) + 1 - start_char; + + if ( ACTPERS ) + { + ++ACT_P->no_of_text_fetches; + mark_person_as_changed( ACTPERS ); + } + + return OK; +} + +/* + * Get text status. + * + * If there are recipients of the text that are secret confs + * those misc-items will be censored. + */ +extern Success +get_text_stat (Text_no text_no, + Text_stat *result ) +{ + Text_stat * text_stat; + Misc_info * orig; + Misc_info * copy; /* Censored Misc_infos */ + + GET_T_STAT(text_stat, text_no, FAILURE); + + if ( !text_read_access(text_no, text_stat) && !ENA(admin, 2)) + { + kom_errno = KOM_NO_SUCH_TEXT; + return FAILURE; + } + + *result = *text_stat; + + /* Delete secret confs */ + + /* ^^^ Possible optimisation: + No need to copy unless there is a secret conf among the recipients. */ + + result->misc_items = tmp_alloc(result->no_of_misc * sizeof ( Misc_info)); + result->no_of_misc = 0; + copy = result->misc_items; + orig = text_stat->misc_items; + + while ( orig < text_stat->misc_items + text_stat->no_of_misc ) + { + switch( orig->type ) + { + case recpt: + if ( fast_access_perm( orig->datum.recipient ) <= none + && !ENA(admin, 4)) + { + skip_recp ( &orig, text_stat ); + } + else + { + *copy++ = *orig++; + ++result->no_of_misc; + } + break; + + case cc_recpt: + if ( fast_access_perm( orig->datum.cc_recipient ) <= none + && !ENA(admin, 4)) + { + skip_recp ( &orig, text_stat ); + } + else + { + *copy++ = *orig++; + ++result->no_of_misc; + } + break; + + case loc_no: + case rec_time: + case comm_to: + case comm_in: + case footn_to: + case footn_in: + case sent_by: + case sent_at: + *copy++ = *orig++; + ++result->no_of_misc; + break; + +#ifndef COMPILE_CHECKS + default: + restart_kom("get_text_stat() - illegal misc_item!\n"); +#endif + } + } + return OK; +} + +/* + * Functions local to create_text: + */ + +/* + * Check that the recipient or cc_recipient at LOC is not already a + * recipient or cc_recipient of this text. + */ +static Success +check_double_subm (Misc_info * misc, + int loc, + Conf_no addressee) +{ + int j; + + for ( j = 0; j < loc; j++) + { + switch ( misc[ j ].type ) + { + case recpt: + if ( misc[ j ].datum.recipient == addressee ) + return FAILURE; + + break; + + case cc_recpt: + if ( misc[ j ].datum.cc_recipient == addressee ) + return FAILURE; + + break; + + case comm_to: + case comm_in: + case footn_to: + case footn_in: + case loc_no: + case rec_time: + case sent_by: + case sent_at: + break; + +#ifndef COMPILE_CHECKS + default: + log(__FILE__, ": check_double_subm(): bad misc_item.\n"); + break; +#endif + } + } + return OK; +} + +/* + * Check that none of the first 'pos' misc_items pointed to by misc + * is a comment or footnote to text forbidden. + */ +static Success +check_double_comm (Misc_info *misc, + int pos, + Text_no forbidden) +{ + for(;pos > 0; pos--, misc++) + { + switch(misc->type) + { + case comm_to: + if ( misc->datum.comment_to == forbidden ) + return FAILURE; + break; + + case footn_to: + if ( misc->datum.footnote_to == forbidden ) + return FAILURE; + break; + + case recpt: + case cc_recpt: + case comm_in: + case footn_in: + case loc_no: + case rec_time: + case sent_by: + case sent_at: + break; + +#ifndef COMPILE_CHECKS + default: + log(__FILE__ "check_double_subm(): bad misc_item.\n"); + break; +#endif + } + } + + return OK; +} + + + +/* + * Check that all misc_items are legal. Return OK / FAILURE. + * Update recpt & cc_recpt fields if the text goes to a super_conf. + * Signal an error if a conference is recipient more than once, or if + * a text is a comment to the same text more than once. + */ +static Success +create_text_check_misc (u_short * no_of_misc, + Misc_info * misc ) +{ + int i; + Text_stat * parent; + Conf_no addressee; + + for ( i = 0; i < *no_of_misc; i++) + { + err_stat = i; /* In case of an error, err_stat indicates which + misc_item caused the error. */ + + switch( misc[ i ].type ) + { + case footn_to: + /* Check that ACTPERS is the author to the text. */ + + GET_T_STAT( parent, misc[ i ].datum.footnote_to, FAILURE); + + if ( check_footn(parent) == FAILURE ) + return FAILURE; + + if ( check_double_comm(misc, i, misc[ i ].datum.footnote_to) + != OK ) + { + kom_errno = KOM_ILL_MISC; + return FAILURE; + } + + break; + + case comm_to: + /* Check that the text exists. */ + + GET_T_STAT( parent, misc[ i ].datum.comment_to, FAILURE); + + if ( check_comm(parent) == FAILURE ) + return FAILURE; + + if ( check_double_comm(misc, i, misc[ i ].datum.comment_to) + != OK ) + { + kom_errno = KOM_ILL_MISC; + return FAILURE; + } + + break; + + case recpt: + /* Check that ACTPERS has write access to the conference or a + superconference. */ + /* Superconfs are recursive */ + + addressee = submit_to( misc[ i ].datum.recipient, NULL); + + /* Update in case of super_conf */ + + if ((misc[ i ].datum.recipient = addressee) == 0) + { + kom_errno = KOM_ACCESS; + return FAILURE; + } + + /* Check that this recipient is not already a recipient. */ + + if ( check_double_subm(misc, i, addressee) != OK ) + { + kom_errno = KOM_ILL_MISC; + return FAILURE; + } + + break; + + case cc_recpt: + /* Check that ACTPERS has write access to the conference or a + superconference. */ + /* Superconfs are recursive */ + + addressee = submit_to( misc[ i ].datum.cc_recipient, NULL); + + /* Update in case of super_conf */ + + if ((misc[ i ].datum.cc_recipient = addressee) == 0) + { + kom_errno = KOM_ACCESS; + return FAILURE; + } + + /* Check that this recipient is not already a recipient. */ + + if (check_double_subm(misc, i, addressee) != OK) + { + kom_errno = KOM_ILL_MISC; + return FAILURE; + } + + break; + + case loc_no: /* Ignore loc_no */ + break; + + case comm_in: + case footn_in: + case rec_time: + case sent_by: + case sent_at: + /* Fall through */ +#ifndef COMPILE_CHECKS + default: +#endif + kom_errno = KOM_ILL_MISC; + return FAILURE; + } + } + + return OK; +} + +/* + * Fix all double references. Eg if misc contains comm_to, add a comm_in field. + * No access-permission checking is done. + */ +static Success +create_text_add_miscs(Text_no new_text, + int no_of_misc, + Misc_info * misc) +{ + int i; + + for ( i = 0; i < no_of_misc; i++) + { + err_stat = i; /* In case of an error, err_stat indicates which + misc_item caused the error. */ + + switch( misc[ i ].type ) + { + case footn_to: + if ( do_add_footnote(new_text, misc[ i ].datum.footnote_to) != OK) + return FAILURE; + + break; + + case comm_to: + if ( do_add_comment(new_text, misc[ i ].datum.comment_to) != OK ) + return FAILURE; + + break; + + case recpt: + if ( do_add_recpt(new_text, NULL, misc[ i ].datum.recipient) != OK) + return FAILURE; + + break; + + case cc_recpt: + if ( do_add_cc_recpt(new_text, NULL, + misc[ i ].datum.cc_recipient) !=OK ) + return FAILURE; + + break; + + case loc_no: /* Ignore loc_no. */ + break; + + case comm_in: + case footn_in: + case rec_time: + case sent_by: + case sent_at: + /* Fall through */ +#ifndef COMPILE_CHECKS + default: +#endif + restart_kom("create_text_add_misc() - illegal Info_type"); + } + } + return OK; +} + +/* + * Create a text. + * + * The recipients may change. See doc for set_permitted_submitters. + * + * The only allowed Misc_items are recpt, cc_recpt, comm_to and footn_to. + * loc_no are allowed, but ignored. + * + * Returns text_no of the created text, or 0 if there was an error. + */ +extern Text_no +create_text(String message, + u_short no_of_misc, + Misc_info * misc ) +{ + Text_no text; + Text_stat * t_stat; + extern int errno; + + CHK_LOGIN(0); + + /* Check all misc-items */ + + if ( create_text_check_misc(&no_of_misc, misc) != OK + || (text = cached_create_text( message )) == 0) + { + return 0; + } + + if ( (t_stat = cached_get_text_stat( text )) == NULL ) + { + restart_kom("create_text: can't get text-stat of " + "newly created text.\n" + "Text == %d, kom_errno == %d, errno == %d\n", + text, kom_errno, errno); + } + + t_stat->author = ACTPERS; + t_stat->creation_time = time(NULL); + t_stat->no_of_lines = count_lines( message ); + t_stat->no_of_chars = s_strlen( message ); + + if ( create_text_add_miscs(text, no_of_misc, misc) != OK ) + { + log("ERROR: create_text(): can't add miscs.\n"); + return 0; + } + + ACT_P->created_texts.texts + = srealloc(ACT_P->created_texts.texts, + ++(ACT_P->created_texts.no_of_texts) * sizeof(Text_no)); + ACT_P->created_texts.texts[ ACT_P->created_texts.no_of_texts - 1 ] = text; + ACT_P->created_lines += t_stat->no_of_lines; + ACT_P->created_bytes += t_stat->no_of_chars; + + mark_person_as_changed( ACTPERS ); + mark_text_as_changed( text ); + + async_new_text( text, t_stat ); /* Send asynchronous message. */ + + return text; +} + +/* + * Delete a text. + * + * Only a supervisor of the author may delete a text. + */ +extern Success +delete_text( Text_no text_no ) +{ + Text_stat *text_s; + + CHK_LOGIN(FAILURE); + GET_T_STAT(text_s, text_no, FAILURE); + + if ( !is_supervisor(text_s->author, NULL) && !ENA(admin, 5) ) + { + kom_errno = (text_read_access(text_no, text_s) + ? KOM_NOT_AUTHOR : KOM_NO_SUCH_TEXT); + return FAILURE; + } + + return do_delete_text(text_no, text_s); +} + +/* + * Add a recipient to a text. + */ +extern Success +add_recipient( Text_no text_no, + Conf_no conf_no, + Info_type type ) /* recpt or cc_recpt */ +{ + Text_stat * t_stat; + Conference * conf_c; + + CHK_LOGIN(FAILURE); + GET_T_STAT(t_stat, text_no, FAILURE); + GET_C_STAT(conf_c, conf_no, FAILURE); + + if ( !text_read_access(text_no, t_stat ) && !ENA(admin, 4)) + { + kom_errno = KOM_NO_SUCH_TEXT; + return FAILURE; + } + + if ( is_recipient(conf_no, t_stat) ) + { + kom_errno = KOM_ALREADY_RECIPIENT; + return FAILURE; + } + + if ( count_recipients( t_stat ) >= MAX_RECIPIENTS ) + { + kom_errno = KOM_RECIPIENT_LIMIT; + return FAILURE; + } + + switch ( type ) + { + case recpt: + if ( do_add_recpt(text_no, t_stat, conf_no) != OK) + return FAILURE; + break; + + case cc_recpt: + if ( do_add_cc_recpt(text_no, t_stat, conf_no) != OK) + return FAILURE; + break; + + case comm_to: + case comm_in: + case footn_to: + case footn_in: + case loc_no: + case rec_time: + case sent_by: + case sent_at: + /* Fall through */ +#ifndef COMPILE_CHECKS + default: +#endif + kom_errno = KOM_ILLEGAL_INFO_TYPE; + return FAILURE; + } + + if ( t_stat->author != ACTPERS ) + ADD_MISC(t_stat, sent_by, sender, ACTPERS); + + ADD_MISC(t_stat, sent_at, sent_at, time(NULL)); + + mark_text_as_changed( text_no ); + return OK; +} + + +/* + * Subtract a recipient from a text. + * + * This may be done by + * a) a supervisor of the author. + * b) a supervisor of the recipient. + * c) the sender of the text to the recipient + */ +extern Success +sub_recipient( Text_no text_no, + Conf_no conf_no) +{ + Text_stat * text_s; + Conference * conf_c; + + CHK_LOGIN(FAILURE); + GET_T_STAT(text_s, text_no, FAILURE); + + if ( !text_read_access(text_no, text_s ) && !ENA(admin, 4)) + { + kom_errno = KOM_NO_SUCH_TEXT; + return FAILURE; + } + + if ( !is_recipient( conf_no, text_s ) ) + { + kom_errno = KOM_NOT_RECIPIENT; + return FAILURE; + } + + GET_C_STAT(conf_c, conf_no, FAILURE); + + + if ( !is_supervisor(text_s->author, NULL) + && !is_supervisor(conf_no, conf_c) + && !is_sender( text_s, conf_no ) ) + { + kom_errno = KOM_PERM; + return FAILURE; + } + + return do_sub_recpt( text_no, text_s, conf_no, conf_c ); +} + +/* + * Add a comment-link between two existing texts. + */ +extern Success +add_comment( Text_no comment, + Text_no comment_to ) +{ + Text_stat *text_s, *parent_s; + + CHK_LOGIN(FAILURE); + GET_T_STAT(text_s, comment, FAILURE); + GET_T_STAT(parent_s, comment_to, FAILURE); + + if ( check_comm(parent_s) != OK ) + return FAILURE; + + if ( !text_read_access(comment, text_s) ) + { + kom_errno = KOM_NO_SUCH_TEXT; + return FAILURE; + } + + /* Check if already comment */ + if ( is_comment_to(comment, parent_s) ) + { + kom_errno = KOM_ALREADY_COMMENT; + return FAILURE; + } + + if ( do_add_comment(comment, comment_to) != OK) + return FAILURE; + + if ( text_s->author != ACTPERS ) + ADD_MISC(text_s, sent_by, sender, ACTPERS); + + ADD_MISC(text_s, sent_at, sent_at, time(NULL)); + + mark_text_as_changed( comment ); + return OK; +} + +/* + * Delete a comment-link between two texts. + * + * This can be done by: + * a) a supervisor of any of the authors. + * b) the sender of the comment. + */ +extern Success +sub_comment( Text_no comment, /* 'comment' is no longer a comment */ + Text_no parent ) /* to 'parent' */ +{ + Text_stat * text_s; + Text_stat * parent_s; + + + CHK_LOGIN(FAILURE); + + GET_T_STAT(text_s, comment, FAILURE); + GET_T_STAT(parent_s, parent, FAILURE); + + if ( !text_read_access(comment, text_s ) && !ENA(admin, 4)) + { + kom_errno = KOM_NO_SUCH_TEXT; + return FAILURE; + } + + if ( !text_read_access(parent, parent_s ) && !ENA(admin, 4)) + { + kom_errno = KOM_NO_SUCH_TEXT; + return FAILURE; + } + + if ( !is_comment_to( comment, parent_s ) ) + { + kom_errno = KOM_NOT_COMMENT; + return FAILURE; + } + + + if ( !is_supervisor(text_s->author, NULL) + && !is_supervisor( parent_s->author, NULL ) + && !is_comm_sender( text_s, parent ) ) + { + kom_errno = KOM_PERM; + return FAILURE; + } + + do_sub_comment( comment, text_s, parent, parent_s ); + return OK; +} + +/* + * Add a footnote-link between two existing texts. Only the author + * may do this. The texts must have the same author. + */ +extern Success +add_footnote( Text_no footnote, + Text_no footnote_to ) +{ + Text_stat *text_s, *parent_s; + + CHK_LOGIN(FAILURE); + GET_T_STAT(text_s, footnote, FAILURE); + GET_T_STAT(parent_s, footnote_to, FAILURE); + + if ( !text_read_access(footnote, text_s) + || !text_read_access(footnote_to, parent_s) ) + { + kom_errno = KOM_NO_SUCH_TEXT; + return FAILURE; + } + + if ( check_footn(parent_s) != OK ) + return FAILURE; + + if ( text_s->author != parent_s->author ) + { + kom_errno = KOM_NOT_AUTHOR; + return FAILURE; + } + + /* Check if already footnote */ + if ( is_footnote_to(footnote, parent_s) ) + { + kom_errno = KOM_ALREADY_FOOTNOTE; + return FAILURE; + } + + if ( do_add_footnote(footnote, footnote_to) != OK) + return FAILURE; + + ADD_MISC(text_s, sent_at, sent_at, time(NULL)); + + mark_text_as_changed( footnote ); + return OK; +} + +/* + * Delete a footnote-link between two texts. + * Only the author may do this. + */ +extern Success +sub_footnote( Text_no footnote, /* 'footnote' is no longer a */ + Text_no parent ) /* footnote to 'parent' */ +{ + Text_stat * text_s; + Text_stat * parent_s; + + + CHK_LOGIN(FAILURE); + + GET_T_STAT(text_s, footnote, FAILURE); + GET_T_STAT(parent_s, parent, FAILURE); + + if ( !text_read_access(footnote, text_s ) && !ENA(admin, 4)) + { + kom_errno = KOM_NO_SUCH_TEXT; + return FAILURE; + } + + if ( !text_read_access(parent, parent_s ) && !ENA(admin, 4)) + { + kom_errno = KOM_NO_SUCH_TEXT; + return FAILURE; + } + + if ( !is_footnote_to( footnote, parent_s ) ) + { + kom_errno = KOM_NOT_FOOTNOTE; + return FAILURE; + } + + + if ( text_s->author != ACTPERS && parent_s->author != ACTPERS ) + { + kom_errno = KOM_PERM; + return FAILURE; + } + + do_sub_footnote( footnote, text_s, parent, parent_s ); + return OK; +} + + +/* + * Get mapping from Local_text_no to (global) Text_no for part of + * a conference. + * + * BUG: You should be allowed to get the mapping if it is a letterbox + * which is not unread_is_secret. +++ (Really? I don't think so. /ceder) + */ +extern Success +get_map (Conf_no conf_no, + Local_text_no first_local_no, + Local_text_no no_of_texts, + Text_list * result) +{ + Conference * conf_c; + Local_text_no highest_wanted_no, highest; + Access acc; + + CHK_LOGIN(FAILURE); + GET_C_STAT(conf_c, conf_no, FAILURE); + + acc = access_perm(conf_no, conf_c); + + if ( acc <= none ) + { + kom_errno = KOM_UNDEF_CONF; + return FAILURE; + } + + if ( acc == read_protected ) + { + kom_errno = KOM_ACCESS; + return FAILURE; + } + + highest = conf_c->texts.first_local_no + conf_c->texts.no_of_texts; + + if ( first_local_no >= highest ) + { + kom_errno = KOM_NO_SUCH_LOCAL_TEXT; + return FAILURE; + } + + result->first_local_no = max(conf_c->texts.first_local_no, + first_local_no); + highest_wanted_no = min(highest, first_local_no + no_of_texts); + + if ( highest_wanted_no >= result->first_local_no ) + result->no_of_texts = highest_wanted_no - result->first_local_no; + else + result->no_of_texts = 0; + + result->texts = &conf_c->texts.texts[ result->first_local_no + - conf_c->texts.first_local_no ]; + + return OK; +} -- GitLab