Almost complete rewrite of the lpc commands 'abort', 'enable', 'disable',
'restart', 'start', 'stop' and 'up'. These are commands which mainly just alter the access bits on the lock-file of a queue, and they all now use a central routine to do that. This reduces the amount of code that is run as the priv userid, and eliminates a number of cases where error messages were written while that priv uid was in effect. As far as users are concerned, there should be no noticable difference in the new versions. In case there *is*, the previous implementations are still there as 'xabort', 'xenable', etc, so they are available for instant fallback. If no one reports a problem after a few weeks, then a later update will remove those x-commands. Reviewed by: freebsd-audit and freebsd-print@bostonradio.org MFC after: 10 days
This commit is contained in:
parent
514fa7d23f
commit
1c055fbea9
@ -50,6 +50,7 @@ static const char rcsid[] =
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@ -252,6 +253,138 @@ status_file_name(const struct printer *pp, char *buf, size_t len)
|
||||
return buf;
|
||||
}
|
||||
|
||||
/*
|
||||
* Routine to change operational state of a print queue. The operational
|
||||
¥Êstate is indicated by the access bits on the lock file for the queue.
|
||||
* At present, this is only called from various routines in lpc/cmds.c.
|
||||
*
|
||||
* XXX - Note that this works by changing access-bits on the
|
||||
* file, and you can only do that if you are the owner of
|
||||
* the file, or root. Thus, this won't really work for
|
||||
* userids in the "LPR_OPER" group, unless lpc is running
|
||||
* setuid to root (or maybe setuid to daemon).
|
||||
* Generally lpc is installed setgid to daemon, but does
|
||||
* not run setuid.
|
||||
*/
|
||||
int
|
||||
set_qstate(int action, const char *lfname)
|
||||
{
|
||||
struct stat stbuf;
|
||||
mode_t chgbits, newbits, oldmask;
|
||||
const char *failmsg, *okmsg;
|
||||
int chres, errsav, fd, res, statres;
|
||||
|
||||
/*
|
||||
* Find what the current access-bits are.
|
||||
*/
|
||||
memset(&stbuf, 0, sizeof(stbuf));
|
||||
seteuid(euid);
|
||||
statres = stat(lfname, &stbuf);
|
||||
errsav = errno;
|
||||
seteuid(uid);
|
||||
if ((statres < 0) && (errsav != ENOENT)) {
|
||||
printf("\tcannot stat() lock file\n");
|
||||
return (SQS_STATFAIL);
|
||||
/* NOTREACHED */
|
||||
}
|
||||
|
||||
/*
|
||||
* Determine which bit(s) should change for the requested action.
|
||||
*/
|
||||
chgbits = stbuf.st_mode;
|
||||
newbits = LOCK_FILE_MODE;
|
||||
okmsg = NULL;
|
||||
failmsg = NULL;
|
||||
if (action & SQS_DISABLEQ) {
|
||||
chgbits |= LFM_QUEUE_DIS;
|
||||
newbits |= LFM_QUEUE_DIS;
|
||||
okmsg = "queuing disabled";
|
||||
failmsg = "disable queuing";
|
||||
}
|
||||
if (action & SQS_STOPP) {
|
||||
chgbits |= LFM_PRINT_DIS;
|
||||
newbits |= LFM_PRINT_DIS;
|
||||
okmsg = "printing disabled";
|
||||
failmsg = "disable printing";
|
||||
if (action & SQS_DISABLEQ) {
|
||||
okmsg = "printer and queuing disabled";
|
||||
failmsg = "disable queuing and printing";
|
||||
}
|
||||
}
|
||||
if (action & SQS_ENABLEQ) {
|
||||
chgbits &= ~LFM_QUEUE_DIS;
|
||||
newbits &= ~LFM_QUEUE_DIS;
|
||||
okmsg = "queuing enabled";
|
||||
failmsg = "enable queuing";
|
||||
}
|
||||
if (action & SQS_STARTP) {
|
||||
chgbits &= ~LFM_PRINT_DIS;
|
||||
newbits &= ~LFM_PRINT_DIS;
|
||||
okmsg = "printing enabled";
|
||||
failmsg = "enable printing";
|
||||
}
|
||||
if (okmsg == NULL) {
|
||||
/* This routine was called with an invalid action. */
|
||||
printf("\t<error in set_qstate!>\n");
|
||||
return (SQS_PARMERR);
|
||||
/* NOTREACHED */
|
||||
}
|
||||
|
||||
res = 0;
|
||||
if (statres >= 0) {
|
||||
/* The file already exists, so change the access. */
|
||||
seteuid(euid);
|
||||
chres = chmod(lfname, chgbits);
|
||||
errsav = errno;
|
||||
seteuid(uid);
|
||||
res = SQS_CHGOK;
|
||||
if (res < 0)
|
||||
res = SQS_CHGFAIL;
|
||||
} else if (newbits == LOCK_FILE_MODE) {
|
||||
/*
|
||||
* The file does not exist, but the state requested is
|
||||
* the same as the default state when no file exists.
|
||||
* Thus, there is no need to create the file.
|
||||
*/
|
||||
res = SQS_SKIPCREOK;
|
||||
} else {
|
||||
/*
|
||||
* The file did not exist, so create it with the
|
||||
* appropriate access bits for the requested action.
|
||||
* Push a new umask around that create, to make sure
|
||||
* all the read/write bits are set as desired.
|
||||
*/
|
||||
oldmask = umask(S_IWOTH);
|
||||
seteuid(euid);
|
||||
fd = open(lfname, O_WRONLY|O_CREAT, newbits);
|
||||
errsav = errno;
|
||||
seteuid(uid);
|
||||
umask(oldmask);
|
||||
res = SQS_CREFAIL;
|
||||
if (fd >= 0) {
|
||||
res = SQS_CREOK;
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
|
||||
switch (res) {
|
||||
case SQS_CHGOK:
|
||||
case SQS_CREOK:
|
||||
case SQS_SKIPCREOK:
|
||||
printf("\t%s\n", okmsg);
|
||||
break;
|
||||
case SQS_CREFAIL:
|
||||
printf("\tcannot create lock file: %s\n",
|
||||
strerror(errsav));
|
||||
break;
|
||||
default:
|
||||
printf("\tcannot %s: %s\n", failmsg, strerror(errsav));
|
||||
break;
|
||||
}
|
||||
|
||||
return (res);
|
||||
}
|
||||
|
||||
/* routine to get a current timestamp, optionally in a standard-fmt string */
|
||||
void
|
||||
lpd_gettime(struct timespec *tsp, char *strp, size_t strsize)
|
||||
|
@ -222,6 +222,23 @@ typedef enum { TR_SENDING, TR_RECVING, TR_PRINTING } tr_sendrecv;
|
||||
#define LOG_FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH)
|
||||
#define TEMP_FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH)
|
||||
|
||||
/*
|
||||
* Bit-flags for set_qstate() actions, followed by the return values.
|
||||
*/
|
||||
#define SQS_DISABLEQ 0x01 /* Disable the queuing of new jobs */
|
||||
#define SQS_STOPP 0x02 /* Stop the printing of jobs */
|
||||
#define SQS_ENABLEQ 0x10 /* Enable the queuing of new jobs */
|
||||
#define SQS_STARTP 0x20 /* Start the printing of jobs */
|
||||
|
||||
#define SQS_PARMERR -9 /* Invalid parameters from caller */
|
||||
#define SQS_CREFAIL -3 /* File did not exist, and create failed */
|
||||
#define SQS_CHGFAIL -2 /* File exists, but unable to change state */
|
||||
#define SQS_STATFAIL -1 /* Unable to stat() the lock file */
|
||||
#define SQS_CHGOK 1 /* File existed, and the state was changed */
|
||||
#define SQS_CREOK 2 /* File did not exist, but was created OK */
|
||||
#define SQS_SKIPCREOK 3 /* File did not exist, and there was */
|
||||
/* no need to create it */
|
||||
|
||||
/*
|
||||
* Command codes used in the protocol.
|
||||
*/
|
||||
@ -272,6 +289,7 @@ void process(const struct printer *_pp, char *_file);
|
||||
void rmjob(const char *_printer);
|
||||
void rmremote(const struct printer *_pp);
|
||||
void setprintcap(char *_newfile);
|
||||
int set_qstate(int _action, const char *_lfname);
|
||||
void show(const char *_nfile, const char *_datafile, int _copies);
|
||||
int startdaemon(const struct printer *_pp);
|
||||
char *status_file_name(const struct printer *_pp, char *_buf,
|
||||
|
@ -70,9 +70,18 @@ static const char rcsid[] =
|
||||
#include "extern.h"
|
||||
#include "pathnames.h"
|
||||
|
||||
/*
|
||||
* Return values from kill_qtask().
|
||||
*/
|
||||
#define KQT_LFERROR -2
|
||||
#define KQT_KILLFAIL -1
|
||||
#define KQT_NODAEMON 0
|
||||
#define KQT_KILLOK 1
|
||||
|
||||
static void abortpr(struct printer *_pp, int _dis);
|
||||
static int doarg(char *_job);
|
||||
static int doselect(struct dirent *_d);
|
||||
static int kill_qtask(const char *lf);
|
||||
static void putmsg(struct printer *_pp, int _argc, char **_argv);
|
||||
static int sortq(const void *_a, const void *_b);
|
||||
static void startpr(struct printer *_pp, int _chgenable);
|
||||
@ -277,6 +286,93 @@ abortpr(struct printer *pp, int dis)
|
||||
seteuid(uid);
|
||||
}
|
||||
|
||||
/*
|
||||
* Kill the current daemon, to stop printing of the active job.
|
||||
*/
|
||||
static int
|
||||
kill_qtask(const char *lf)
|
||||
{
|
||||
FILE *fp;
|
||||
pid_t pid;
|
||||
int errsav, killres, lockres, res;
|
||||
|
||||
seteuid(euid);
|
||||
fp = fopen(lf, "r");
|
||||
errsav = errno;
|
||||
seteuid(uid);
|
||||
res = KQT_NODAEMON;
|
||||
if (fp == NULL) {
|
||||
/*
|
||||
* If there is no lock file, then there is no daemon to
|
||||
* kill. Any other error return means there is some
|
||||
* kind of problem with the lock file.
|
||||
*/
|
||||
if (errsav != ENOENT)
|
||||
res = KQT_LFERROR;
|
||||
goto killdone;
|
||||
}
|
||||
|
||||
/* If the lock file is empty, then there is no daemon to kill */
|
||||
if (getline(fp) == 0)
|
||||
goto killdone;
|
||||
|
||||
/*
|
||||
* If the file can be locked without blocking, then there
|
||||
* no daemon to kill, or we should not try to kill it.
|
||||
*
|
||||
* XXX - not sure I understand the reasoning behind this...
|
||||
*/
|
||||
lockres = flock(fileno(fp), LOCK_SH|LOCK_NB);
|
||||
(void) fclose(fp);
|
||||
if (lockres == 0)
|
||||
goto killdone;
|
||||
|
||||
pid = atoi(line);
|
||||
if (pid < 0) {
|
||||
/*
|
||||
* If we got a negative pid, then the contents of the
|
||||
* lock file is not valid.
|
||||
*/
|
||||
res = KQT_LFERROR;
|
||||
goto killdone;
|
||||
}
|
||||
|
||||
seteuid(uid);
|
||||
killres = kill(pid, SIGTERM);
|
||||
errsav = errno;
|
||||
seteuid(uid);
|
||||
if (killres == 0) {
|
||||
res = KQT_KILLOK;
|
||||
printf("\tdaemon (pid %d) killed\n", pid);
|
||||
} else if (errno == ESRCH) {
|
||||
res = KQT_NODAEMON;
|
||||
} else {
|
||||
res = KQT_KILLFAIL;
|
||||
printf("\tWarning: daemon (pid %d) not killed:\n", pid);
|
||||
printf("\t %s\n", strerror(errsav));
|
||||
}
|
||||
|
||||
killdone:
|
||||
switch (res) {
|
||||
case KQT_LFERROR:
|
||||
printf("\tcannot open lock file: %s\n",
|
||||
strerror(errsav));
|
||||
break;
|
||||
case KQT_NODAEMON:
|
||||
printf("\tno daemon to abort\n");
|
||||
break;
|
||||
case KQT_KILLFAIL:
|
||||
case KQT_KILLOK:
|
||||
/* These two already printed messages to the user. */
|
||||
break;
|
||||
default:
|
||||
printf("\t<internal error in kill_qtask>\n");
|
||||
break;
|
||||
}
|
||||
|
||||
return (res);
|
||||
}
|
||||
|
||||
/*
|
||||
* Write a message into the status file.
|
||||
*/
|
||||
@ -301,6 +397,58 @@ upstat(struct printer *pp, const char *msg)
|
||||
(void) close(fd);
|
||||
}
|
||||
|
||||
/*
|
||||
* kill an existing daemon and disable printing.
|
||||
*/
|
||||
void
|
||||
abort_q(struct printer *pp)
|
||||
{
|
||||
int killres, setres;
|
||||
char lf[MAXPATHLEN];
|
||||
|
||||
lock_file_name(pp, lf, sizeof lf);
|
||||
printf("%s:\n", pp->printer);
|
||||
|
||||
/*
|
||||
* Turn on the owner execute bit of the lock file to disable printing.
|
||||
*/
|
||||
setres = set_qstate(SQS_STOPP, lf);
|
||||
|
||||
/*
|
||||
* If set_qstate found that there already was a lock file, then
|
||||
* call a routine which will read that lock file and kill the
|
||||
* lpd-process which is listed in that lock file. If the lock
|
||||
* file did not exist, then either there is no daemon running
|
||||
* for this queue, or there is one running but *it* could not
|
||||
* write a lock file (which means we can not determine the
|
||||
* process id of that lpd-process).
|
||||
*/
|
||||
switch (setres) {
|
||||
case SQS_CHGOK:
|
||||
case SQS_CHGFAIL:
|
||||
/* Kill the process */
|
||||
killres = kill_qtask(lf);
|
||||
break;
|
||||
case SQS_CREOK:
|
||||
case SQS_CREFAIL:
|
||||
printf("\tno daemon to abort\n");
|
||||
break;
|
||||
case SQS_STATFAIL:
|
||||
printf("\tassuming no daemon to abort\n");
|
||||
break;
|
||||
default:
|
||||
printf("\t<unexpected result (%d) from set_qstate>\n",
|
||||
setres);
|
||||
break;
|
||||
}
|
||||
|
||||
if (setres >= 0) {
|
||||
seteuid(euid);
|
||||
upstat(pp, "printing disabled\n");
|
||||
seteuid(uid);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* "global" variables for all the routines related to 'clean' and 'tclean'
|
||||
*/
|
||||
@ -749,6 +897,21 @@ enable(struct printer *pp)
|
||||
seteuid(uid);
|
||||
}
|
||||
|
||||
/*
|
||||
* Enable queuing to the printer (allow lpr to add new jobs to the queue).
|
||||
*/
|
||||
void
|
||||
enable_q(struct printer *pp)
|
||||
{
|
||||
int setres;
|
||||
char lf[MAXPATHLEN];
|
||||
|
||||
lock_file_name(pp, lf, sizeof lf);
|
||||
printf("%s:\n", pp->printer);
|
||||
|
||||
setres = set_qstate(SQS_ENABLEQ, lf);
|
||||
}
|
||||
|
||||
/*
|
||||
* Disable queuing.
|
||||
*/
|
||||
@ -785,6 +948,21 @@ disable(struct printer *pp)
|
||||
seteuid(uid);
|
||||
}
|
||||
|
||||
/*
|
||||
* Disable queuing.
|
||||
*/
|
||||
void
|
||||
disable_q(struct printer *pp)
|
||||
{
|
||||
int setres;
|
||||
char lf[MAXPATHLEN];
|
||||
|
||||
lock_file_name(pp, lf, sizeof lf);
|
||||
printf("%s:\n", pp->printer);
|
||||
|
||||
setres = set_qstate(SQS_DISABLEQ, lf);
|
||||
}
|
||||
|
||||
/*
|
||||
* Disable queuing and printing and put a message into the status file
|
||||
* (reason for being down).
|
||||
@ -923,6 +1101,37 @@ restart(struct printer *pp)
|
||||
startpr(pp, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Kill and restart the daemon.
|
||||
*/
|
||||
void
|
||||
restart_q(struct printer *pp)
|
||||
{
|
||||
int killres, setres, startok;
|
||||
char lf[MAXPATHLEN];
|
||||
|
||||
lock_file_name(pp, lf, sizeof lf);
|
||||
printf("%s:\n", pp->printer);
|
||||
|
||||
killres = kill_qtask(lf);
|
||||
|
||||
/*
|
||||
* XXX - if the kill worked, we should probably sleep for
|
||||
* a second or so before trying to restart the queue.
|
||||
*/
|
||||
|
||||
/* make sure the queue is set to print jobs */
|
||||
setres = set_qstate(SQS_STARTP, lf);
|
||||
|
||||
seteuid(euid);
|
||||
startok = startdaemon(pp);
|
||||
seteuid(uid);
|
||||
if (!startok)
|
||||
printf("\tcouldn't restart daemon\n");
|
||||
else
|
||||
printf("\tdaemon restarted\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* Enable printing on the specified printer and startup the daemon.
|
||||
*/
|
||||
@ -961,6 +1170,30 @@ startpr(struct printer *pp, int chgenable)
|
||||
seteuid(uid);
|
||||
}
|
||||
|
||||
/*
|
||||
* Enable printing on the specified printer and startup the daemon.
|
||||
*/
|
||||
void
|
||||
start_q(struct printer *pp)
|
||||
{
|
||||
int setres, startok;
|
||||
char lf[MAXPATHLEN];
|
||||
|
||||
lock_file_name(pp, lf, sizeof lf);
|
||||
printf("%s:\n", pp->printer);
|
||||
|
||||
setres = set_qstate(SQS_STARTP, lf);
|
||||
|
||||
seteuid(euid);
|
||||
startok = startdaemon(pp);
|
||||
seteuid(uid);
|
||||
if (!startok)
|
||||
printf("\tcouldn't start daemon\n");
|
||||
else
|
||||
printf("\tdaemon started\n");
|
||||
seteuid(uid);
|
||||
}
|
||||
|
||||
/*
|
||||
* Print the status of the printer queue.
|
||||
*/
|
||||
@ -1064,6 +1297,28 @@ stop(struct printer *pp)
|
||||
seteuid(uid);
|
||||
}
|
||||
|
||||
/*
|
||||
* Stop the specified daemon after completing the current job and disable
|
||||
* printing.
|
||||
*/
|
||||
void
|
||||
stop_q(struct printer *pp)
|
||||
{
|
||||
int setres;
|
||||
char lf[MAXPATHLEN];
|
||||
|
||||
lock_file_name(pp, lf, sizeof lf);
|
||||
printf("%s:\n", pp->printer);
|
||||
|
||||
setres = set_qstate(SQS_STOPP, lf);
|
||||
|
||||
if (setres >= 0) {
|
||||
seteuid(euid);
|
||||
upstat(pp, "printing disabled\n");
|
||||
seteuid(uid);
|
||||
}
|
||||
}
|
||||
|
||||
struct jobqueue **queue;
|
||||
int nitems;
|
||||
time_t mtime;
|
||||
@ -1237,3 +1492,26 @@ up(struct printer *pp)
|
||||
{
|
||||
startpr(pp, 2);
|
||||
}
|
||||
|
||||
/*
|
||||
* Enable both queuing & printing, and start printer (undo `down').
|
||||
*/
|
||||
void
|
||||
up_q(struct printer *pp)
|
||||
{
|
||||
int setres, startok;
|
||||
char lf[MAXPATHLEN];
|
||||
|
||||
lock_file_name(pp, lf, sizeof lf);
|
||||
printf("%s:\n", pp->printer);
|
||||
|
||||
setres = set_qstate(SQS_ENABLEQ+SQS_STARTP, lf);
|
||||
|
||||
seteuid(euid);
|
||||
startok = startdaemon(pp);
|
||||
seteuid(uid);
|
||||
if (!startok)
|
||||
printf("\tcouldn't start daemon\n");
|
||||
else
|
||||
printf("\tdaemon started\n");
|
||||
}
|
||||
|
@ -65,22 +65,29 @@ char uphelp[] = "enable everything and restart spooling daemon";
|
||||
#define PR 1 /* a privileged command */
|
||||
|
||||
struct cmd cmdtab[] = {
|
||||
{ "abort", aborthelp, PR, 0, doabort },
|
||||
{ "abort", aborthelp, PR, 0, abort_q },
|
||||
{ "clean", cleanhelp, PR, init_clean, clean_q },
|
||||
{ "enable", enablehelp, PR, 0, enable },
|
||||
{ "enable", enablehelp, PR, 0, enable_q },
|
||||
{ "exit", quithelp, 0, quit, 0 },
|
||||
{ "disable", disablehelp, PR, 0, disable },
|
||||
{ "disable", disablehelp, PR, 0, disable_q },
|
||||
{ "down", downhelp, PR, down, 0 },
|
||||
{ "help", helphelp, 0, help, 0 },
|
||||
{ "quit", quithelp, 0, quit, 0 },
|
||||
{ "restart", restarthelp, 0, 0, restart },
|
||||
{ "start", starthelp, PR, 0, startcmd },
|
||||
{ "restart", restarthelp, 0, 0, restart_q },
|
||||
{ "start", starthelp, PR, 0, start_q },
|
||||
{ "status", statushelp, 0, 0, status },
|
||||
{ "stop", stophelp, PR, 0, stop },
|
||||
{ "stop", stophelp, PR, 0, stop_q },
|
||||
{ "tclean", tcleanhelp, 0, init_tclean, clean_q },
|
||||
{ "topq", topqhelp, PR, topq, 0 },
|
||||
{ "up", uphelp, PR, 0, up },
|
||||
{ "up", uphelp, PR, 0, up_q },
|
||||
{ "?", helphelp, 0, help, 0 },
|
||||
{ "xabort", aborthelp, PR, 0, doabort },
|
||||
{ "xenable", enablehelp, PR, 0, enable },
|
||||
{ "xdisable", disablehelp, PR, 0, disable },
|
||||
{ "xrestart", restarthelp, 0, 0, restart },
|
||||
{ "xstart", starthelp, PR, 0, startcmd },
|
||||
{ "xstop", stophelp, PR, 0, stop },
|
||||
{ "xup", uphelp, PR, 0, up },
|
||||
{ 0, 0, 0, 0, 0},
|
||||
};
|
||||
|
||||
|
@ -42,11 +42,11 @@
|
||||
|
||||
|
||||
__BEGIN_DECLS
|
||||
void abort_q(struct printer *_pp);
|
||||
void clean_q(struct printer *_pp);
|
||||
void disable(struct printer *_pp);
|
||||
void doabort(struct printer *_pp);
|
||||
void disable_q(struct printer *_pp);
|
||||
void down(int _argc, char *_argv[]);
|
||||
void enable(struct printer *_pp);
|
||||
void enable_q(struct printer *_pp);
|
||||
void generic(void (*_specificrtn)(struct printer *_pp),
|
||||
void (*_initcmd)(int _argc, char *_argv[]),
|
||||
int _argc, char *_argv[]);
|
||||
@ -54,12 +54,19 @@ void help(int _argc, char *_argv[]);
|
||||
void init_clean(int _argc, char *_argv[]);
|
||||
void init_tclean(int _argc, char *_argv[]);
|
||||
void quit(int _argc, char *_argv[]);
|
||||
void restart(struct printer *_pp);
|
||||
void startcmd(struct printer *_pp);
|
||||
void restart_q(struct printer *_pp);
|
||||
void start_q(struct printer *_pp);
|
||||
void status(struct printer *_pp);
|
||||
void stop(struct printer *_pp);
|
||||
void stop_q(struct printer *_pp);
|
||||
void topq(int _argc, char *_argv[]);
|
||||
void up(struct printer *_pp);
|
||||
void up_q(struct printer *_pp);
|
||||
void disable(struct printer *_pp); /* X-version */
|
||||
void doabort(struct printer *_pp); /* X-version */
|
||||
void enable(struct printer *_pp); /* X-version */
|
||||
void restart(struct printer *_pp); /* X-version */
|
||||
void startcmd(struct printer *_pp); /* X-version */
|
||||
void stop(struct printer *_pp); /* X-version */
|
||||
void up(struct printer *_pp); /* X-version */
|
||||
__END_DECLS
|
||||
|
||||
extern int NCMDS;
|
||||
|
Loading…
Reference in New Issue
Block a user