/*
- * "$Id: classes.c 7608 2008-05-21 01:37:21Z mike $"
+ * "$Id$"
*
- * Printer class routines for the Common UNIX Printing System (CUPS).
+ * Printer class routines for the CUPS scheduler.
*
- * Copyright 2007 by Apple Inc.
+ * Copyright 2007-2011 by Apple Inc.
* Copyright 1997-2007 by Easy Software Products, all rights reserved.
*
* These coded instructions, statements, and computer programs are the
* cupsdAddPrinterToClass() - Add a printer to a class...
* cupsdDeletePrinterFromClass() - Delete a printer from a class.
* cupsdDeletePrinterFromClasses() - Delete a printer from all classes.
- * cupsdDeleteAllClasses() - Remove all classes from the system.
* cupsdFindAvailablePrinter() - Find an available printer in a class.
* cupsdFindClass() - Find the named class.
* cupsdLoadAllClasses() - Load classes from the classes.conf file.
* cupsdSaveAllClasses() - Save classes to the classes.conf file.
- * cupsdUpdateImplicitClasses() - Update the accepting state of implicit
- * classes.
*/
/*
cupsdAddClass(const char *name) /* I - Name of class */
{
cupsd_printer_t *c; /* New class */
+ char uri[1024]; /* Class URI */
/*
c->type = CUPS_PRINTER_CLASS;
- cupsdSetStringf(&c->uri, "ipp://%s:%d/classes/%s", ServerName, LocalPort,
- name);
- cupsdSetString(&c->error_policy, "retry-job");
+ httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
+ ServerName, RemotePort, "/classes/%s", name);
+ cupsdSetString(&c->uri, uri);
+
+ cupsdSetString(&c->error_policy, "retry-current-job");
}
return (c);
* 'cupsdDeletePrinterFromClass()' - Delete a printer from a class.
*/
-void
+int /* O - 1 if class changed, 0 otherwise */
cupsdDeletePrinterFromClass(
cupsd_printer_t *c, /* I - Class to delete from */
cupsd_printer_t *p) /* I - Printer to delete */
{
- int i; /* Looping var */
- cups_ptype_t type, /* Class type */
- oldtype; /* Old class type */
+ int i; /* Looping var */
/*
(c->num_printers - i) * sizeof(cupsd_printer_t *));
}
else
- return;
+ return (0);
/*
- * Recompute the printer type mask as needed...
+ * Update the IPP attributes (have to do this for member-names)...
*/
- if (c->num_printers > 0)
- {
- oldtype = c->type;
- type = c->type & (CUPS_PRINTER_CLASS | CUPS_PRINTER_IMPLICIT);
- c->type = ~CUPS_PRINTER_REMOTE;
-
- for (i = 0; i < c->num_printers; i ++)
- c->type &= c->printers[i]->type;
-
- c->type |= type;
-
- /*
- * Update the IPP attributes...
- */
+ cupsdSetPrinterAttrs(c);
- if (c->type != oldtype)
- cupsdSetPrinterAttrs(c);
- }
+ return (1);
}
* 'cupsdDeletePrinterFromClasses()' - Delete a printer from all classes.
*/
-void
+int /* O - 1 if class changed, 0 otherwise */
cupsdDeletePrinterFromClasses(
cupsd_printer_t *p) /* I - Printer to delete */
{
+ int changed = 0; /* Any class changed? */
cupsd_printer_t *c; /* Pointer to current class */
* from each class listed...
*/
- for (c = (cupsd_printer_t *)cupsArrayFirst(Printers);
- c;
- c = (cupsd_printer_t *)cupsArrayNext(Printers))
- if (c->type & (CUPS_PRINTER_CLASS | CUPS_PRINTER_IMPLICIT))
- cupsdDeletePrinterFromClass(c, p);
-
- /*
- * Then clean out any empty implicit classes...
- */
-
- for (c = (cupsd_printer_t *)cupsArrayFirst(ImplicitPrinters);
- c;
- c = (cupsd_printer_t *)cupsArrayNext(ImplicitPrinters))
- if (c->num_printers == 0)
- {
- cupsArrayRemove(ImplicitPrinters, c);
- cupsdDeletePrinter(c, 0);
- }
-}
-
-
-/*
- * 'cupsdDeleteAllClasses()' - Remove all classes from the system.
- */
-
-void
-cupsdDeleteAllClasses(void)
-{
- cupsd_printer_t *c; /* Pointer to current printer/class */
-
-
for (c = (cupsd_printer_t *)cupsArrayFirst(Printers);
c;
c = (cupsd_printer_t *)cupsArrayNext(Printers))
if (c->type & CUPS_PRINTER_CLASS)
- cupsdDeletePrinter(c, 0);
+ changed |= cupsdDeletePrinterFromClass(c, p);
+
+ return (changed);
}
cupsd_printer_t *c; /* Current class/printer */
- if ((c = cupsdFindDest(name)) != NULL &&
- (c->type & (CUPS_PRINTER_CLASS | CUPS_PRINTER_IMPLICIT)))
+ if ((c = cupsdFindDest(name)) != NULL && (c->type & CUPS_PRINTER_CLASS))
return (c);
else
return (NULL);
void
cupsdLoadAllClasses(void)
{
+ int i; /* Looping var */
cups_file_t *fp; /* classes.conf file */
int linenum; /* Current line number */
- char line[1024], /* Line from file */
+ char line[4096], /* Line from file */
*value, /* Pointer to value */
*valueptr; /* Pointer into value */
cupsd_printer_t *p, /* Current printer class */
*/
snprintf(line, sizeof(line), "%s/classes.conf", ServerRoot);
- if ((fp = cupsFileOpen(line, "r")) == NULL)
- {
- if (errno != ENOENT)
- cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to open %s - %s", line,
- strerror(errno));
+ if ((fp = cupsdOpenConfFile(line)) == NULL)
return;
- }
/*
* Read class configurations until we hit EOF...
* Decode the directive...
*/
- if (!strcasecmp(line, "<Class") ||
- !strcasecmp(line, "<DefaultClass"))
+ if (!_cups_strcasecmp(line, "<Class") ||
+ !_cups_strcasecmp(line, "<DefaultClass"))
{
/*
* <Class name> or <DefaultClass name>
p->accepting = 1;
p->state = IPP_PRINTER_IDLE;
- if (!strcasecmp(line, "<DefaultClass"))
+ if (!_cups_strcasecmp(line, "<DefaultClass"))
DefaultPrinter = p;
}
else
- {
cupsdLogMessage(CUPSD_LOG_ERROR,
"Syntax error on line %d of classes.conf.", linenum);
- break;
- }
}
- else if (!strcasecmp(line, "</Class>"))
+ else if (!_cups_strcasecmp(line, "</Class>"))
{
if (p != NULL)
{
p = NULL;
}
else
- {
cupsdLogMessage(CUPSD_LOG_ERROR,
"Syntax error on line %d of classes.conf.", linenum);
- break;
- }
}
else if (!p)
{
cupsdLogMessage(CUPSD_LOG_ERROR,
"Syntax error on line %d of classes.conf.", linenum);
- break;
}
- else if (!strcasecmp(line, "AuthInfoRequired"))
+ else if (!_cups_strcasecmp(line, "UUID"))
+ {
+ if (value && !strncmp(value, "urn:uuid:", 9))
+ cupsdSetString(&(p->uuid), value);
+ else
+ cupsdLogMessage(CUPSD_LOG_ERROR,
+ "Bad UUID on line %d of classes.conf.", linenum);
+ }
+ else if (!_cups_strcasecmp(line, "AuthInfoRequired"))
{
if (!cupsdSetAuthInfoRequired(p, value, NULL))
cupsdLogMessage(CUPSD_LOG_ERROR,
"Bad AuthInfoRequired on line %d of classes.conf.",
linenum);
}
- else if (!strcasecmp(line, "Info"))
+ else if (!_cups_strcasecmp(line, "Info"))
{
if (value)
cupsdSetString(&p->info, value);
}
- else if (!strcasecmp(line, "Location"))
+ else if (!_cups_strcasecmp(line, "Location"))
{
if (value)
cupsdSetString(&p->location, value);
}
- else if (!strcasecmp(line, "Option") && value)
+ else if (!_cups_strcasecmp(line, "Option") && value)
{
/*
* Option name value
&(p->options));
}
}
- else if (!strcasecmp(line, "Printer"))
+ else if (!_cups_strcasecmp(line, "Printer"))
{
if (!value)
{
cupsdLogMessage(CUPSD_LOG_ERROR,
"Syntax error on line %d of classes.conf.", linenum);
- break;
+ continue;
}
else if ((temp = cupsdFindPrinter(value)) == NULL)
{
{
cupsdSetString(&temp->make_model, "Remote Printer on unknown");
- temp->state = IPP_PRINTER_STOPPED;
- temp->type |= CUPS_PRINTER_REMOTE;
- temp->browse_time = 2147483647;
+ temp->state = IPP_PRINTER_STOPPED;
+ temp->type |= CUPS_PRINTER_REMOTE;
cupsdSetString(&temp->location, "Location Unknown");
cupsdSetString(&temp->info, "No Information Available");
if (temp)
cupsdAddPrinterToClass(p, temp);
}
- else if (!strcasecmp(line, "State"))
+ else if (!_cups_strcasecmp(line, "State"))
{
/*
* Set the initial queue state...
*/
- if (!strcasecmp(value, "idle"))
+ if (!_cups_strcasecmp(value, "idle"))
p->state = IPP_PRINTER_IDLE;
- else if (!strcasecmp(value, "stopped"))
+ else if (!_cups_strcasecmp(value, "stopped"))
+ {
p->state = IPP_PRINTER_STOPPED;
+
+ for (i = 0 ; i < p->num_reasons; i ++)
+ if (!strcmp("paused", p->reasons[i]))
+ break;
+
+ if (i >= p->num_reasons &&
+ p->num_reasons < (int)(sizeof(p->reasons) / sizeof(p->reasons[0])))
+ {
+ p->reasons[p->num_reasons] = _cupsStrAlloc("paused");
+ p->num_reasons ++;
+ }
+ }
else
- {
cupsdLogMessage(CUPSD_LOG_ERROR,
"Syntax error on line %d of classes.conf.",
linenum);
- break;
- }
}
- else if (!strcasecmp(line, "StateMessage"))
+ else if (!_cups_strcasecmp(line, "StateMessage"))
{
/*
* Set the initial queue state message...
if (value)
strlcpy(p->state_message, value, sizeof(p->state_message));
}
- else if (!strcasecmp(line, "StateTime"))
+ else if (!_cups_strcasecmp(line, "StateTime"))
{
/*
* Set the state time...
if (value)
p->state_time = atoi(value);
}
- else if (!strcasecmp(line, "Accepting"))
+ else if (!_cups_strcasecmp(line, "Accepting"))
{
/*
* Set the initial accepting state...
*/
if (value &&
- (!strcasecmp(value, "yes") ||
- !strcasecmp(value, "on") ||
- !strcasecmp(value, "true")))
+ (!_cups_strcasecmp(value, "yes") ||
+ !_cups_strcasecmp(value, "on") ||
+ !_cups_strcasecmp(value, "true")))
p->accepting = 1;
else if (value &&
- (!strcasecmp(value, "no") ||
- !strcasecmp(value, "off") ||
- !strcasecmp(value, "false")))
+ (!_cups_strcasecmp(value, "no") ||
+ !_cups_strcasecmp(value, "off") ||
+ !_cups_strcasecmp(value, "false")))
p->accepting = 0;
else
- {
cupsdLogMessage(CUPSD_LOG_ERROR,
"Syntax error on line %d of classes.conf.",
linenum);
- break;
- }
}
- else if (!strcasecmp(line, "Shared"))
+ else if (!_cups_strcasecmp(line, "Shared"))
{
/*
* Set the initial shared state...
*/
if (value &&
- (!strcasecmp(value, "yes") ||
- !strcasecmp(value, "on") ||
- !strcasecmp(value, "true")))
+ (!_cups_strcasecmp(value, "yes") ||
+ !_cups_strcasecmp(value, "on") ||
+ !_cups_strcasecmp(value, "true")))
p->shared = 1;
else if (value &&
- (!strcasecmp(value, "no") ||
- !strcasecmp(value, "off") ||
- !strcasecmp(value, "false")))
+ (!_cups_strcasecmp(value, "no") ||
+ !_cups_strcasecmp(value, "off") ||
+ !_cups_strcasecmp(value, "false")))
p->shared = 0;
else
- {
cupsdLogMessage(CUPSD_LOG_ERROR,
"Syntax error on line %d of classes.conf.",
linenum);
- break;
- }
}
- else if (!strcasecmp(line, "JobSheets"))
+ else if (!_cups_strcasecmp(line, "JobSheets"))
{
/*
* Set the initial job sheets...
valueptr ++);
if (*valueptr)
- *valueptr++ = '\0';
+ *valueptr = '\0';
cupsdSetString(&p->job_sheets[1], value);
}
}
else
- {
cupsdLogMessage(CUPSD_LOG_ERROR,
"Syntax error on line %d of classes.conf.", linenum);
- break;
- }
}
- else if (!strcasecmp(line, "AllowUser"))
+ else if (!_cups_strcasecmp(line, "AllowUser"))
{
if (value)
{
p->deny_users = 0;
- cupsdAddPrinterUser(p, value);
+ cupsdAddString(&(p->users), value);
}
else
- {
cupsdLogMessage(CUPSD_LOG_ERROR,
"Syntax error on line %d of classes.conf.", linenum);
- break;
- }
}
- else if (!strcasecmp(line, "DenyUser"))
+ else if (!_cups_strcasecmp(line, "DenyUser"))
{
if (value)
{
p->deny_users = 1;
- cupsdAddPrinterUser(p, value);
+ cupsdAddString(&(p->users), value);
}
else
- {
cupsdLogMessage(CUPSD_LOG_ERROR,
"Syntax error on line %d of classes.conf.", linenum);
- break;
- }
}
- else if (!strcasecmp(line, "QuotaPeriod"))
+ else if (!_cups_strcasecmp(line, "QuotaPeriod"))
{
if (value)
p->quota_period = atoi(value);
else
- {
cupsdLogMessage(CUPSD_LOG_ERROR,
"Syntax error on line %d of classes.conf.", linenum);
- break;
- }
}
- else if (!strcasecmp(line, "PageLimit"))
+ else if (!_cups_strcasecmp(line, "PageLimit"))
{
if (value)
p->page_limit = atoi(value);
else
- {
cupsdLogMessage(CUPSD_LOG_ERROR,
"Syntax error on line %d of classes.conf.", linenum);
- break;
- }
}
- else if (!strcasecmp(line, "KLimit"))
+ else if (!_cups_strcasecmp(line, "KLimit"))
{
if (value)
p->k_limit = atoi(value);
else
- {
cupsdLogMessage(CUPSD_LOG_ERROR,
"Syntax error on line %d of classes.conf.", linenum);
- break;
- }
}
- else if (!strcasecmp(line, "OpPolicy"))
+ else if (!_cups_strcasecmp(line, "OpPolicy"))
{
if (value)
{
value, linenum);
}
else
- {
cupsdLogMessage(CUPSD_LOG_ERROR,
"Syntax error on line %d of classes.conf.", linenum);
- break;
- }
}
- else if (!strcasecmp(line, "ErrorPolicy"))
+ else if (!_cups_strcasecmp(line, "ErrorPolicy"))
{
if (value)
- cupsdSetString(&p->error_policy, value);
- else
{
+ if (strcmp(value, "retry-current-job") && strcmp(value, "retry-job"))
+ cupsdLogMessage(CUPSD_LOG_WARN,
+ "ErrorPolicy %s ignored on line %d of classes.conf",
+ value, linenum);
+ }
+ else
cupsdLogMessage(CUPSD_LOG_ERROR,
"Syntax error on line %d of classes.conf.", linenum);
- break;
- }
}
else
{
cupsdSaveAllClasses(void)
{
cups_file_t *fp; /* classes.conf file */
- char temp[1024]; /* Temporary string */
- char backup[1024]; /* classes.conf.O file */
+ char filename[1024], /* classes.conf filename */
+ temp[1024], /* Temporary string */
+ value[2048], /* Value string */
+ *name; /* Current user name */
cupsd_printer_t *pclass; /* Current printer class */
int i; /* Looping var */
time_t curtime; /* Current time */
struct tm *curdate; /* Current date */
cups_option_t *option; /* Current option */
- const char *ptr; /* Pointer into info/location */
/*
* Create the classes.conf file...
*/
- snprintf(temp, sizeof(temp), "%s/classes.conf", ServerRoot);
- snprintf(backup, sizeof(backup), "%s/classes.conf.O", ServerRoot);
+ snprintf(filename, sizeof(filename), "%s/classes.conf", ServerRoot);
- if (rename(temp, backup))
- {
- if (errno != ENOENT)
- cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to backup classes.conf - %s",
- strerror(errno));
- }
-
- if ((fp = cupsFileOpen(temp, "w")) == NULL)
- {
- cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to save classes.conf - %s",
- strerror(errno));
-
- if (rename(backup, temp))
- cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to restore classes.conf - %s",
- strerror(errno));
+ if ((fp = cupsdCreateConfFile(filename, ConfigFilePerm)) == NULL)
return;
- }
- else
- cupsdLogMessage(CUPSD_LOG_INFO, "Saving classes.conf...");
-
- /*
- * Restrict access to the file...
- */
- fchown(cupsFileNumber(fp), RunUser, Group);
- fchmod(cupsFileNumber(fp), 0600);
+ cupsdLogMessage(CUPSD_LOG_INFO, "Saving classes.conf...");
/*
* Write a small header to the file...
cupsFilePuts(fp, "# Class configuration file for " CUPS_SVERSION "\n");
cupsFilePrintf(fp, "# Written by cupsd on %s\n", temp);
+ cupsFilePuts(fp, "# DO NOT EDIT THIS FILE WHEN CUPSD IS RUNNING\n");
/*
* Write each local class known to the system...
*/
if ((pclass->type & CUPS_PRINTER_REMOTE) ||
- (pclass->type & CUPS_PRINTER_IMPLICIT) ||
!(pclass->type & CUPS_PRINTER_CLASS))
continue;
else
cupsFilePrintf(fp, "<Class %s>\n", pclass->name);
- if (pclass->num_auth_info_required > 0)
- {
- cupsFilePrintf(fp, "AuthInfoRequired %s", pclass->auth_info_required[0]);
- for (i = 1; i < pclass->num_auth_info_required; i ++)
- cupsFilePrintf(fp, ",%s", pclass->auth_info_required[i]);
- cupsFilePutChar(fp, '\n');
- }
+ cupsFilePrintf(fp, "UUID %s\n", pclass->uuid);
- if (pclass->info)
+ if (pclass->num_auth_info_required > 0)
{
- if ((ptr = strchr(pclass->info, '#')) != NULL)
+ switch (pclass->num_auth_info_required)
{
- /*
- * Need to quote the first # in the info string...
- */
-
- cupsFilePuts(fp, "Info ");
- cupsFileWrite(fp, pclass->info, ptr - pclass->info);
- cupsFilePutChar(fp, '\\');
- cupsFilePuts(fp, ptr);
- cupsFilePutChar(fp, '\n');
+ case 1 :
+ strlcpy(value, pclass->auth_info_required[0], sizeof(value));
+ break;
+
+ case 2 :
+ snprintf(value, sizeof(value), "%s,%s",
+ pclass->auth_info_required[0],
+ pclass->auth_info_required[1]);
+ break;
+
+ case 3 :
+ default :
+ snprintf(value, sizeof(value), "%s,%s,%s",
+ pclass->auth_info_required[0],
+ pclass->auth_info_required[1],
+ pclass->auth_info_required[2]);
+ break;
}
- else
- cupsFilePrintf(fp, "Info %s\n", pclass->info);
+
+ cupsFilePutConf(fp, "AuthInfoRequired", value);
}
- if (pclass->location)
- {
- if ((ptr = strchr(pclass->info, '#')) != NULL)
- {
- /*
- * Need to quote the first # in the location string...
- */
+ if (pclass->info)
+ cupsFilePutConf(fp, "Info", pclass->info);
- cupsFilePuts(fp, "Location ");
- cupsFileWrite(fp, pclass->location, ptr - pclass->location);
- cupsFilePutChar(fp, '\\');
- cupsFilePuts(fp, ptr);
- cupsFilePutChar(fp, '\n');
- }
- else
- cupsFilePrintf(fp, "Location %s\n", pclass->location);
- }
+ if (pclass->location)
+ cupsFilePutConf(fp, "Location", pclass->location);
if (pclass->state == IPP_PRINTER_STOPPED)
- {
cupsFilePuts(fp, "State Stopped\n");
- cupsFilePrintf(fp, "StateMessage %s\n", pclass->state_message);
- }
else
cupsFilePuts(fp, "State Idle\n");
else
cupsFilePuts(fp, "Shared No\n");
- cupsFilePrintf(fp, "JobSheets %s %s\n", pclass->job_sheets[0],
- pclass->job_sheets[1]);
+ snprintf(value, sizeof(value), "%s %s", pclass->job_sheets[0],
+ pclass->job_sheets[1]);
+ cupsFilePutConf(fp, "JobSheets", value);
- for (i = 0; i < pclass->num_users; i ++)
- {
- if ((ptr = strchr(pclass->users[i], '#')) != NULL)
- {
- /*
- * Need to quote the first # in the user string...
- */
-
- cupsFilePrintf(fp, "%sUser ", pclass->deny_users ? "Deny" : "Allow");
- cupsFileWrite(fp, pclass->users[i], ptr - pclass->users[i]);
- cupsFilePutChar(fp, '\\');
- cupsFilePuts(fp, ptr);
- cupsFilePutChar(fp, '\n');
- }
- else
- cupsFilePrintf(fp, "%sUser %s\n",
- pclass->deny_users ? "Deny" : "Allow",
- pclass->users[i]);
- }
+ for (i = 0; i < pclass->num_printers; i ++)
+ cupsFilePrintf(fp, "Printer %s\n", pclass->printers[i]->name);
cupsFilePrintf(fp, "QuotaPeriod %d\n", pclass->quota_period);
cupsFilePrintf(fp, "PageLimit %d\n", pclass->page_limit);
cupsFilePrintf(fp, "KLimit %d\n", pclass->k_limit);
- for (i = 0; i < pclass->num_users; i ++)
- cupsFilePrintf(fp, "%sUser %s\n", pclass->deny_users ? "Deny" : "Allow",
- pclass->users[i]);
+ for (name = (char *)cupsArrayFirst(pclass->users);
+ name;
+ name = (char *)cupsArrayNext(pclass->users))
+ cupsFilePutConf(fp, pclass->deny_users ? "DenyUser" : "AllowUser", name);
- if (pclass->op_policy)
- cupsFilePrintf(fp, "OpPolicy %s\n", pclass->op_policy);
+ if (pclass->op_policy)
+ cupsFilePutConf(fp, "OpPolicy", pclass->op_policy);
if (pclass->error_policy)
- cupsFilePrintf(fp, "ErrorPolicy %s\n", pclass->error_policy);
+ cupsFilePutConf(fp, "ErrorPolicy", pclass->error_policy);
for (i = pclass->num_options, option = pclass->options;
i > 0;
i --, option ++)
- cupsFilePrintf(fp, "Option %s %s\n", option->name, option->value);
+ {
+ snprintf(value, sizeof(value), "%s %s", option->name, option->value);
+ cupsFilePutConf(fp, "Option", value);
+ }
cupsFilePuts(fp, "</Class>\n");
}
- cupsFileClose(fp);
-}
-
-
-/*
- * 'cupsdUpdateImplicitClasses()' - Update the accepting state of implicit
- * classes.
- */
-
-void
-cupsdUpdateImplicitClasses(void)
-{
- int i; /* Looping var */
- cupsd_printer_t *pclass; /* Current class */
- int accepting; /* printer-is-accepting-jobs value */
-
-
- for (pclass = (cupsd_printer_t *)cupsArrayFirst(ImplicitPrinters);
- pclass;
- pclass = (cupsd_printer_t *)cupsArrayNext(ImplicitPrinters))
- {
- /*
- * Loop through the printers to come up with a composite state...
- */
-
- for (i = 0, accepting = 0; i < pclass->num_printers; i ++)
- if ((accepting = pclass->printers[i]->accepting) != 0)
- break;
-
- pclass->accepting = accepting;
- }
+ cupsdCloseCreatedConfFile(fp, filename);
}
/*
- * End of "$Id: classes.c 7608 2008-05-21 01:37:21Z mike $".
+ * End of "$Id$".
*/