2 * $Id: main.c,v 1.20 2007-04-27 11:30:16 didg Exp $
4 * Copyright (c) 1990,1995 Regents of The University of Michigan.
5 * All Rights Reserved. See COPYRIGHT.
10 #endif /* HAVE_CONFIG_H */
14 #include <sys/param.h>
17 #if defined( sun ) && defined( __svr4__ )
18 #include </usr/ucbinclude/sys/file.h>
19 #else /* sun && __svr4__ */
21 #endif /* sun && __svr4__ */
22 #include <sys/socket.h>
23 #include <atalk/logger.h>
25 /* POSIX.1 sys/wait.h check */
26 #include <sys/types.h>
27 #ifdef HAVE_SYS_WAIT_H
29 #endif /* HAVE_SYS_WAIT_H */
31 #define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
32 #endif /* ! WEXITSTATUS */
34 #define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
35 #endif /* ! WIFEXITED */
42 #else /* STDC_HEADERS */
46 #endif /* HAVE_STRCHR */
47 char *strchr (), *strrchr ();
49 #define memcpy(d,s,n) bcopy ((s), (d), (n))
50 #define memmove(d,s,n) bcopy ((s), (d), (n))
51 #endif /* ! HAVE_MEMCPY */
52 #endif /* STDC_HEADERS */
61 #endif /* HAVE_UNISTD_H */
63 #include <netatalk/endian.h>
64 #include <netatalk/at.h>
65 #include <atalk/compat.h>
66 #include <atalk/atp.h>
67 #include <atalk/pap.h>
68 #include <atalk/paths.h>
69 #include <atalk/util.h>
70 #include <atalk/nbp.h>
71 #include <atalk/unicode.h>
77 #include "print_cups.h"
79 #define _PATH_PAPDPPDFILE ".ppd"
81 #define PIPED_STATUS "status: print spooler processing job"
83 struct printer defprinter;
84 struct printer *printers = NULL;
87 static char *conffile = _PATH_PAPDCONF;
88 char *printcap = _PATH_PAPDPRINTCAP;
89 unsigned char connid, quantum, sock, oquantum = PAP_MAXQUANTUM;
90 char *cannedstatus = PIPED_STATUS;
91 struct printer *printer = NULL;
92 char *version = VERSION;
93 static char *pidfile = _PATH_PAPDLOCK;
96 char *uampath = _PATH_PAPDUAMPATH;
98 /* Prototypes for locally used functions */
99 int getstatus( struct printer *pr, char *buf );
100 int rprintcap( struct printer *pr );
101 static void getprinters( char *cf );
104 /* this only needs to be used by the server process */
105 static void papd_exit(const int i)
107 server_unlock(pidfile);
112 #if !defined( ibm032 ) && !defined( _IBMR2 )
114 #endif /* ! ibm032 && ! _IBMR2 */
121 memset(&addr, 0, sizeof(addr));
123 for ( pr = printers; pr; pr = pr->p_next ) {
124 if ( pr->p_flags & P_REGISTERED ) {
125 if ( nbp_unrgstr( pr->p_name, pr->p_type, pr->p_zone, &addr ) < 0 ) {
126 LOG(log_error, logtype_papd, "can't unregister %s:%s@%s", pr->p_name,
127 pr->p_type, pr->p_zone );
130 LOG(log_info, logtype_papd, "unregister %s:%s@%s", pr->p_name, pr->p_type,
134 if ( pr->p_flags & P_SPOOLED && pr->p_flags & P_CUPS_PPD ) {
135 LOG(log_info, logtype_papd, "Deleting CUPS temp PPD file for %s (%s)", pr->p_name, pr->p_ppdfile);
136 unlink (pr->p_ppdfile);
138 #endif /* HAVE_CUPS */
144 #if !defined( ibm032 ) && !defined( _IBMR2 )
146 #endif /* ! ibm032 && ! _IBMR2 */
152 while (( pid = wait3( &status, WNOHANG, 0 )) > 0 ) {
153 if ( WIFEXITED( status )) {
154 if ( WEXITSTATUS( status )) {
155 LOG(log_error, logtype_papd, "child %d exited with %d", pid,
156 WEXITSTATUS( status ));
158 LOG(log_info, logtype_papd, "child %d done", pid );
161 if ( WIFSIGNALED( status )) {
162 LOG(log_error, logtype_papd, "child %d killed with %d", pid,
165 LOG(log_error, logtype_papd, "child %d died", pid );
172 char rbuf[ 255 + 1 + 8 ];
181 struct atp_block atpb;
182 struct sockaddr_at sat;
187 char *p, hostname[ MAXHOSTNAMELEN ];
192 if ( gethostname( hostname, sizeof( hostname )) < 0 ) {
193 perror( "gethostname" );
196 if (( p = strchr( hostname, '.' )) != 0 ) {
199 if (( defprinter.p_name = (char *)malloc( strlen( hostname ) + 1 ))
204 strcpy( defprinter.p_name, hostname );
205 defprinter.p_type = "LaserWriter";
206 defprinter.p_zone = "*";
207 memset(&defprinter.p_addr, 0, sizeof(defprinter.p_addr));
208 defprinter.p_ppdfile = _PATH_PAPDPPDFILE;
210 defprinter.p_flags = P_PIPED;
211 defprinter.p_printer = "/usr/bin/lp -T PS";
213 defprinter.p_flags = P_SPOOLED;
214 defprinter.p_printer = "lp";
215 #endif /* __svr4__ */
216 defprinter.p_operator = "operator";
217 defprinter.p_spool = _PATH_PAPDSPOOLDIR;
219 defprinter.p_role = NULL;
220 defprinter.p_srvid = 0;
221 #endif /* ABS_PRINT */
222 defprinter.p_pagecost = 200; /* default cost */
223 defprinter.p_pagecost_msg = NULL;
224 defprinter.p_lock = "lock";
226 while (( c = getopt( ac, av, "adf:p:P:v" )) != EOF ) {
228 case 'a' : /* for compatibility with old papd */
231 case 'd' : /* debug */
235 case 'f' : /* conffile */
239 case 'p' : /* printcap */
247 case 'v' : /* version */
248 printf( "papd (version %s)\n", VERSION );
254 "Usage:\t%s [ -d ] [ -f conffile ] [ -p printcap ]\n",
261 switch (server_lock("papd", pidfile, debug)) {
262 case 0: /* open a couple things again in the child */
263 if ((c = open("/", O_RDONLY)) >= 0) {
281 if (( p = strrchr( av[ 0 ], '/' )) == NULL ) {
287 openlog( p, LOG_PID );
290 syslog_setup(log_debug, logtype_default, logoption_ndelay|logoption_pid, logfacility_lpr );
293 LOG(log_info, logtype_papd, "restart (%s)", version );
295 LOG(log_info, logtype_papd, "CUPS support enabled (%s)", CUPS_API_VERSION );
298 getprinters( conffile );
300 for ( pr = printers; pr; pr = pr->p_next ) {
301 if (( pr->p_flags & P_SPOOLED ) && rprintcap( pr ) < 0 ) {
302 LOG(log_error, logtype_papd, "printcap problem: %s", pr->p_printer );
305 if (!(pr->p_flags & P_CUPS)) {
306 if ((size_t)-1 != convert_string_allocate(CH_UNIX, CH_MAC, pr->p_name, strlen(pr->p_name), &atname)) {
307 pr->p_u_name = pr->p_name;
312 if (( pr->p_atp = atp_open( ATADDR_ANYPORT, &pr->p_addr )) == NULL ) {
313 LOG(log_error, logtype_papd, "atp_open: %m" );
316 if ( nbp_rgstr( atp_sockaddr( pr->p_atp ), pr->p_name, pr->p_type,
318 LOG(log_error, logtype_papd, "can't register %s:%s@%s", pr->p_u_name, pr->p_type,
322 if ( pr->p_flags & P_AUTH ) {
323 LOG(log_info, logtype_papd, "Authentication enabled: %s", pr->p_u_name );
326 LOG(log_info, logtype_papd, "Authentication disabled: %s", pr->p_u_name );
328 LOG(log_info, logtype_papd, "register %s:%s@%s", pr->p_u_name, pr->p_type,
330 pr->p_flags |= P_REGISTERED;
333 memset(&sv, 0, sizeof(sv));
335 sigemptyset( &sv.sa_mask );
336 sv.sa_flags = SA_RESTART;
337 if ( sigaction( SIGTERM, &sv, 0 ) < 0 ) {
338 LOG(log_error, logtype_papd, "sigaction: %m" );
342 sv.sa_handler = reap;
343 sigemptyset( &sv.sa_mask );
344 sv.sa_flags = SA_RESTART;
345 if ( sigaction( SIGCHLD, &sv, 0 ) < 0 ) {
346 LOG(log_error, logtype_papd, "sigaction: %m" );
353 auth_load(uampath, uamlist);
356 * Begin accepting connections.
360 for ( pr = printers; pr; pr = pr->p_next ) {
361 FD_SET( atp_fileno( pr->p_atp ), &fdset );
363 if (( c = select( FD_SETSIZE, &fdset, 0, 0, 0 )) < 0 ) {
364 if ( errno == EINTR ) {
367 LOG(log_error, logtype_papd, "select: %m" );
371 for ( pr = printers; pr; pr = pr->p_next ) {
372 if ( FD_ISSET( atp_fileno( pr->p_atp ), &fdset )) {
375 memset( &sat, 0, sizeof( struct sockaddr_at ));
377 sat.sat_len = sizeof( struct sockaddr_at );
379 sat.sat_family = AF_APPLETALK;
380 sat.sat_addr.s_net = ATADDR_ANYNET;
381 sat.sat_addr.s_node = ATADDR_ANYNODE;
382 sat.sat_port = ATADDR_ANYPORT;
383 /* do an atp_rsel(), to prevent hangs */
384 if (( c = atp_rsel( pr->p_atp, &sat, ATP_TREQ )) != ATP_TREQ ) {
387 atpb.atp_saddr = &sat;
388 atpb.atp_rreqdata = cbuf;
389 atpb.atp_rreqdlen = sizeof( cbuf );
390 if ( atp_rreq( pr->p_atp, &atpb ) < 0 ) {
391 LOG(log_error, logtype_papd, "atp_rreq: %m" );
395 /* should check length of req buf */
397 switch( cbuf[ 1 ] ) {
399 connid = (unsigned char)cbuf[ 0 ];
400 sock = (unsigned char)cbuf[ 4 ];
401 quantum = (unsigned char)cbuf[ 5 ];
402 rbuf[ 0 ] = cbuf[ 0 ];
403 rbuf[ 1 ] = PAP_OPENREPLY;
404 rbuf[ 2 ] = rbuf[ 3 ] = 0;
406 if (( pr->p_flags & P_SPOOLED ) && rprintcap( pr ) != 0 ) {
407 LOG(log_error, logtype_papd, "printcap problem: %s",
409 rbuf[ 2 ] = rbuf[ 3 ] = 0xff;
415 * If cups is not accepting jobs, we return
416 * 0xffff to indicate we're busy
419 LOG(log_debug, logtype_papd, "CUPS: PAP_OPEN");
421 if ( (pr->p_flags & P_SPOOLED) && (cups_get_printer_status ( pr ) == 0)) {
422 LOG(log_error, logtype_papd, "CUPS_PAP_OPEN: %s is not accepting jobs",
424 rbuf[ 2 ] = rbuf[ 3 ] = 0xff;
427 #endif /* HAVE_CUPS */
431 * If this fails, we've run out of sockets. Rather than
432 * just die(), let's try to continue. Maybe some sockets
433 * will close, and we can continue;
435 if (( atp = atp_open( ATADDR_ANYPORT,
436 &pr->p_addr)) == NULL ) {
437 LOG(log_error, logtype_papd, "atp_open: %m" );
438 rbuf[ 2 ] = rbuf[ 3 ] = 0xff; /* printer busy */
439 rbuf[ 4 ] = 0; /* FIXME is it right? */
443 rbuf[ 4 ] = atp_sockaddr( atp )->sat_port;
445 rbuf[ 5 ] = oquantum;
446 rbuf[ 6 ] = rbuf[ 7 ] = 0;
449 iov.iov_len = 8 + getstatus( pr, &rbuf[ 8 ] );
450 atpb.atp_sresiov = &iov;
451 atpb.atp_sresiovcnt = 1;
453 * This may error out if we lose a route, so we won't die().
455 if ( atp_sresp( pr->p_atp, &atpb ) < 0 ) {
456 LOG(log_error, logtype_papd, "atp_sresp: %m" );
464 switch ( c = fork()) {
466 LOG(log_error, logtype_papd, "fork: %m" );
473 if (( printer->p_flags & P_SPOOLED ) &&
474 chdir( printer->p_spool ) < 0 ) {
475 LOG(log_error, logtype_papd, "chdir %s: %m", printer->p_spool );
479 if (( printer->p_flags & P_SPOOLED ) &&
480 chdir( SPOOLDIR ) < 0 ) {
481 LOG(log_error, logtype_papd, "chdir %s: %m", SPOOLDIR );
487 sv.sa_handler = SIG_DFL;
488 sigemptyset( &sv.sa_mask );
489 sv.sa_flags = SA_RESTART;
490 if ( sigaction( SIGTERM, &sv, 0 ) < 0 ) {
491 LOG(log_error, logtype_papd, "sigaction: %m" );
495 for ( pr = printers; pr; pr = pr->p_next ) {
496 atp_close( pr->p_atp );
499 if ( session( atp, &sat ) < 0 ) {
500 LOG(log_error, logtype_papd, "bad session" );
506 default : /* parent */
507 LOG(log_info, logtype_papd, "child %d for \"%s\" from %u.%u",
508 c, pr->p_name, ntohs( sat.sat_addr.s_net ),
509 sat.sat_addr.s_node);
514 case PAP_SENDSTATUS :
516 rbuf[ 1 ] = PAP_STATUS;
517 rbuf[ 2 ] = rbuf[ 3 ] = 0;
518 rbuf[ 4 ] = rbuf[ 5 ] = 0;
519 rbuf[ 6 ] = rbuf[ 7 ] = 0;
522 iov.iov_len = 8 + getstatus( pr, &rbuf[ 8 ] );
523 atpb.atp_sresiov = &iov;
524 atpb.atp_sresiovcnt = 1;
526 * This may error out if we lose a route, so we won't die().
528 if ( atp_sresp( pr->p_atp, &atpb ) < 0 ) {
529 LOG(log_error, logtype_papd, "atp_sresp: %m" );
534 LOG(log_error, logtype_papd, "Bad request from %u.%u!",
535 ntohs( sat.sat_addr.s_net ), sat.sat_addr.s_node );
542 * Sometimes the child process will send its first READ
543 * before the parent has sent the OPEN REPLY. Moving this
544 * code into the OPEN/STATUS switch fixes this problem.
547 iov.iov_len = 8 + getstatus( pr, &rbuf[ 8 ] );
548 atpb.atp_sresiov = &iov;
549 atpb.atp_sresiovcnt = 1;
551 * This may error out if we lose a route, so we won't die().
553 if ( atp_sresp( pr->p_atp, &atpb ) < 0 ) {
554 LOG(log_error, logtype_papd, "atp_sresp: %m" );
564 * We assume buf is big enough for 255 bytes of data and a length byte.
567 int getstatus( pr, buf )
573 if ( pr->p_flags & P_PIPED ) {
574 *buf = strlen( cannedstatus );
575 strncpy( &buf[ 1 ], cannedstatus, *buf );
578 cups_get_printer_status( pr );
579 *buf = strlen ( pr->p_status );
580 strncpy ( &buf[1], pr->p_status, *buf);
585 char path[ MAXPATHLEN ];
588 if ( pr->p_flags & P_SPOOLED && ( pr->p_spool != NULL )) {
589 strcpy( path, pr->p_spool );
590 strcat( path, "/status" );
591 fd = open( path, O_RDONLY);
594 if (( pr->p_flags & P_PIPED ) || ( fd < 0 )) {
595 *buf = strlen( cannedstatus );
596 strncpy( &buf[ 1 ], cannedstatus, *buf );
599 if (( rc = read( fd, &buf[ 1 ], 255 )) < 0 ) {
603 if ( rc && buf[ rc ] == '\n' ) { /* remove trailing newline */
609 #endif /* HAVE_CUPS */
613 char *getpname(char **area, int bufsize);
615 #define PF_CONFBUFFER 1024
617 static void getprinters( cf )
620 char buf[ PF_CONFBUFFER ], area[ PF_CONFBUFFER ], *a, *p, *name, *type, *zone;
624 while (( c = getprent( cf, buf, PF_CONFBUFFER )) > 0 ) {
627 * Get the printer's nbp name.
629 if (( p = getpname( &a, PF_CONFBUFFER )) == NULL ) {
630 fprintf( stderr, "No printer name\n" );
634 if (( pr = (struct printer *)malloc( sizeof( struct printer )))
639 memset( pr, 0, sizeof( struct printer ));
641 name = defprinter.p_name;
642 type = defprinter.p_type;
643 zone = defprinter.p_zone;
644 if ( nbp_name( p, &name, &type, &zone )) {
645 fprintf( stderr, "Can't parse \"%s\"\n", name );
648 if ( name != defprinter.p_name ) {
649 if (( pr->p_name = (char *)malloc( strlen( name ) + 1 )) == NULL ) {
653 strcpy( pr->p_name, name );
657 if ( type != defprinter.p_type ) {
658 if (( pr->p_type = (char *)malloc( strlen( type ) + 1 )) == NULL ) {
662 strcpy( pr->p_type, type );
666 if ( zone != defprinter.p_zone ) {
667 if (( pr->p_zone = (char *)malloc( strlen( zone ) + 1 )) == NULL ) {
671 strcpy( pr->p_zone, zone );
676 if ( pnchktc( cf ) != 1 ) {
677 fprintf( stderr, "Bad papcap entry\n" );
684 if (( p = pgetstr( "pd", &a )) == NULL ) {
685 pr->p_ppdfile = defprinter.p_ppdfile;
687 if (( pr->p_ppdfile = (char *)malloc( strlen( p ) + 1 )) == NULL ) {
691 strcpy( pr->p_ppdfile, p );
695 * Get lpd printer name.
697 if (( p = pgetstr( "pr", &a )) == NULL ) {
698 pr->p_printer = defprinter.p_printer;
699 pr->p_flags = defprinter.p_flags;
703 pr->p_flags = P_PIPED;
705 pr->p_flags = P_SPOOLED;
707 if (( pr->p_printer = (char *)malloc( strlen( p ) + 1 )) == NULL ) {
711 strcpy( pr->p_printer, p );
715 * Do we want authenticated printing?
717 if ((p = pgetstr( "ca", &a )) != NULL ) {
718 if ((pr->p_authprintdir = (char *)malloc(strlen(p)+1)) == NULL) {
722 strcpy( pr->p_authprintdir, p );
723 pr->p_flags |= P_AUTH;
724 pr->p_flags |= P_AUTH_CAP;
725 } else { pr->p_authprintdir = NULL; }
727 if ( pgetflag( "sp" ) == 1 ) {
728 pr->p_flags |= P_AUTH;
729 pr->p_flags |= P_AUTH_PSSP;
732 if ((p = pgetstr("am", &a)) != NULL ) {
733 if ((uamlist = (char *)malloc(strlen(p)+1)) == NULL ) {
740 if ( pr->p_flags & P_SPOOLED ) {
744 if (( p = pgetstr( "op", &a )) == NULL ) {
745 pr->p_operator = defprinter.p_operator;
747 if (( pr->p_operator = (char *)malloc( strlen( p ) + 1 ))
752 strcpy( pr->p_operator, p );
756 /* get printer's appletalk address. */
757 if (( p = pgetstr( "pa", &a )) == NULL )
758 memcpy(&pr->p_addr, &defprinter.p_addr, sizeof(pr->p_addr));
760 atalk_aton(p, &pr->p_addr);
763 if ((p = pgetstr("co", &a)) != NULL ) {
764 pr->p_cupsoptions = strdup(p);
765 LOG (log_error, logtype_papd, "enabling cups-options for %s: %s", pr->p_name, pr->p_cupsoptions);
769 /* convert line endings for setup sections.
770 real ugly work around for foomatic deficiencies,
771 need to get rid of this */
772 if ( pgetflag("fo") == 1 ) {
773 pr->p_flags |= P_FOOMATIC_HACK;
774 LOG (log_error, logtype_papd, "enabling foomatic hack for %s", pr->p_name);
777 if (strncasecmp (pr->p_name, "cupsautoadd", 11) == 0)
780 pr = cups_autoadd_printers (pr, printers);
783 LOG (log_error, logtype_papd, "cupsautoadd: Cups support not compiled in");
784 #endif /* HAVE_CUPS */
788 if ( cups_check_printer ( pr, printers, 1) == 0)
790 pr->p_next = printers;
794 pr->p_next = printers;
796 #endif /* HAVE_CUPS */
801 } else { /* No capability file, do default */
802 printers = &defprinter;
814 if ( pr->p_flags & P_SPOOLED && !(pr->p_flags & P_CUPS_AUTOADDED) ) { /* Skip check if autoadded */
815 if ( cups_printername_ok ( pr->p_printer ) != 1) {
816 LOG(log_error, logtype_papd, "No such CUPS printer: '%s'", pr->p_printer );
822 * Check for ppd file, moved here because of cups_autoadd we cannot check at the usual location
825 if ( pr->p_ppdfile == defprinter.p_ppdfile ) {
826 if ( (p = (char *) cups_get_printer_ppd ( pr->p_printer )) != NULL ) {
827 if (( pr->p_ppdfile = (char *)malloc( strlen( p ) + 1 )) == NULL ) {
828 LOG(log_error, logtype_papd, "malloc: %m" );
831 strcpy( pr->p_ppdfile, p );
832 pr->p_flags |= P_CUPS_PPD;
833 /*LOG(log_info, logtype_papd, "PPD File for %s set to %s", pr->p_printer, pr->p_ppdfile );*/
840 char buf[ 1024 ], area[ 1024 ], *a, *p;
844 * Spool directory from printcap file.
846 if ( pr->p_flags & P_SPOOLED ) {
847 if ( pgetent( printcap, buf, pr->p_printer ) != 1 ) {
848 LOG(log_error, logtype_papd, "No such printer: %s", pr->p_printer );
855 if ( pr->p_spool != NULL && pr->p_spool != defprinter.p_spool ) {
859 if (( p = pgetstr( "sd", &a )) == NULL ) {
860 pr->p_spool = defprinter.p_spool;
862 if (( pr->p_spool = (char *)malloc( strlen( p ) + 1 )) == NULL ) {
863 LOG(log_error, logtype_papd, "malloc: %m" );
866 strcpy( pr->p_spool, p );
873 if ( pgetstr( "af", &a ) == NULL ) {
874 pr->p_flags &= ~P_ACCOUNT;
876 pr->p_flags |= P_ACCOUNT;
878 if ( pr->p_role != NULL && pr->p_role != defprinter.p_role ) {
882 if (( p = pgetstr( "ro", &a )) == NULL ) {
883 pr->p_role = defprinter.p_role;
886 (char *)malloc( strlen( p ) + 1 )) == NULL ) {
887 LOG(log_error, logtype_papd, "malloc: %m" );
890 strcpy( pr->p_role, p );
893 if (( c = pgetnum( "si" )) < 0 ) {
894 pr->p_srvid = defprinter.p_srvid;
898 #endif /* ABS_PRINT */
905 if ( pr->p_pagecost_msg != NULL &&
906 pr->p_pagecost_msg != defprinter.p_pagecost_msg ) {
907 free( pr->p_pagecost_msg );
910 if (( p = pgetstr( "pc", &a )) != NULL ) {
911 if (( pr->p_pagecost_msg =
912 (char *)malloc( strlen( p ) + 1 )) == NULL ) {
913 LOG(log_error, logtype_papd, "malloc: %m" );
916 strcpy( pr->p_pagecost_msg, p );
918 } else if ( pr->p_flags & P_ACCOUNT ) {
919 if (( c = pgetnum( "pc" )) < 0 ) {
920 pr->p_pagecost = defprinter.p_pagecost;
924 pr->p_pagecost_msg = NULL;
930 if ( pr->p_lock != NULL && pr->p_lock != defprinter.p_lock ) {
934 if (( p = pgetstr( "lo", &a )) == NULL ) {
935 pr->p_lock = defprinter.p_lock;
937 if (( pr->p_lock = (char *)malloc( strlen( p ) + 1 )) == NULL ) {
938 LOG(log_error, logtype_papd, "malloc: %m" );
941 strcpy( pr->p_lock, p );
946 * Must Kerberos authenticate?
948 if ( pgetflag( "ka" ) == 1 ) {
949 pr->p_flags |= P_KRB;
951 pr->p_flags &= ~P_KRB;
957 #endif /* HAVE_CUPS */