[comp.sources.unix] v24i071: Purdue tool for students to turn in work, Part01/02

rsalz@uunet.uu.net (Rich Salz) (03/21/91)

Submitted-by: Kevin Braunsdorf <ksb@cc.purdue.edu>
Posting-number: Volume 24, Issue 71
Archive-name: pucc-turnin/part01

This two part archive contains the PUCC electronic submissions system,
it takes a while to install and RUNS SETUID ROOT.

Turnin(1L) and project(1L) provide administration of electronic
submissions for a large number of classes.  These tools may be applied
to any type of course (programming, art & design, word processing, etc.)
which requires `students' to submit `projects' to an `instructor'.

A `student' is any login name on a UNIX system.  Multiple logins under
the same uid are handled correctly.  The environment variables USER and
LOGNAME are consulted (in that order) to determine which login name is
submitting the file.

A `project' is a broad term for a group of submissions, one per login
name, which will be collected over time.  These may then be processed
by running a shell command for each project.   Submitted files are
stored in a tar(1) format archive.  Large archives are kept compressed,
but nether user nor administrator will see this detail.

An `instructor' is a supervising login name.  This login is able to
limit the collection time for any `project' and issue grade commands.

The user interface to turnin(1L) is quite simple and obvious.  The user
simply runs
	$ turnin files
to submit files.  If the request is ambiguous turnin will request
information from stdin.


#!/bin/sh
# This is pucc-1e, a shell archive (produced by shar 3.49)
# To extract the files from this archive, save it to a file, remove
# everything above the "!/bin/sh" line above, and type "sh file_name".
#
# made 11/29/1990 16:01 UTC by ksb@cc.purdue.edu (Kevin Braunsdorf)
# Source directory /ksb/c.s.u-2
#
# existing files will NOT be overwritten unless -c is specified
#
# This shar contains:
# length  mode       name
# ------ ---------- ------------------------------------------
#   1608 -rw-r--r-- INSTALL.05
#  30395 -r--r--r-- turnin/turnin.c
#   5973 -r--r--r-- project/project.1l
#   3242 -r--r--r-- turnin/INSTALL
#   2553 -r--r--r-- turnin/turnin.1l
#   2655 -r--r--r-- turnin.cf/turnin.5l
#   1781 -r--r--r-- project/template
#   2259 -rw-r--r-- project/machine.h
#   1368 -rw-r--r-- turnin/Makefile
#   1386 -rw-r--r-- turnin.cf/turnin.cf
#   1361 -r--r--r-- turnin.cf/turnin.cf.5l
#   1575 -r--r--r-- project/install
#   1326 -rw-r--r-- project/Makefile
#    767 -rw-r--r-- turnin.cf/Makefile
#    366 -rw-r--r-- turnin.cf/ckcf.sh
#    149 -r--r--r-- turnin.cf/Distfile
#  32572 -r--r--r-- project/project.c
#
# ============= INSTALL.05 ==============
if test -f 'INSTALL.05' -a X"$1" != X"-c"; then
	echo 'x - skipping INSTALL.05 (File already exists)'
else
echo 'x - extracting INSTALL.05 (Text)'
sed 's/^X//' << 'Purdue' > 'INSTALL.05' &&
Contains:
X	project(1l)	- control electronic submission
X	turnin(1l)	- electronically submit files for grading
X	turnin(5l)	- instructions for instructors who use turnin
X	turnin.cf(5l)	- turnin configuration file format
X
X
Notes on depends:
X	- libopt.a need not be installed, but it would make life easier
X	  if you want other PUCC tools.
X
X	- turnin needs to be compiled with libopt.a
X
X	- project needs libopt.a and flock(2)   !!  BSD only, sorry  !!
X
X
To install these tools:
X
0\ read the manual pages, see if you want any of them
X
1\ decide where to install all this stuff, change the destinations in
X   the Makefiles {BIN,LIB,ETC,HEADER}
X	vi */Makefile
X	
2\ select a place for turnin's database file (now /usr/local/lib/turnin.cf)
X	vi turnin.cf/Makefile project/machine.h
X
3\ if your system doesn't have strcasecmp in libc you'll have to copy it
X   from the mkcat distribution and add it to ONEC in the Makefiles
X	
3\ build project & turnin
X	(cd project && make)
X	(cd turnin && make)
X
4\ install the tools and files
X	su
X	(cd project && make install)			#
X	(cd turnin && make install)			#
X	(cd turnin.cf && make install)			#
X	exit						# the root shell
X
5\ install the manual pages, turnin/turnin.5l will need lots of site editing
X   (don't install it if you have not edited it)
X	mkcat -v turnin/turnin.1l project/project.1l turnin.cf/turnin.cf.5l
X
6\ clean up the dirs
X	(cd turnin && make clean)
X	(cd project && make clean)
X
7\ add the mail-alias `enroll-info' to point to the turnin.cf maintainer
X	: 'I do not know how *you* do that!'
X
kayessbee
--
Kevin Braunsdorf, ksb@cc.purdue.edu, pur-ee!ksb, purdue!ksb
Purdue
chmod 0644 INSTALL.05 ||
echo 'restore of INSTALL.05 failed'
Wc_c="`wc -c < 'INSTALL.05'`"
test 1608 -eq "$Wc_c" ||
	echo 'INSTALL.05: original size 1608, current size' "$Wc_c"
fi
# ============= turnin/turnin.c ==============
if test ! -d 'turnin'; then
    echo 'x - creating directory turnin'
    mkdir 'turnin'
fi
if test -f 'turnin/turnin.c' -a X"$1" != X"-c"; then
	echo 'x - skipping turnin/turnin.c (File already exists)'
else
echo 'x - extracting turnin/turnin.c (Text)'
sed 's/^X//' << 'Purdue' > 'turnin/turnin.c' &&
/*
X * turnin files - electronic submission, the send side			(ksb)
X *
X * expects the environment varaiable
X *	<SUBPREFIX><course>=d<div>s<sec>
X * to be in the environment
X *
X * $Compile: ${cc-cc} ${cc_debug--O} -I/usr/include/local %f -o %F -lopt
X *
X * configuration
X *	apcCourse contains a list of the courses, and their submit
X *	directories, each of these directories contains a file called
X *	"Projlist" which contains the current project numbers.
X *	The students files will be filed in his division.section
X *	subdirectory under his login name.
X 
X		THIS PROGRAM RUNS SET UID ROOT
X 
X */
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <sys/wait.h>
#include <sys/param.h>
#include <errno.h>
#include <pwd.h>
#include <grp.h>
#include <stdio.h>
#include <ctype.h>		/* for isspace() and isdigit() macros	*/
#include <strings.h>
#include "getopt.h"
#include "../project/machine.h"
X
static char RCSId[] =
X	"$Id: turnin.c,v 4.8 90/11/23 14:05:25 becker Exp $";
X
char *progname =		/* our name to the world		*/
X	RCSId;
static int
X	fVerbose = 0;		/* tar tvf %s for the user		*/
X
typedef enum {
X	INITUSER,		/* initialize a users account		*/
X	LISTPROJ,		/* only list projects for a course	*/
X	LISTSUB,		/* only list subscribed courses		*/
X	VERSION,		/* give version of the program		*/
X	UNKNOWN			/* nothing to do, maybe list tar file	*/
} WHAT;
X
static WHAT eWhat;		/* which operation the user wants	*/
X
extern struct group *getgrnam(), *getgrgid();
extern struct passwd *getpwnam();
extern char *strchr(), *strrchr(), *getenv();
extern char *mktemp(), **environ, *malloc(), *calloc();
#define strsave(Mpch) strcpy(malloc(strlen(Mpch)+1), Mpch)
extern int errno;
extern char *sys_errlist[];
#define strerror(Me)	(sys_errlist[Me])
X
static char acCompress[] =	/* path to compress			*/
X	COMPRESS_PATH;
static char acDotZ[] =		/* compress extender			*/
X	".Z";
static char acTar[] =		/* path to tar				*/
X	TAR_PATH;
static char acSub[] =		/* record the SUB_ prefix		*/
X	SUBPREFIX;
static char acSectIgnore[] =	/* bogus, ``no sections'' string	*/
X	SECTIGNORE;
static int iCourses;		/* number of valid subscribed courses	*/
X
static char *apcCourse[MAXCOURSE];	/* who				*/
static char *apcUid[MAXCOURSE];		/* who -> uid			*/
static char *apcDir[MAXCOURSE];		/* who -> where			*/
static char *apcGroup[MAXCOURSE];	/* who -> group			*/
static char *apcSections[MAXCOURSE];	/* who -> sections (*)		*/
X
/* lines from the project file	*/
static char *apcProjects[MAXPROJECTS+1];
X
/*
X * reads a configuration file of the form:			(doc/ksb)
X * course:alpha uid:subdir for this course:alpha gid\n
X */
static void
ReadDB()
{
X	register FILE *fpDB;
X	register char *pc;
X	register int i;
X	static char acError[] = "%s: error in data base line %d\n";
X	static char acLine[MAXCHARS];
X	static char acTurnbase[] = TURNBASE; 
X
X	if (0 == (fpDB = fopen(acTurnbase, "r"))) {
X		fprintf(stderr, "%s: fopen: %s: %s\n", progname, acTurnbase, strerror(errno));
X		exit(2);
X	}
X
X	i = 0;
X	while ((char *)0 != fgets(acLine, MAXCHARS, fpDB)) {
X		pc = acLine;
X		while (isspace(*pc) && '\n' != *pc)
X			++pc;
X		if ('#' == *pc || '\n' == *pc)	/* comments and blank lines */
X			continue;
X		apcCourse[i] = pc = strsave(pc);
X		if ((char *)0 == (pc = strchr(pc, SEP))) {
X			printf(acError, progname, i);
X			exit(16);
X		}
X		*pc++ = '\000';
X		apcUid[i] = pc;
X		if ((char *)0 == (pc = strchr(pc, SEP))) {
X			printf(acError, progname, i);
X			exit(17);
X		}
X		*pc++ = '\000';
X		apcDir[i] = pc;
X		if ((char *)0 == (pc = strchr(pc, SEP))) {
X			printf(acError, progname, i);
X			exit(18);
X		}
X		*pc++ = '\000';
X		apcGroup[i] = pc;
X		if ((char *)0 == (pc = strchr(pc, SEP))) {
X			printf(acError, progname, i);
X			exit(19);
X		}
X		*pc++ = '\000';
X		apcSections[i] = pc;
X		if ((char *)0 != (pc = strchr(pc, '\n'))) {
X			*pc = '\000';
X		}
X		++i;
X	}
X	apcCourse[i] = (char *)0;
}
X
/*
X * list all the courses turnin knows about				(ksb)
X */
void
ListCourses(fp)
FILE *fp;
{
X	register int iWho;
X
X	for (iWho = 0; (char *)0 != apcCourse[iWho]; ++iWho) {
X		fprintf(fp, "%16s", apcCourse[iWho]);
X		if (3 == iWho % 4)
X			fprintf(fp, "\n");
X	}
X	if (0 != iWho % 4) {
X		fprintf(fp, "\n");
X	}
}
X
/*
X * list the sections in the given course				(ksb)
X * input which one the user wants and filter it for case
X */
void
FindSection(iWho, fp, pcIndex)
int iWho;
FILE *fp;
char *pcIndex;
{
X	register char *pcComma, *pcLast;
X
X	/* only one, take that one
X	 */
X	if ((char *)0 == strchr(apcSections[iWho], ',')) {
X		/** fprintf(fp, "There is only one section for %s, using \"%s\".\n", apcCourse[iWho], apcSections[iWho]);
X		 **/
X		(void)strcpy(pcIndex, apcSections[iWho]);
X		return;
X	}
X	/* if the user gave us one to try
X	 */
X	if ('\000' != *pcIndex) {
X		goto trysec;
X	}
X	for (;;) {
X		fprintf(fp, "The sections of %s are:\n", apcCourse[iWho]);
X		pcComma = apcSections[iWho];
X		while ((char *)0 != (pcLast = pcComma)) {
X			pcComma = strchr(pcComma, ',');
X			if ((char *)0 != pcComma)
X				*pcComma = '\000';
X			fprintf(fp, "\t%s\n", pcLast);
X			if ((char *)0 != pcComma)
X				*pcComma++ = ',';
X		}
X
X		fprintf(fp, "Enter your section: ");
X		if ((char *)0  == fgets(pcIndex, MAXDIVSEC, stdin)) {
X			fprintf(fp, "quit\n");
X			exit(0);
X		}
X		if ((char *)0 == (pcLast = strchr(pcIndex, '\n'))) {
X			fprintf(fp, "%s: invalid division/section identifier\n", progname);
X			continue;
X		}
X		*pcLast = '\000';
X
X	trysec:
X		pcComma = apcSections[iWho];
X		while ((char *)0 != (pcLast = pcComma)) {
X			pcComma = strchr(pcComma, ',');
X			if ((char *)0 != pcComma)
X				*pcComma = '\000';
X			if (0 == strcasecmp(pcIndex, pcLast)) {
X				/* fix the case for them */
X				(void)strcpy(pcIndex, pcLast);
X				if ((char *)0 != pcComma)
X					*pcComma++ = ',';
X				return;
X			}
X			if ((char *)0 != pcComma)
X				*pcComma++ = ',';
X		}
X		fprintf(fp, "%s: cannot find section `%s\' in the list for `%s\', retype it please.\n", progname, pcIndex, apcCourse[iWho]);
X	}
}
X
/*
X * If the given course if on the course list, return its index		(aho)
X * in the apcCourse[] array.  Otherwise, return -1.
X */
static int
VerifyCourse(pcCourse)
char *pcCourse;
{
X	register char **ppch;
X
X	for (ppch = apcCourse; (char *)0 != *ppch; ++ppch) {
X		if (0 == strcasecmp(pcCourse, *ppch)) {
X			return ppch - apcCourse;
X		}
X	}
X	return -1;
}
X
X
/*
X * we need to tell the user (on stdout) what to put in his		(ksb)
X * .login/.profile.  Write to him only on stderr.
X * (run as user for no reason, btw)
X */
int
DoInit(argc, argv, pcGuess)
int argc;
char **argv, *pcGuess;
{
X	register char *pcIndex, *pcEqual;
X	auto int iWho, fShell, fMore;
X	auto char acBuf[MAXCOURSENAME + MAXDIVSEC];
X	auto char *pcShell, acYesNo[MAXYESNO+1];
X	auto char *pcKopOut = "abort";
X
X	fprintf(stderr, "\n\
\tConfiguring your account to send electronic submissions\n\n\
This program sets up your computer account so that you may use \"%s\",\n\
the electronic submission program.\n\n", progname);
X
X	if ((char *)0 == (pcShell = getenv("SHELL"))) {
X		pcShell = "/bin/sh";
X	}
X
X	iWho = strlen(pcShell);
X	if (iWho >= 3 && 'c' == pcShell[iWho-3])
X		fShell = 'c';
X	else
X		fShell = 's';
X
X	/* if the user used turnin -i -c cs100 we need to put cs100 in the list
X	 * we know argv is (char *)0 terminated, we can use that slot.
X	 */
X	if ((char *)0 != pcGuess) {
X		argv[argc] = pcGuess;
X		++argc;
X	}
X	fMore = 0 == argc;
X	if (fMore) {
X		fprintf(stderr, "\
Below you will see a list of courses that are presently using this system.\n\
For each course that you are enrolled in, enter its name and section when\n\
prompted.  When you have entered information for all of your courses, press\n\
^D (Control-D) to quit.\n");
X	} else {
X		fprintf(stderr, "\
By responding the prompts below you will be enrolled in electronic\n\
submissions for");
X		for (iWho = 0; iWho < argc; ++iWho) {
X			fprintf(stderr, "%s%s", (0 == iWho ? " " : ", "), argv[iWho]);
X		}
X		fprintf(stderr, ".\n");
X	}
X	while (fMore || argc > 0) {
X		fprintf(stderr, "\n");
X		if ((char *)0 != argv[0]) {
X			(void)strcpy(acBuf, argv[0]);
X			pcIndex = strchr(acBuf, '\000');
X			--argc, ++argv;
X			goto tryit;
X		}
X
X		fprintf(stderr, "Here is a list of the courses currently available\n");
X		fprintf(stderr, "(if you cannot find your course in this list type it anyway):\n");
X		for (;;) {
X			ListCourses(stderr);
X			fprintf(stderr, "Enter a course name you are subscribed to or ^D to quit: ");
X			fflush(stderr);
X			if ((char *)0 == fgets(acBuf, MAXCOURSENAME, stdin) || 0 == strcmp("^D\n", acBuf) || 0 == strcmp("^d\n", acBuf)) {
X				fprintf(stderr, "%s\n", pcKopOut);
X				exit(0);
X			}
X			if ((char *)0 == (pcIndex = strchr(acBuf, '\n'))) {
X				fprintf(stderr, "%s: invalid course name\n", progname);
X				exit(6);
X			}
X			*pcIndex = '\000';
X	tryit:
X			if ('\000' == acBuf[0]) {
X				/* skip blank lines */
X				continue;
X			}
X			if ((char *)0 != (pcEqual = strchr(acBuf, '='))) {
X				pcIndex = pcEqual;
X				*pcIndex = '\000';
X			}
X			if (-1 != (iWho = VerifyCourse(acBuf))) {
X				break;
X			}
X			fprintf(stderr, "\nIt is possible that you have entered a course name that will be valid later\n");
X			fprintf(stderr, "If you are sure that is the name of your course I will arrange to have %s\n", progname);
X			fprintf(stderr, "ask you for the section information later.\n\n");
X			fprintf(stderr, "Is `%s\' is a course identifier for one of your courses? [ny] ", acBuf);
X			if ((char *)0 == fgets(acYesNo, MAXYESNO, stdin)) {
X				exit(1);
X			}
X			if ('y' == acYesNo[0] || 'Y' == acYesNo[0]) {
X				if ((char *)0 == pcEqual) {
X					*pcIndex++ = '\000';
X					*pcIndex = '\000';
X				}
X				goto unknown;
X			}
X		}
X
X		/*
X		 * if the user is subscribed to this course, don't ask him
X		 * for divsion/section
X		 */
X		*pcIndex++ = '\000';
X		if ((char *)0 == pcEqual) {
X			*pcIndex = '\000';
X		}
X
X		FindSection(iWho, stderr, pcIndex);
X
X		(void)setpwent();
X		if ((struct passwd *)0 == getpwnam(apcUid[iWho])) {
X			fprintf(stderr, "Instructor \"%s\" doesn't exist on this machine, therefore, you probably\n", apcUid[iWho]);
X			fprintf(stderr, "cannot use this machine for \"%s\".\n", acBuf);
X		}
X		(void)endpwent();
X
X		(void)setgrent();
X		if ((struct group *)0 == getgrnam(apcGroup[iWho])) {
X			fprintf(stderr, "Group \"%s\" doesn't exist on this machine, report this to a consultant.\n", apcUid[iWho]);
X		}
X		(void)endgrent();
X
X	unknown:
X		if ('s' == fShell) {
X			printf("%s%s=\'%s\'; export %s%s\n", acSub, acBuf, pcIndex, acSub, acBuf);
X		} else {
X			printf("setenv %s%s \'%s\'\n",acSub, acBuf, pcIndex);
X		}
X		pcKopOut = "done";
X	}
}
X
/*
X * compare two strings for qsort
X */
static int
q_compare(ppch1, ppch2)
X	char **ppch1, **ppch2;
{
X	return strcmp(*ppch1, *ppch2);
}
X
X
/*
X * Move environment variables beginning with SUBPREFIX to the		(aho)
X * beginning of the environment (using swaps).  Sort the
X * SUBPREFIX variables. Set the global variable iCourses to
X * the number of submit variables found.
X * If we find a SUBPREFIX variable in the environment for a course we
X * don't find on the course list, we throw it out.
X *
X * for SUBPREFIX="SUB_"
X *	SUB_cs300=d1s1
X *	SUB_cs400=morning
X *	SUB_cs400=930kevin
X *
X * note: we assume we can write on what environ points to
X */
static int
FixEnvironment()
{
X	register char **ppch;
X	register char *pcTemp;
X	register char *pcIndex;
X
X	iCourses = 0;
X	for (ppch = environ; (char *)0 != *ppch; ++ppch) {
X		if (0 == strncmp(acSub, *ppch, sizeof(acSub)-1) &&
X		(char *)0 != (pcIndex = strchr(sizeof(acSub)-1 + *ppch, '='))) {
X			*pcIndex = '\000';
X			if (-1 != VerifyCourse(sizeof(acSub)-1 + *ppch)) {
X				pcTemp = environ[iCourses];
X				environ[iCourses] = *ppch;
X				*ppch = pcTemp;
X				++iCourses;
X			}
X			*pcIndex = '=';
X		}
X	}
X	if (1 < iCourses) {
X		qsort((char *)environ, iCourses, sizeof(char *), q_compare);
X	}
}
X
X
/*
X * Search the subscribed courses (the SUB_<course> environment 	    (ksb/aho)
X * varibles) for a single course this submission is to.  If
X * there is more than one subscribed course, use pcGuess to
X * arbitrate.  If we fail, return (char *)0.
X *
X * note: we assume that FixEnvironment() has been called before us
X */
static char *
Which(pcGuess)
char *pcGuess;
{
X	register char **ppch;
X	register char *pcCourse;
X	register int i;
X
X	pcCourse = (char *)0;
X	for (ppch = environ; ppch < & environ[iCourses]; ++ppch) {
X		/* two course in env, no -c, use Ask from main
X		 */
X		if ((char *)0 != pcCourse) {
X			return (char *)0;
X		}
X		pcCourse = sizeof(acSub)-1 + *ppch;
X		if ((char *)0 == pcGuess) {
X			continue;
X		}
X		i = strlen(pcGuess);
X		if (0 == strncmp(pcGuess, pcCourse, i) && '=' == pcCourse[i]) {
X			return pcCourse;
X		}
X		pcCourse = (char *)0;
X	}
X	return pcCourse;
}
X
/*
X * List the courses the user is subscribed to which are also in the
X * course list.
X */
static void
ListSubscribed(fp)
FILE *fp;
{
X	register char **ppchEnv;
X	register char *pc;
X	register int chSep = ':';
X
X	if (0 == iCourses) {
X		fprintf(fp, "You are not subscribed to any courses.\n");
X		return;
X	}
X	fprintf(fp, "You are subscribed to");
X	for (ppchEnv = environ; ppchEnv < & environ[iCourses]; ++ppchEnv) {
X		if ((char *)0 == (pc = strchr(sizeof(acSub)-1 + *ppchEnv, '='))) {
X			continue;
X		}
X		*pc = '\000';
X		fprintf(fp, "%c %s", chSep, sizeof(acSub)-1 + *ppchEnv);
X		*pc = '=';
X		chSep = ',';
X	}
X	putc('\n', fp);
}
X
/*
X * get the words of wisdom from the user				(ksb)
X * strip leading white space, etc
X */
char *
GetWord(pcBuf, iLen, pcDef)
char *pcBuf, *pcDef;
int iLen;
{
X	register char *pcWhite, *pcCopy;
X	register int l;
X
X	fflush(stdout);
X	pcBuf[0] = '\n';
X	if ((char *)0 == fgets(pcBuf, iLen, stdin))
X		return (char *)0;
X	pcWhite = pcBuf;
X	while (isspace(*pcWhite) && '\n' != *pcWhite)
X		++pcWhite;
X
X	pcCopy = pcBuf;
X	l = iLen;
X	while (l-- > 0 && '\n' != (*pcCopy = *pcWhite))
X		++pcCopy, ++pcWhite;
X
X	if (pcCopy == pcBuf)
X		(void)strncpy(pcBuf, pcDef, iLen);
X	else
X		*pcCopy = '\000';
X	return pcBuf;
}
X
/*
X * We couldn't figure out the course, so now we ask the user		(aho)
X * interactively.  We return the course and div/sec as
X * follows: cs100=d1s1
X *
X * invariant: we do not return (char *)0
X */
char *
AskCourse()
{
X	register char **ppch;
X	auto struct group *grpMe;
X	static char acBuf[MAXCOURSENAME + MAXDIVSEC];
X	static char acDef[MAXCOURSENAME + MAXDIVSEC];
X
X	/* if our groupname is a valid course, use it as the default
X	 * else if our groupname is the group for a course, use that
X	 */
X	acDef[0] = '\000';
X	(void)setgrent();
X	if ((struct group *)0 != (grpMe = getgrgid(getgid()))) {
X		(void)strcpy(acDef, grpMe->gr_name);
X		if (-1 == VerifyCourse(acDef)) {
X			register int i;
X			for (i = 0; (char *)0 != apcCourse[i]; ++i) {
X				if (0 == strcmp(apcGroup[i], acDef))
X					break;
X			}
X			if ((char *)0 != apcCourse[i]) {
X				(void)strcpy(acDef, apcCourse[i]);
X			} else {
X				acDef[0] = '\000';
X			}
X		}
X	}
X	(void)endgrent();
X
X	switch (iCourses) {
X	case 0:
X		printf("\
You are not suscribed to any valid course, here is a list of the courses\n\
you may use currently:\n");
X		ListCourses(stdout);
X		/* use the first course as the default
X		 */
X		if ('\000' == acDef[0] && (char *)0 != apcCourse[0]) {
X			(void)strcpy(acDef, apcCourse[0]);
X		}
X		break;
X	default:
X		ListSubscribed(stdout);
X		/* use the first subscribed course as the default
X		 */
X		if ('\000' == acDef[0]) {
X			register int i;
X			register char *pcIndex;
X
X			pcIndex = environ[0] + sizeof(acSub)-1;
X			for (i = 0; '=' != pcIndex[i]; ++i) {
X				acDef[i] = pcIndex[i];
X			}
X			acDef[i] = '\000';
X		}
X		printf("\nIf the course you wish to submit to is not on the above list, try it anyway\n");
X		break;
X	}
X	printf("Enter course name? [%s] ", acDef);
X
X	if ((char *)0 == GetWord(acBuf, MAXCOURSENAME, acDef)) {
X		fprintf(stderr, "%s: invalid course name\n", progname);
X		exit(6);
X	}
X	if (-1 == VerifyCourse(acBuf)) {
X		fprintf(stderr, "%s: cannot find \"%s\" in the course list\n", progname, acBuf);
X		exit(7);
X	}
X	/*
X	 * if the user is subscribed to this course, don't ask him
X	 * for divsion/section, return the info here
X	 */
X	for (ppch = environ; ppch < & environ[iCourses]; ++ppch) {
X		if (0 == strncmp(acBuf, sizeof(acSub)-1 + *ppch, strlen(acBuf))) {
X			return sizeof(acSub)-1 + *ppch;
X		}
X	}
X	return acBuf;
}
X
/*
X * read the given project list file and store in apcProjects		(aho)
X */
ReadProjects(pcFile)
char *pcFile;
{
X	register FILE *fp;
X	register char *pc;
X	register int iProject;
X	auto char acLine[MAXCHARS];
X
X	iProject = 0;
X	if ((FILE *)0 == (fp = fopen(pcFile, "r"))) {
X		printf("%s: can\'t find project list file in %s\n", progname, pcFile);
X		exit(9);
X	}
X	while ((char *)0 != fgets(acLine, MAXCHARS, fp)) {
X		if ('\n' == acLine[0] || '#' == acLine[0])
X			continue;
X		if (iProject >= MAXPROJECTS) {
X			/*ZZZ bogus */
X			fprintf(stderr, "%s: too many projects in project file\n", progname);
X			exit(10);
X		}
X		pc = acLine + strlen(acLine) - 1;
X		while (isspace(*pc)) {
X			*pc-- = '\000';
X		}
X		apcProjects[iProject++] = strsave(acLine);
X	}
X	apcProjects[iProject] = (char *)0;
X	(void)fclose(fp);
}
X
/*
X * Determine the project name for this submission.  If we can't figure it out,
X * we print errors and exit.
X */
static char *
ProjName(pcGuess, pcClass)
X	char *pcGuess, *pcClass;
{
X	register char **ppch;
X
X	for (ppch = apcProjects; (char *)0 != *ppch; ++ppch) {
X		switch (**ppch) {
X		case '=':
X			if ((char *)0 == pcGuess) {
X				return 1 + *ppch;
X			}
X			/*FALLTHROUGH*/
X		case '+':
X			if ((char *)0 == pcGuess) {
X				continue;
X			}
X			if (0 == strcasecmp(1+*ppch, pcGuess)) {
X				return 1 + *ppch;
X			}
X			break;
X		default:
X			if ((char *)0 == pcGuess) {
X				continue;
X			}
X			if (0 == strcasecmp(1+*ppch, pcGuess)) {
X				fprintf(stderr, "%s: submissions for ", progname);
X				if (isdigit(**ppch)) {
X					fprintf(stderr, "project ");
X				}
X				fprintf(stderr, "%s have been turned off.\n", *ppch);
X				exit(11);
X			}
X		}
X	}
X	if ((char *)0 == pcGuess) {
X		fprintf(stderr, "%s: no current project for %s\n", progname, pcClass);
X		fprintf(stderr, "Use \"%s -l\" for a list of projects in a course.\n", progname);
X		exit(12);
X	}
X	fprintf(stderr, "%s: ", progname);
X	if (isdigit(**ppch)) {
X		fprintf(stderr, "Project ");
X	}
X	fprintf(stderr, "\"%s\" is not a current project for submission in %s.  Use\n\"project -l\" for a list of projects in this course.\n", pcGuess, pcClass);
X	exit(13);
X	/*NOTREACHED*/
}
X
X
/*
X * list the valid projects to stdout; the argument pcClass is for
X * printing headers/error messages
X *
X * (assume that ReadProjects has been called)
X */
static void
ListProjects(pcCourse)
char *pcCourse;
{
X	register char **ppch;
X
X	if ((char *)0 == apcProjects[0]) {
X		printf("There are no current projects for %s\n", pcCourse);
X		return;
X	}
X	printf("Current projects for %s:\n", pcCourse);
X	for (ppch = apcProjects; (char *)0 != *ppch; ++ppch) {
X		printf("%-16s", 1+*ppch);
X		switch (**ppch) {
X		case '=':
X			printf(" on (current)\n");
X			break;
X		case '+':
X			printf(" on (alternate)\n");
X			break;
X		default:
X			printf(" off\n");
X			break;
X		}
X	}
}
X
X
/*
X * To prevent people from making clever paths to the turnin dirctory
X * with empty components, dots, and slashes, call this routine
X */
static void
GoodFile(pcName, pcWhat)
char *pcName, *pcWhat;
{
X	if ((char *)0 == pcName || '\000' == pcName[0]) {
X		fprintf(stderr, "%s: no %s specified\n", progname, pcWhat);
X		exit(13);
X	}
X	if ((char *)0 != strchr(pcName, '.')) {
X		fprintf(stderr, "%s: will not allow %ss with \'.\' in them\n", progname, pcWhat);
X		exit(14);
X	}
X	if ((char *)0 != strchr(pcName, '/')) {
X		fprintf(stderr, "%s: will not allow %ss with \'/\' in them\n", progname, pcWhat);
X		exit(15);
X	}
}
X
/*
X * show an index of the submitted files					(ksb)
X * prefer the uncompressed file
X *	tar tvf - <file
X *
X * but show compressed if no other choice
X *	compress -d <file | tar tvf -
X */
void
ShowTar(pcFile)
char *pcFile;
{
X	auto char *nargv[4];
X	auto int pid, n;
X	auto int fds[2];
X
X	fflush(stdout);
X	fflush(stderr);
X	switch (pid = fork()) {
X	case 0:
X		close(0);
X		if (0 != open(pcFile, O_RDONLY, 0600)) {
X			strcat(pcFile, acDotZ);
X			if (0 != open(pcFile, O_RDONLY, 0600)) {
X				fprintf(stderr, "%s: open: %s: %s\n", progname, pcFile, strerror(errno));
X				exit(1);
X			}
X			if (0 != pipe(fds)) {
X				fprintf(stderr, "%s: pipe: %s\n", progname, strerror(errno));
X				exit(1);
X			}
X			switch (fork()) {
X			case -1:
X				fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
X				exit(1);
X			case 0:		/* zcat of tar file.Z		*/
X				close(1);
X				dup(fds[1]);
X				close(fds[1]);
X				close(fds[0]);
X				nargv[0] = "compress";
X				nargv[1] = "-d";
X				nargv[2] = (char *)0;
X				execve(acCompress, nargv, environ);
X				/* exec zcat */
X				exit(1);
X			default:	/* |tar fvf -			*/
X				close(0);
X				dup(fds[0]);
X				close(fds[0]);
X				close(fds[1]);
X				break;
X			}
X		}
X		nargv[0] = "tar";
X		nargv[1] = "-tvf";
X		nargv[2] = "-";
X		nargv[3] = (char *)0;
X		execve(acTar, nargv, environ);
X		fprintf(stderr, "%s: execve: %s: %s\n", progname, acTar, strerror(errno));
X		exit(21);
X		break;
X	case -1:
X		fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
X		return;
X	default:
X		break;
X	}
X	while (-1 != (n = wait((union wait *)0)) && pid != n)
X		;
}
X
static char acHelp[] =
X	"%s: usage [-hlsvV] [-c course[=section]] [-p project] [files]\n";
static char *apcHelp[] = {
X	"c course   specify the course to use",
X	"h	   print this help message",
X	"i	   output shell commands to set user\'s environment",
X	"l	   list projects active for the selected course",
X	"p project  specify the project to submit to",
X	"s	   list courses the envoker is enrolled in",
X	"v	   be verbose (also passed to tar)",
X	"V	   show version information",
X	"files	   list of files to submit",
X	(char *)0
};
X
/*
X * output a long usage message						(ksb)
X */
void
usage(fp)
FILE *fp;
{
X	register char **ppc;
X
X	fprintf(fp, acHelp, progname);
X	for (ppc = apcHelp; (char *)0 != *ppc; ++ppc)
X		fprintf(fp, "%s\n", *ppc);
}
X
/*
X * make an archive and transfer to instructors account			(ksb)
X */
int
main(argc, argv)
int argc;
char **argv;
{
X	register int i, n, pid;
X	register char *pcCourse;
X	register char **nargv;
X	register char *pcProj;
X	register char *pcDiv;
X	auto char *pcUser;
X	auto struct passwd *ppw;
X	auto struct group *pgr;
X	static char acPath[MAXPATHLEN+1];
X	static char acYes[MAXCHARS];
X	static int iWho;
X	static struct stat stat_buf;
X	static union wait wait_buf;
X	static char acOnly[] = "%s: only one of -i, -l,or  -s may be given\n";
X
X	/* RUNS SET UID ROOT */
X
X	if ((char *)0 == (progname = strrchr(argv[0], '/')))
X		progname = argv[0];
X	else
X		++progname;
X
X	/*
X	 * command line parser
X	 */
X	pcCourse = (char *)0;
X	pcProj = (char *)0;
X	eWhat = UNKNOWN;
X	while (EOF != (i = getopt(argc, argv, "hilsp:c:vV"))) {
X		switch(i) {
X		case 'h':
X			usage(stdout);
X			exit(0);
X		case 'i':
X			if (UNKNOWN != eWhat) {
X				fprintf(stderr, acOnly, progname);
X				exit(1);
X			}
X			eWhat = INITUSER;
X			break;
X		case 'l':
X			if (UNKNOWN != eWhat) {
X				fprintf(stderr, acOnly, progname);
X				exit(1);
X			}
X			eWhat = LISTPROJ;
X			break;
X		case 's':
X			if (UNKNOWN != eWhat) {
X				fprintf(stderr, acOnly, progname);
X				exit(1);
X			}
X			eWhat = LISTSUB;
X			break;
X		case 'V':
X			if (UNKNOWN != eWhat) {
X				fprintf(stderr, acOnly, progname);
X				exit(1);
X			}
X			eWhat = VERSION;
X			break;
X		case 'v':
X			fVerbose = 1;
X			break;
X		case 'p':
X			pcProj = optarg;
X			break;
X		case 'c':
X			pcCourse = optarg;
X			break;
X		default:
X			fprintf(stderr, acHelp, progname);
X			exit(1);
X			break;
X		}
X	}
X	if (optind == argc && UNKNOWN == eWhat && 0 == fVerbose) {
X		fprintf(stderr, acHelp, progname);
X		exit(1);
X	}
X
X	/*
X	 * Read submission information from the user's environment, from
X	 * the instructors project list file, and from our data base, and
X	 * check it all for consistency.
X	 */
X	ReadDB();
X	if (INITUSER == eWhat) {
#ifdef HP_UX
X		if (-1 == setresuid(-1, getuid(), -1)) {
X			fprintf(stderr, "%s: setresuid: %s\n", progname, strerror(errno));
#else
X		if (-1 == seteuid(getuid())) {
X			fprintf(stderr, "%s: seteuid: %s\n", progname, strerror(errno));
#endif
X			exit(20);
X		}
X		DoInit(argc-optind, argv+optind, pcCourse);
X		exit(0);
X	}
X	if (VERSION == eWhat) {
X		printf("%s: %s\n", progname, RCSId);
X		exit(0);
X	}
X
X	FixEnvironment();
X	if (LISTSUB == eWhat) {
X		if (0 == iCourses) {
X			printf("You are not subscribed to any courses.\n");
X			exit(1);
X		}
X		ListSubscribed(stdout);
X		exit(0);
X	}
X
X	if ((char *)0 == pcCourse && iCourses > 0) {
X		pcCourse = Which(pcCourse);
X	}
X	if ((char *)0 == pcCourse) {
X		printf("\n\
For this %s, you may enter the course information interactively.\n",  LISTPROJ == eWhat ? "listing" : "submission");
X		pcCourse = AskCourse();
X	}
X	if ((char *)0 != (pcDiv = strchr(pcCourse, '='))) {
X		*pcDiv++ = '\000';
X	}
X
X	if (-1 == (iWho = VerifyCourse(pcCourse))) {
X		fprintf(stderr, "%s: cannot find your course (%s) in course list\n", progname, pcCourse);
X		exit(7);
X	}
X
X	(void) setpwent();
X	if ((struct passwd *)0 == (ppw = getpwnam(apcUid[iWho]))) {
X		printf("%s: owner \"%s\" doesn\'t exist on this machine\n", progname, apcUid[iWho]);
X		exit(17);
X	}
X	(void) setgrent();
X	if ((struct group *)0 == (pgr = getgrnam(apcGroup[iWho]))) {
X		printf("%s: group \"%s\" doesn\'t exist on this machine\n", progname, apcGroup[iWho]);
X		exit(17);
X	}
X
X	(void)sprintf(acPath, "%s/%s/%s", ppw->pw_dir, apcDir[iWho], PROJLIST);
X	ReadProjects(acPath);
X	if (LISTPROJ == eWhat) {
X		ListProjects(pcCourse);
X		exit(0);
X	}
X
X	pcProj = ProjName(pcProj, pcCourse);
X	GoodFile(pcProj, "project");
X
X	if ((char *)0 == pcDiv || '\000' == pcDiv[0]) {
X		static char acDiv[MAXDIVSEC+1];
X		pcDiv = acDiv;
X		*pcDiv = '\000';
X		FindSection(iWho, stdout, pcDiv);
X	}
X	GoodFile(pcDiv, "division/section");
X
X	(void)sprintf(acPath, "%s/%s/%s", ppw->pw_dir, apcDir[iWho], pcProj);
X	if (0 != strcmp(acSectIgnore, pcDiv) || 0 != strcmp(acSectIgnore, apcSections[iWho])) {
X		(void)strcat(acPath, "/");
X		(void)strcat(acPath, pcDiv);
X	}
X	/* become the instructor
X	 */
#ifdef HP_UX
X		if (-1 == setresgid(-1, pgr->gr_gid, -1) || -1 == setresuid(-1, ppw->pw_uid, -1)) {
X		fprintf(stderr, "%s: setres{u,g}id: %s\n", progname, strerror(errno));
#else
X	if (-1 == setegid(pgr->gr_gid) || -1 == seteuid(ppw->pw_uid)) {
X		fprintf(stderr, "%s: sete{u,g}id: %s\n", progname, strerror(errno));
#endif
X		exit(20);
X	}
X
X	if (0 != stat(acPath, & stat_buf)) {
X		fprintf(stderr, "%s: stat: %s: %s\n", progname, acPath, strerror(errno));
X		exit(18);
X	}
X	strcat(acPath, "/");
X
X	/* preserve $USER or $LOGNAME if uid of that name matches our uid
X	 * this is a gratuitous feature no one will ever use... (ksb)
X	 */
X	if ((char *)0 == (pcUser = getenv("USER"))) {
X		pcUser = getenv("LOGNAME");
X	}
X	if ((char *)0 != pcUser && (struct passwd *)0 != (ppw = getpwnam(pcUser)) && getuid() == ppw->pw_uid) {
X		strcat(acPath, pcUser);
X	} else if ((struct passwd *)0 != (ppw = getpwuid(getuid()))) {
X		strcat(acPath, ppw->pw_name);
X	} else {
X		fprintf(stderr, "%s: getpwuid(%d): %s\n", progname, getuid(), strerror(errno));
X		exit(1);
X	}
X
X	if (0 != fVerbose && optind == argc) {
X		ShowTar(acPath);
X		exit(0);
X	}
#if CONFIRM
X	if (!isatty(fileno(stdin)) || !isatty(fileno(stdout))) {
X		/* do not check, we are not interactive */;
X	} else if (0 == stat(acPath, & stat_buf)) {
X		printf("%s: overwrite previously submitted project? [yn] ", progname);
X		if ((char *)0 != GetWord(acYes, MAXCHARS, "yes") && ! ('y' == acYes[0] || 'Y' == acYes[0]))
X			exit(0);
X	} else {
X		strcat(acPath, acDotZ);
X		if (0 == stat(acPath, & stat_buf)) {
X			printf("%s: overwrite previously submitted project? [yn] ", progname);
X			if ((char *)0 != GetWord(acYes, MAXCHARS, "yes") && ! ('y' == acYes[0] || 'Y' == acYes[0]))
X				exit(0);
X		}
X		acPath[strlen(acPath)-sizeof(acDotZ)+1] = '\000';
X	}
#endif
X	(void)unlink(acPath);	/* no symlink to another file	*/
X
X		/*
X		** trash any former compressed files
X		*/
X	strcat(acPath, ".Z");
X	(void)unlink(acPath);
X	acPath[strlen(acPath)-2] = '\000';
X
X	if ((char **)0 == (nargv = (char **)calloc(argc+6, sizeof(char *)))) {
X		fprintf(stderr, "%s: out of memory\n", progname);
X		exit(18);
X	}
X	nargv[0] = "tar";
X	nargv[1] = "chbf";
X	nargv[2] = "1";
X	nargv[3] = "-";
X	for (i = optind; i < argc; ++i) {	/* copy args for tar	*/
X		if ('/' == *argv[i]) {
X			fprintf(stderr, "%s: full pathnames not allowed on submitted files\n", progname);
X			exit(19);
X		}
X		nargv[i+4-optind] = argv[i];
X	}
X
X	fflush(stdout);
X	fflush(stderr);
X	switch (pid = fork()) {			/* child is tar		*/
X	case -1:
X		fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
X		exit(21);
X	case 0:
X		(void)umask(0077);
X		close(1);
X		if (1 != open(acPath, O_CREAT|O_TRUNC|O_WRONLY, 0600)) {
X			fprintf(stderr, "%s: open: %s: %s\n", progname, acPath, strerror(errno));
X			exit(22);
X		}
X		if (-1 == setuid(getuid()) || -1 == setgid(getgid())) {
X			fprintf(stderr, "%s: set{g,u}id: %s\n", progname, strerror(errno));
X			exit(1);
X		}
X		execve(acTar, nargv, environ);
X		fprintf(stderr, "%s: exec: %s: %s\n", progname, acTar, strerror(errno));
X		exit(21);
X	default:
X		while (-1 != (n = wait(& wait_buf)) && pid != n)
X			;
X		if (-1 == n) {
X			fprintf(stderr, "%s: wait: %s\n", progname, strerror(errno));
X			exit(22);
X		}
X		if (0 != wait_buf.w_retcode) {
X			fprintf(stderr, "%s: tar failed (%d)\n", progname, wait_buf.w_retcode);
X			exit((int)wait_buf.w_retcode);
X		}
X		break;
X	}
X
X	/*still instructor here*/
X	if (0 != stat(acPath, & stat_buf)) {
X		fprintf(stderr, "%s: stat: %s: %s\n", progname, acPath, strerror(errno));
X		exit(18);
X	}
X	if (0 != fVerbose) {
X		ShowTar(acPath);
X	}
X	if (stat_buf.st_size > (off_t) BIGTAR) {
X		printf("Compressing submitted files... please wait\n");
X		fflush(stdout);
X		nargv[0] = "compress";
X		nargv[1] = "-f";
X		nargv[2] = acPath;
X		nargv[3] = (char *)0;
X		switch (pid = fork()) {		/* child is compress	*/
X		case -1:
X			fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
X			exit(21);
X		case 0:
X			(void)umask(0077);
X			execve(acCompress, nargv, environ);
X			fprintf(stderr, "%s: execve: %s: %s\n", progname, acCompress, strerror(errno));
X			exit(21);
X		default:
X			while (-1 != (n = wait(& wait_buf)) && pid != n)
X				;
X			if (-1 == n) {
X				fprintf(stderr, "%s: wait: %s\n", progname, strerror(errno));
X				exit(22);
X			}
X			if (0 != wait_buf.w_retcode) {
X				fprintf(stderr, "%s: compress failed (%d)\n", progname, wait_buf.w_retcode);
X				exit((int)wait_buf.w_retcode);
X			}
X			break;
X		}
X	} else {
X		/* remove any old, larger file */
X		(void) strcat(acPath, acDotZ);
X		(void) unlink(acPath);
X	}
X
X	printf("Your files have been submitted to %s, ", pcCourse);
X	if (isdigit(*pcProj)) {
X		printf("project ");
X	}
X	printf("%s for grading.\n", pcProj);
X
X	(void) endpwent();
X	(void) endgrent();
X	exit(0);
}
Purdue
chmod 0444 turnin/turnin.c ||
echo 'restore of turnin/turnin.c failed'
Wc_c="`wc -c < 'turnin/turnin.c'`"
test 30395 -eq "$Wc_c" ||
	echo 'turnin/turnin.c: original size 30395, current size' "$Wc_c"
fi
# ============= project/project.1l ==============
if test ! -d 'project'; then
    echo 'x - creating directory project'
    mkdir 'project'
fi
if test -f 'project/project.1l' -a X"$1" != X"-c"; then
	echo 'x - skipping project/project.1l (File already exists)'
else
echo 'x - extracting project/project.1l (Text)'
sed 's/^X//' << 'Purdue' > 'project/project.1l' &&
.TH PROJECT 1L PUCC
.SH NAME
project \- control electronic submission
.SH SYNOPSIS
\fBproject\fP [\fB\-dehilnqrvV\fP] [\fB\-c\fP\fIcourse\fP] [\fB\-G\fP\fIcmd\fP] [\fB\-g\fP\fIcmd\fP] [\fIproject\-name\fP]
.br
.SH DESCRIPTION
.I Project
first determines for which class the invoking user is receiving projects.
If \fIproject\fP does not find the invoker's login name in its database,
it aborts.  Otherwise,
.I project
looks to see what projects this course has and which one is current.
.PP
If
.I project
is run without options and no 
.IR project\-name ,
it displays a help message.  If
.I project
is initiated without options but with a 
.IR project\-name ,
it will look for
the submission directory \fIsubmit\fP
(note: this directory may have any name)
in the home directory of the user.
If \fIsubmit\fP does not exist,
.I project
will create one.
.I Project
then modifies the file \fIProjlist\fP to enable submissions for the named
project and
creates a
subdirectory in \fIsubmit\fP with the same name as the project being
enabled
to receive the submitted projects. 
Submission permissions for the new project are unchanged.
.PP
Project names may not have a period in them, but they may contain letters,
numbers, or other marks of punctuation.
.PP
.I Project
invoked with options but without a
.IR project\-name ,
uses the file \fIProjlist\fP in the submission directory to determine
the current project name.
.PP
The invoker may use the options below to disable, enable, grade, and remove
the submitted files.  The submitted files are stored in a tar format file
named for the login name which submitted them (see turnin(5L)).
.SH OPTIONS
.TP
.BI \-c course
Project, when run by the superuser, will use this \fIcourse\fP to
select the effective user id.
Normal users may use this option to administer more than one course.
(If the selected user doesn't exist on this machine, project will exit
nonzero.)
.TP
.B \-d
Disable submissions for the current project.
.TP
.B \-e
Enable submissions for the current project and make the current project the
default project.
.TP
.B \-l
Enable submissions for the current project but do not change the default
project.
.TP
.BI \-G cmd
Execute the given command once for every division of the class.
Note that the \fIcmd\fP must be in the search path of the invoker.
See \fB\-G\fP below for escape sequences, except %u expands to ``*''.
If both \fB\-g\fP and \fB\-G\fP are given all the \fB\-g\fP commands
for a division are done before the \fB\-G\fP command for that division.
.TP
.BI \-g cmd
Execute the given command once for every submitted assignment.
Note that the \fIcmd\fP must be in the search path of the invoker.
Several escape sequences are used to pass arguments to
.I cmd ,
all begin with a percent sign (%), they are:
.br
.TS
rw(8) l.
h	home directory of the instructor
t	turnin directory name
p	current project
d	division/section identifier for the current student
u	current students login name
s	the full path name of the associated tar file
%	yields a percent sign
.TE
.TP
.B \-h
Display a summary of 
.I project's
options.
.TP
.B \-i
\fBProject\fP will ask the user about how they wish to use the
electronic submission system and initialize their account.
Note that mail is sent to a superuser to do the final configuration,
so this command is not immediate.
An example of this option may be found in /usr/doc/local/project/install.
.TP
.B \-n
Print what would be done but don't do it.
This option implies the \-\fBv\fP option.
.TP
.B \-o
Display a status line on the terminal.
.TP
.B \-q
\fBProject\fP confirms the command then removes all the project
related files in the user's account.
.TP
.B \-r
Remove
.I all
files associated with the current project number.
.I Project
will ask you to verify that you really want to do this before proceeding.
.TP
.B \-v
Verbose.  Print shell commands as they are executed.
.TP
.B \-V
Show which version of \fIproject\fP is running.
.SH EXAMPLES
.TP
project \-e project4
Enable submissions for project 4.
.TP
project \-d parser
Turn off all new submissions for the parser project.
.TP
project \-g "echo %u"
List all the login names of the students that have submitted something for
the current project.
.TP
project \-g \*(lq\fIgradeit\fP %s\*(rq 4
Grade all submitted files with a shell script named \fIgradeit\fP.
.sp
Note that the script is usually passed the full path name to the
submitted files which are kept in a single tar format file.  The script
that is doing the grading will make a temporary directory to unpack
and compile the submitted code (see FILES).
.TP
project \-g \*(lqif [ d1s1 = %d ] ; then gradeit %s ; fi\*(rq
Grad only division 1 section 2 with a grading script (gradeit).
.sp 1
The use of the shell if statement in the \fIcmd\fP is to prevent
the unnecessary invocation of the grading script. The script, however,
could do the comparison itself using exit if the division/section
identifier did not match an ideal one.
.sp 1
For an instructor to disable submission for project 3 at around
midnight on October 15 for all hosts mentor.cc, expert.cc,
sage.cc, (from sage.cc):
.br
X	at \-m 0005 Oct 15
.br
X	at> project \-d project3
.br
X	at> rsh mentor project \-d project3
.br
X	at> rsh expert project \-d project3
.br
X	at> ^D
.br
(these commands could be put into a file).
.br
.TP
project \-i
Set up this account to receive submissions.
.TP
project \-V
project: $\&Id: project.c,v 4.6 90/07/02 14:18:32 ksb Exp $
.br
project: default directory name: submit
.SH FILES
/usr/doc/local/project/template is a sample shell script to grade Pascal programs.
.br
/usr/doc/local/project/install is a sample installation session.
.SH BUGS
.PP
.I Project
.B must
be run on every machine which you wish students to submit files.
.sp
Attempts to copy the project submission directory or the
\fIProjlist\fI file to other hosts will cause problems when
students use
.IR turnin (1L)
to turn in their projects.
.SH SEE ALSO
at(1), turnin(1L), sh(1), tar(1), environ(7)
Purdue
chmod 0444 project/project.1l ||
echo 'restore of project/project.1l failed'
Wc_c="`wc -c < 'project/project.1l'`"
test 5973 -eq "$Wc_c" ||
	echo 'project/project.1l: original size 5973, current size' "$Wc_c"
fi
# ============= turnin/INSTALL ==============
if test -f 'turnin/INSTALL' -a X"$1" != X"-c"; then
	echo 'x - skipping turnin/INSTALL (File already exists)'
else
echo 'x - extracting turnin/INSTALL (Text)'
sed 's/^X//' << 'Purdue' > 'turnin/INSTALL' &&
Turnin(1L) and project(1L) provide administration of electronic
submissions for a large number of classes.  These tools may be applied
to any type of course (programming, art & design, word processing, etc.)
which requires `students' to submit `projects' to an `instructor'.
X
A `student' is any login name on a UNIX system.  Multiple logins under
the same uid are handled correctly.  The environment variables USER and
LOGNAME are consulted (in that order) to determine which login name is
submitting the file.
X
A `project' is a broad term for a group of submissions, one per login
name, which will be collected over time.  These may then be processed
by running a shell command for each project.   Submitted files are
stored in a tar(1) format archive.  Large archives are kept compressed,
but nether user nor administrator will see this detail.
X
An `instructor' is a supervising login name.  This login is able to
limit the collection time for any `project' and issue grade commands.
X
The user interface to turnin(1L) is quite simple and obvious.  The user
simply runs
X
X	$ turnin files
X
to submit files.  If the request is ambiguous turnin will request
information from stdin.  Command line options are provides to suppress
such prompting.  Some profs use a simple (1 line) shell script to force
the course option on the user.  For example cs444 might use a script
`sub444' which read:
X
X	#!/bin/sh turnin -c cs444 "$@"
X
to `simplify' the user environment.
X
The user interface to project(1L) also quite simple.  The administrator
may enable a project with the -e option:
X
X	project -e btrees
X
would enable a project called `btrees' and disable a project with the
-d option:
X
X	project -d queues
X
After submissions for a project are complete (or even while some are
still coming in) the instructor may use the -g option to scan the
submitted files with a shell command.  The shell command is run once
for each login name who has submitted files.  Some percent (%) escapes
are substituted before the command is given to the shell.
X
X	project -g "echo %u" queues
X
would scan the project `queues' and echo the login name of each user
who has submitted files.
X
Of course there are other options to project, but these three are by
far the most common.
X
X
Setting up the system.
X
The superuser must install 3 files in the user-accessible portion of
the UNIX system.
X
The binaries for project and turnin should be installed in the system's
default search path in something like /usr/local/bin or /usr/custom/bin.
The project binary should be mode 0755 (or 0711), the turnin binary
should be mode 04755 (or 04711) and owned by the superuser.  This is
because turnin must set-effective-uid to the target instructor on the
fly.  (It could be setuid to a single instructor, but the program is
useful to many instructors.)
X
There is a configuration file which should be superuser-writable only
(mode 0644, owned by the superuser).  This file tells the system which
logins are available to accept projects.  This file is installed in a
lib directory, usually /usr/local/lib or /usr/custom/lib, and is called
turnin.cf.  This file contains a colon-separated list of data very much
like the passwd file; there is a manual page for these fields
(turnin.cf.5L).
X
Purdue
chmod 0444 turnin/INSTALL ||
echo 'restore of turnin/INSTALL failed'
Wc_c="`wc -c < 'turnin/INSTALL'`"
test 3242 -eq "$Wc_c" ||
	echo 'turnin/INSTALL: original size 3242, current size' "$Wc_c"
fi
# ============= turnin/turnin.1l ==============
if test -f 'turnin/turnin.1l' -a X"$1" != X"-c"; then
	echo 'x - skipping turnin/turnin.1l (File already exists)'
else
echo 'x - extracting turnin/turnin.1l (Text)'
sed 's/^X//' << 'Purdue' > 'turnin/turnin.1l' &&
.\" $Compile: ${nroff-nroff} -man %f |${PAGER-${more-more}}
.\" $Laser: ${ltroff-ltroff} -man %f
.TH TURNIN 1L
.UC 4
.SH NAME
turnin \- electronically submit files for grading
.SH SYNOPSIS
.B turnin
[\fB\-hilsvV\fP] [\fB\-c\fP\fIcourse\fP] [\fB\-p\fP\fIproject\fP]
.I files
.br
.SH DESCRIPTION
.I Turnin
reads each of
.I files
in sequence and copies it to the directory designated by the course's
instructor.  If the file being read is a directory,
.I turnin
will recursively copy the entire contents of the directory.
.PP
In order for
.I turnin
the instructor must have submissions turned on, see \fIproject\fP(1L).
.PP
Any previous submissions for the current project are overwritten.
.SH OPTIONS
.TP
\-c \fIcourse\fR
Specify the course.  Must be used if the student is in more than one course
that uses \fIturnin\fR to collect assignments.  The student will be prompted
for the \fIcourse\fR if they are enrolled in more than one and this option
isn't used.
.TP
\-h
Outputs a terse usage message.
.TP
\-i
In this case \fIturnin\fP doesn't actually submit any files.
Any \fIfiles\fP given are taken to be course names for which to produce
envrironment settings.
If no \fIfiles\fP are given the user it prompted to list all the courses
he is currently enrolled in.
The shell commands output on stdout are appropriate to the users SHELL
environment variable.
.TP
\-l
Lists the projects for the specified class.  Projects open for submissions
and ones closed to submissions are listed.
.TP
\-p \fIproject\fR
Submit \fIfiles\fP to the specified \fIproject\fP. 
.TP
\-s
Lists the classes that the student is enrolled in.
.TP
\-v
.I Turnin
displays a \`tar tvf\' of the submitted files when this option is
specified (either with or without submitting any files).
.TP
\-V
Show the RCS Id for this version of \fIturnin\fP.
.SH EXAMPLES
.TP
turnin -c cs100 factorial.p
Submit the file \*(lqfactorial.p\*(rq to the instructor for cs100.
.TP
turnin -v Makefile tour.c passport.c
Submit three files to the default course
(or prompt for the course to submit to)
then display a list of the submitted files.
.TP
turnin -v
Display a list of the files this user most recently submitted
(for the default project).
.TP
turnin -v -p project1-late
Display a list of the files this user most recently submitted
for \*(lqproject1-late\*(rq.
.TP
turnin -i cs100 >>.login
Add a \fIcsh\fP(1) command to the users .login to set the users section
for cs100.
.TP
turnin -V
turnin: $\&Id: turnin.c,v 5.0 90/06/22 11:43:02 ksb Exp $
.SH "SEE ALSO"
csh(1), project(1L), sh(1), tar(1)
Purdue
chmod 0444 turnin/turnin.1l ||
echo 'restore of turnin/turnin.1l failed'
Wc_c="`wc -c < 'turnin/turnin.1l'`"
test 2553 -eq "$Wc_c" ||
	echo 'turnin/turnin.1l: original size 2553, current size' "$Wc_c"
fi
# ============= turnin.cf/turnin.5l ==============
if test ! -d 'turnin.cf'; then
    echo 'x - creating directory turnin.cf'
    mkdir 'turnin.cf'
fi
if test -f 'turnin.cf/turnin.5l' -a X"$1" != X"-c"; then
	echo 'x - skipping turnin.cf/turnin.5l (File already exists)'
else
echo 'x - extracting turnin.cf/turnin.5l (Text)'
sed 's/^X//' << 'Purdue' > 'turnin.cf/turnin.5l' &&
.\"
.TH TURNIN 5L
.UC 4
.SH NAME
turnin \- instructions for instructors who use
.IR turnin ( 1L )
and
.IR project ( 1L )
.br
.SH DESCRIPTION
.PP
.IR Turnin ( 1L )
is a program provided by Purdue University Computing Center to
aid instructors in the collecting of assignments from students.
To use
.IR turnin ,
the instructor must run the command
.sp 1
X	project -i
.sp 1
which will interactively prompt the instructor for more information about
the course in question.
The superuser will be notified that the instructor wishes to use
turnin and will add the user's login name to a configuration file.
.PP
The Computing Center may also build a .login (.profile) in each of
your students accounts to create a initial csh (sh) environment.
In this .login (.profile), there will be an environment variable
called \*(lqSUB_\fIcourse\fP\*(rq, where \fIcourse\fP is the course that
the student is enrolled in.
This environment variable is set to the student's division or section
to allow for large courses to be managed effectively.
For example the command:
.sp 1
X	setenv SUB_cs300 morning
.sp 1
might be used to put a given user's files into the `morning' section
for cs300 (this command would be in the user's generated .login).
.PP
The project command maintains a directory hierarchy in the course
administrator's account which \fIturnin\fP(1L) will modify to 
submit files for a login.
This directory tree is rooted at the name given to the project command
when it was run with the -i option and must be world readable.
The default name for the root of this tree is `submit'.
.PP
Here is a sample directory layout:
.sp 1
.ta 0.7i 1.4i 2.1i 2.8i 3.5i 4.2i 4.9i 5.6i
X	$ cd
.br
X	$ ls -aF submit
.br
X	\&./	\&../	\&.lock	Projlist	1/	2/
.br
X	$ ls -aF submit/1
.br
X	\&./	\&../	morning/	afternoon/
.br
X	$ ls -aF submit/1/morning
.br
X	\&./	\&../	dsg	ksb	mjb	mjs
.br
X	$ exit
.sp 1
.PP
At the top level there are 3 types of entries:  a lock file (`.lock')
which is \fIflock\fP(2)'d when project is running, a database file
(`Projlist') which contains the status information on the current
projects, and a directory for each active project.
.PP
Each active project directory contains a subdirectory for each division or
section of the course.
Each of these, in turn, contains one \fItar\fP(1) file for each login
name that has submitted files for this project.
.SH BUGS
A student may change sections (or divisions) by changing an environment
variable.
This generally leads to a grader not getting a submission from the student.
.SH AUTHOR
Kevin S Braunsdorf, PUCC (ksb@cc.purdue.edu, pur-ee!ksb, purdue!ksb)
.SH "SEE ALSO"
project(1L), turnin(1L), tar(1), turnin.cf(5L)
Purdue
chmod 0444 turnin.cf/turnin.5l ||
echo 'restore of turnin.cf/turnin.5l failed'
Wc_c="`wc -c < 'turnin.cf/turnin.5l'`"
test 2655 -eq "$Wc_c" ||
	echo 'turnin.cf/turnin.5l: original size 2655, current size' "$Wc_c"
fi
# ============= project/template ==============
if test -f 'project/template' -a X"$1" != X"-c"; then
	echo 'x - skipping project/template (File already exists)'
else
echo 'x - extracting project/template (Text)'
sed 's/^X//' << 'Purdue' > 'project/template' &&
#!/bin/sh
# $1 is our only argument, a %s (full path to tar file)
#  we were envoked from project -g with something like
#	project -g "grade7 %s" seven
X
# you should replace the "cs101" below with a unique course id (3-7 chars)
COURSE=cs101
X
# since we start off in the division/section directory of the student
# we need to get to someplace to make a temp directory
# ($$ is a randon number, see sh(1))
cd /tmp
mkdir $COURSE$$
cd $COURSE$$
X
# now output a message indicating who we are working on this file
echo "----------"
echo "--  $1"
echo "----------"
tar xvf $1
echo ""
X
# remove crufty a.out he may have submitted (by mistake, of course)
rm -f a.out core
X
# compile the project in your favorite language
pc -l *.p
#cc *.c
#f77 *.f
#fortran *.f
#make a.out
X
# we could check to see what langauge he used, like C or Pacal
#if [ -f Makefile -o -f makefile ]; then
#	make a.out
#elif [ "`echo *.c`" != "*.c" ]; then
#	cc *.c
#elif [ "`echo *.p`" != "*.p" ]; then
#	pc *.c
#else
#	echo "unrecognized language (not C or Pascal)"
#	ls
#fi
X
# if it did not build an a.out he looses
if [ -f ./a.out ]; then
X	./a.out 				; : "(#) see below"
X	if [ -f core ]; then
X		echo "DROPPED CORE"
X	fi
else
X	echo "NO a.out generated"
fi
X
# (#) above we could use lots of command to check for things
# we could draw input from a data file
#	./a.out < /usere/ksb/data2
# which could be argument $2
#	./a.out < $2
#
# we can limit resources through a csh line (see csh(1))
#	csh -fc "limit cpu 30; ./a.out"
#
# we can run many files
#	for DATA_FILE in /usera/ksb/data/d7.*
#	do
#		./a.out <$DATA_FILE
#	done
#
# we can mix any of the above
X
# now we remove the compiled program so we don't fill up a file system!
cd ..
rm -rf $COURSE$$
X
# and exit with a happy face (important in the future)
Xexit 0
Purdue
chmod 0444 project/template ||
echo 'restore of project/template failed'
Wc_c="`wc -c < 'project/template'`"
test 1781 -eq "$Wc_c" ||
	echo 'project/template: original size 1781, current size' "$Wc_c"
fi
# ============= project/machine.h ==============
if test -f 'project/machine.h' -a X"$1" != X"-c"; then
	echo 'x - skipping project/machine.h (File already exists)'
else
echo 'x - extracting project/machine.h (Text)'
sed 's/^X//' << 'Purdue' > 'project/machine.h' &&
/*
X * These might be locally tuned						(ksb)
X */
/* #define bsd		/**/
/* #define HPUX7	/**/
/* #define pdp11	/**/
X
/*
X * send reports of requests for project additions:
X */
#if !defined(MAILTO)
#define MAILTO		"enroll-info"
#endif
X
/*
X * where the global database is kept for who can ctrl project(s)
X */
#if ! defined(TURNBASE)
#define TURNBASE	"/usr/local/lib/turnin.cf"
#endif
X
/*
X * where the list of active projects is kept in the submit directory
X */
#if ! defined(PROJLIST)
#define PROJLIST	"Projlist"
#endif
X
#if !defined(SECTIGNORE)
#define SECTIGNORE	"ALL"
#endif
X
#if ! defined(LOCKFILE)
#define LOCKFILE	".lock"
#endif
X
#if ! defined(SUBPREFIX)
#define SUBPREFIX	"SUB_"
#endif
X
#if ! defined(COMPRESS_PATH)
#if defined(HPUX7)
#define COMPRESS_PATH	"/usr/bin/compress"
#else
#define COMPRESS_PATH	"/usr/ucb/compress"
#endif
#endif
X
#if ! defined(MAILX_PATH)
#if defined(HPUX7)
#define MAILX_PATH	"/usr/bin/mailx"
#else
#define MAILX_PATH	"/usr/ucb/Mail"
#endif
#endif
X
#define BIGTAR		5000	/* size of a large project (to compress)*/
X
#if ! defined(TAR_PATH)
#if defined(HPUX7)
#define TAR_PATH	"/usr/bin/tar"
#else
#define TAR_PATH	"/bin/tar"
#endif
#endif
X
/*
X * These might not change much						(ksb)
X */
#define MAXCHARS	2000	/* maximum line length in database	*/
#define SEP		':'	/* field separator for data base	*/
#define MAXCOURSENAME	32	/* max width of a course name		*/
#define MAXLOGINNAME	32	/* max chars in a login name		*/
#define MAXSUBDIRNAME	48	/* max chars in a subdir name		*/
#define MAXGROUPNAME	32	/* max chars in a group name		*/
#define MAXDIVSEC	32	/* max width of a div/sec name		*/
X
/* not kept in an online database -- no one nees it ?
X */
#define MAXLONGNAME	132	/* long name for the superuser		*/
#define MAXYESNO	24	/* a two word explain, `none yet'	*/
X
#define MAXPROJNAME	32	/* maximum chars in a project name	*/
#define MAXPROJECTS	32	/* maximum active projects/course	*/
#define MAXCOURSE	64	/* maximum courses in db (total)	*/
X
#if !defined(MAXPATHLEN)
#define MAXPATHLEN 1024
#endif
X
#define HAVE_NAME	defined(pdp11)
#define HAVE_STRINGS	defined(bsd)	/* 1->strings.h, 0->string.h	*/
#define USE_LOCKF	defined(HPUX7)	/* 1->lockf, 0-> flock		*/
X
#if defined(bsd)
#define strchr	index
#define strrchr	rindex
#endif
Purdue
chmod 0644 project/machine.h ||
echo 'restore of project/machine.h failed'
Wc_c="`wc -c < 'project/machine.h'`"
test 2259 -eq "$Wc_c" ||
	echo 'project/machine.h: original size 2259, current size' "$Wc_c"
fi
# ============= turnin/Makefile ==============
if test -f 'turnin/Makefile' -a X"$1" != X"-c"; then
	echo 'x - skipping turnin/Makefile (File already exists)'
else
echo 'x - extracting turnin/Makefile (Text)'
sed 's/^X//' << 'Purdue' > 'turnin/Makefile' &&
#	$Id: Makefile,v 4.5 90/11/23 14:10:21 becker Exp $
#
#	Makefile for turnin, depends on project (being in ../project)
#
# if you don't have strcasecmp see ../project/Makefile
X
PROG=	turnin
BIN=	${DESTDIR}/usr/local/bin
X
PROJECT=	../project
X
L=../libopt
#L=/usr/include/local
I=/usr/include
S=/usr/include/sys
P=
X
INCLUDE= -I$L
DEBUG=	-O
CDEFS=  
#CDEFS=	-DHP_UX
CFLAGS= ${DEBUG} ${CDEFS} ${INCLUDE}
X
HDR=	${PROJECT}/machine.h
SRC=	turnin.c
OBJ=	turnin.o
#SRC=	turnin.c strcasecmp.c
#OBJ=	turnin.o strcasecmp.o
OTHER=	
MAN=	turnin.1l
SOURCE=	Makefile ${HDR} ${SRC} ${MAN} ${OTHER}
X
all: ${PROG}
X
${PROG}:$P ${OBJ}
#	${CC} -o $@ ${CFLAGS} ${OBJ} -lopt
#	${CC} -o $@ ${CFLAGS} ${OBJ} -L /usr/local/lib -lopt
X	${CC} -o $@ ${CFLAGS} ${OBJ} ../libopt/libopt.a
X
clean: FRC
X	rm -f Makefile.bak ${PROG} *.o a.out core errs tags
X
depend: ${HDR} ${SRC} FRC
X	maketd ${CDEFS} ${INCLUDE} ${SRC}
X
install: all FRC
X	install -cs -m 4755 -o root ${PROG} ${BIN}/${PROG}
X
lint: ${HDR} ${SRC} FRC
X	lint -h ${CDEFS} ${INCLUDE} ${SRC}
X
mkcat: ${MAN}
X	mkcat ${MAN}
X
print: source FRC
X	lpr -J'${PROG} source' ${SOURCE}
X
source: ${SOURCE}
X
spotless: clean
X	rcsclean ${SOURCE}
X
tags: ${HDR} ${SRC}
X	ctags -t ${HDR} ${SRC}
X
${SOURCE}:
X	co -q $@
X
FRC:
X
# DO NOT DELETE THIS LINE - maketd DEPENDS ON IT
X
turnin: ../project/machine.h turnin.c
X
# *** Do not add anything here - It will go away. ***
Purdue
chmod 0644 turnin/Makefile ||
echo 'restore of turnin/Makefile failed'
Wc_c="`wc -c < 'turnin/Makefile'`"
test 1368 -eq "$Wc_c" ||
	echo 'turnin/Makefile: original size 1368, current size' "$Wc_c"
fi
true || echo 'restore of turnin.cf/turnin.cf failed'
echo End of part 1, continue with part 2
exit 0

exit 0 # Just in case...
-- 
Please send comp.sources.unix-related mail to rsalz@uunet.uu.net.
Use a domain-based address or give alternate paths, or you may lose out.