11ab64890Smrg/* 21ab64890Smrg * (c) Copyright 1996 by Sebastien Marineau and Holger Veit 31ab64890Smrg * <marineau@genie.uottawa.ca> 41ab64890Smrg * <Holger.Veit@gmd.de> 51ab64890Smrg * 661b2299dSmrg * Permission is hereby granted, free of charge, to any person obtaining a 761b2299dSmrg * copy of this software and associated documentation files (the "Software"), 861b2299dSmrg * to deal in the Software without restriction, including without limitation 961b2299dSmrg * the rights to use, copy, modify, merge, publish, distribute, sublicense, 1061b2299dSmrg * and/or sell copies of the Software, and to permit persons to whom the 111ab64890Smrg * Software is furnished to do so, subject to the following conditions: 121ab64890Smrg * 131ab64890Smrg * The above copyright notice and this permission notice shall be included in 141ab64890Smrg * all copies or substantial portions of the Software. 1561b2299dSmrg * 161ab64890Smrg * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 171ab64890Smrg * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 1861b2299dSmrg * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 1961b2299dSmrg * HOLGER VEIT BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 2061b2299dSmrg * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF 2161b2299dSmrg * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 221ab64890Smrg * SOFTWARE. 2361b2299dSmrg * 2461b2299dSmrg * Except as contained in this notice, the name of Sebastien Marineau or Holger Veit 2561b2299dSmrg * shall not be used in advertising or otherwise to promote the sale, use or other 261ab64890Smrg * dealings in this Software without prior written authorization from Holger Veit or 271ab64890Smrg * Sebastien Marineau. 281ab64890Smrg * 291ab64890Smrg */ 301ab64890Smrg 311ab64890Smrg 321ab64890Smrg/* A few OS/2 functions needed in the X11 lib. Mainly, the file path redirection 331ab64890Smrg * functions and the "optimized" select() for the clients */ 341ab64890Smrg 351ab64890Smrg#define I_NEED_OS2_H 361ab64890Smrg#ifdef HAVE_CONFIG_H 371ab64890Smrg#include <config.h> 381ab64890Smrg#endif 391ab64890Smrg#include <X11/Xpoll.h> 401ab64890Smrg#include <stdio.h> 411ab64890Smrg#include <sys/errno.h> 421ab64890Smrg#define INCL_DOSSEMAPHORES 431ab64890Smrg#define INCL_DOSNPIPES 441ab64890Smrg#define INCL_DOSMISC 451ab64890Smrg#define INCL_DOSMODULEMGR 461ab64890Smrg#undef BOOL 471ab64890Smrg#undef BYTE 481ab64890Smrg#include <os2.h> 491ab64890Smrg#include <sys/select.h> 501ab64890Smrg#include <sys/time.h> 511ab64890Smrg 521ab64890Smrgchar *__XOS2RedirRoot(char *fname) 531ab64890Smrg{ 541ab64890Smrg /* This adds a further redirection by allowing the ProjectRoot 551ab64890Smrg * to be prepended by the content of the envvar X11ROOT. 561ab64890Smrg * This is for the purpose to move the whole X11 stuff to a different 571ab64890Smrg * disk drive. 581ab64890Smrg * The feature was added despite various environment variables 591ab64890Smrg * because not all file opens respect them. 601ab64890Smrg */ 611ab64890Smrg static char redirname[300]; /* enough for long filenames */ 621ab64890Smrg char *root; 631ab64890Smrg 641ab64890Smrg /* if name does not start with /, assume it is not root-based */ 651ab64890Smrg if (fname==0 || !(fname[0]=='/' || fname[0]=='\\')) 661ab64890Smrg return fname; 671ab64890Smrg 681ab64890Smrg root = (char*)getenv("X11ROOT"); 6961b2299dSmrg if (root==0 || 701ab64890Smrg (fname[1]==':' && isalpha(fname[0])) || 711ab64890Smrg (strlen(fname)+strlen(root)+2) > 300) 721ab64890Smrg return fname; 731ab64890Smrg sprintf(redirname,"%s%s",root,fname); 741ab64890Smrg return redirname; 751ab64890Smrg} 761ab64890Smrg 771ab64890Smrgchar *__XOS2RedirRoot1(char *format, char *arg1, char *arg2, char *arg3) 781ab64890Smrg{ 791ab64890Smrg /* this first constructs a name from a format and up to three 801ab64890Smrg * components, then adds a path 811ab64890Smrg */ 821ab64890Smrg char buf[300]; 831ab64890Smrg sprintf(buf,format,arg1,arg2,arg3); 841ab64890Smrg return __XOS2RedirRoot(buf); 851ab64890Smrg} 861ab64890Smrg 871ab64890Smrg/* This below implements select() for the calls in this file. It has been */ 881ab64890Smrg/* somewhat optimized for improved performance, but assumes a few */ 891ab64890Smrg/* things so it cannot be used as a general select. If both pipes and */ 901ab64890Smrg/* sockets are present, this may call the emx select */ 911ab64890Smrg 921ab64890Smrg 931ab64890SmrgHEV hPipeSem; 941ab64890SmrgHMODULE hmod_so32dll; 951ab64890Smrgstatic int (*os2_tcp_select)(int*,int,int,int,long); 961ab64890SmrgULONG os2_get_sys_millis(); 971ab64890Smrgextern int _files[]; 981ab64890Smrg 991ab64890Smrg#define MAX_TCP 256 1001ab64890Smrg/* These lifted from sys/emx.h. Change if that changes there! */ 1011ab64890Smrg#define F_SOCKET 0x10000000 1021ab64890Smrg#define F_PIPE 0x20000000 1031ab64890Smrg 1041ab64890Smrgstruct select_data 1051ab64890Smrg{ 1061ab64890Smrg fd_set read_copy; 1071ab64890Smrg fd_set write_copy; 1081ab64890Smrg BOOL have_read; 1091ab64890Smrg BOOL have_write; 1101ab64890Smrg int tcp_select_mask[MAX_TCP]; 1111ab64890Smrg int tcp_emx_handles[MAX_TCP]; 1121ab64890Smrg int tcp_select_copy[MAX_TCP]; 1131ab64890Smrg int socket_nread; 1141ab64890Smrg int socket_nwrite; 1151ab64890Smrg int socket_ntotal; 1161ab64890Smrg int pipe_ntotal; 1171ab64890Smrg int pipe_have_write; 1181ab64890Smrg int max_fds; 1191ab64890Smrg}; 1201ab64890Smrg 1211ab64890Smrgint os2ClientSelect(int nfds, fd_set *readfds, fd_set *writefds, 1221ab64890Smrg fd_set *exceptfds, struct timeval *timeout) 1231ab64890Smrg{ 1241ab64890Smrgstatic BOOL FirstTime=TRUE; 1251ab64890Smrgstatic haveTCPIP=TRUE; 1261ab64890SmrgULONG timeout_ms; 1271ab64890SmrgULONG postCount, start_millis,now_millis; 1281ab64890Smrgchar faildata[16]; 1291ab64890Smrgstruct select_data sd; 1301ab64890SmrgBOOL any_ready; 1311ab64890Smrgint np,ns, i,ready_handles,n; 1321ab64890SmrgAPIRET rc; 1331ab64890Smrg 13461b2299dSmrgsd.have_read=FALSE; sd.have_write=FALSE; 1351ab64890Smrgsd.socket_nread=0; sd.socket_nwrite=0; sd.socket_ntotal=0; 1361ab64890Smrgsd.max_fds=31; ready_handles=0; any_ready=FALSE; 1371ab64890Smrgsd.pipe_ntotal=0; sd.pipe_have_write=FALSE; 1381ab64890Smrg 1391ab64890Smrgif(FirstTime){ 1401ab64890Smrg /* First load the so32dll.dll module and get a pointer to the SELECT fn */ 1411ab64890Smrg 1421ab64890Smrg if((rc=DosLoadModule(faildata,sizeof(faildata),"SO32DLL",&hmod_so32dll))!=0){ 1431ab64890Smrg fprintf(stderr, "Could not load module so32dll.dll, rc = %d. Error note %s\n",rc,faildata); 1441ab64890Smrg haveTCPIP=FALSE; 1451ab64890Smrg } 1461ab64890Smrg if((rc = DosQueryProcAddr(hmod_so32dll, 0, "SELECT", (PPFN)&os2_tcp_select))!=0){ 1471ab64890Smrg fprintf(stderr, "Could not query address of SELECT, rc = %d.\n",rc); 1481ab64890Smrg haveTCPIP=FALSE; 1491ab64890Smrg } 1501ab64890Smrg /* Call these a first time to set the semaphore */ 1511ab64890Smrg rc = DosCreateEventSem(NULL, &hPipeSem, DC_SEM_SHARED, FALSE); 15261b2299dSmrg if(rc) { 1531ab64890Smrg fprintf(stderr, "Could not create event semaphore, rc=%d\n",rc); 1541ab64890Smrg return(-1); 1551ab64890Smrg } 1561ab64890Smrg rc = DosResetEventSem(hPipeSem, &postCount); 1571ab64890Smrg FirstTime = FALSE; 1581ab64890Smrg} 1591ab64890Smrg 1601ab64890Smrg/* Set up the time delay structs */ 1611ab64890Smrg 1621ab64890Smrg if(timeout!=NULL) { 1631ab64890Smrg timeout_ms=timeout->tv_sec*1000+timeout->tv_usec/1000; 1641ab64890Smrg } 1651ab64890Smrg else { timeout_ms=1000000; } /* This should be large enough... */ 1661ab64890Smrg if(timeout_ms>0) start_millis=os2_get_sys_millis(); 1671ab64890Smrg 1681ab64890Smrg/* Copy the masks */ 1691ab64890Smrg {FD_ZERO(&sd.read_copy);} 1701ab64890Smrg {FD_ZERO(&sd.write_copy);} 1711ab64890Smrg if(readfds!=NULL){ XFD_COPYSET(readfds,&sd.read_copy); sd.have_read=TRUE;} 1721ab64890Smrg if(writefds!=NULL) {XFD_COPYSET(writefds,&sd.write_copy);sd.have_write=TRUE;} 1731ab64890Smrg 1741ab64890Smrg/* And zero the original masks */ 1751ab64890Smrg if(sd.have_read){ FD_ZERO(readfds);} 1761ab64890Smrg if(sd.have_write) {FD_ZERO(writefds);} 1771ab64890Smrg if(exceptfds != NULL) {FD_ZERO(exceptfds);} 1781ab64890Smrg 1791ab64890Smrg/* Now we parse the fd_sets passed to select and separate pipe/sockets */ 1801ab64890Smrg n = os2_parse_select(&sd,nfds); 1811ab64890Smrg if(n == -1) { 1821ab64890Smrg errno = EBADF; 1831ab64890Smrg return (-1); 1841ab64890Smrg } 1851ab64890Smrg 1861ab64890Smrg/* Now we have three cases: either we have sockets, pipes, or both */ 1871ab64890Smrg/* We handle all three cases differently to optimize things */ 1881ab64890Smrg 1891ab64890Smrg/* Case 1: only pipes! */ 1901ab64890Smrg if((sd.pipe_ntotal >0) && (!sd.socket_ntotal)){ 1911ab64890Smrg np = os2_check_pipes(&sd,readfds,writefds); 1921ab64890Smrg if(np > 0){ 1931ab64890Smrg return (np); 1941ab64890Smrg } 1951ab64890Smrg else if (np == -1) { return(-1); } 1961ab64890Smrg while(!any_ready){ 1971ab64890Smrg rc = DosWaitEventSem(hPipeSem, timeout_ms); 1981ab64890Smrg if(rc == 640) { 1991ab64890Smrg return(0); 2001ab64890Smrg } 2011ab64890Smrg if((rc != 0) && (rc != 95)) {errno= EBADF; return(-1);} 2021ab64890Smrg np = os2_check_pipes(&sd,readfds,writefds); 2031ab64890Smrg if (np > 0){ 2041ab64890Smrg return(np); 2051ab64890Smrg } 2061ab64890Smrg else if (np < 0){ return(-1); } 2071ab64890Smrg } 2081ab64890Smrg } 2091ab64890Smrg 2101ab64890Smrg/* Case 2: only sockets. Just let the os/2 tcp select do the work */ 2111ab64890Smrg if((sd.socket_ntotal > 0) && (!sd.pipe_ntotal)){ 2121ab64890Smrg ns = os2_check_sockets(&sd, readfds, writefds, timeout_ms); 2131ab64890Smrg return (ns); 2141ab64890Smrg } 2151ab64890Smrg 2161ab64890Smrg/* Case 3: combination of both */ 2171ab64890Smrg if((sd.socket_ntotal > 0) && (sd.pipe_ntotal)){ 2181ab64890Smrg np = os2_check_pipes(&sd,readfds,writefds); 2191ab64890Smrg if(np > 0){ 2201ab64890Smrg any_ready=TRUE; 2211ab64890Smrg ready_handles += np; 2221ab64890Smrg } 2231ab64890Smrg else if (np == -1) { return(-1); } 2241ab64890Smrg 2251ab64890Smrg ns = os2_check_sockets(&sd,readfds,writefds, 0); 2261ab64890Smrg if(ns>0){ 2271ab64890Smrg ready_handles+=ns; 2281ab64890Smrg any_ready = TRUE; 2291ab64890Smrg } 2301ab64890Smrg else if (ns == -1) {return(-1);} 2311ab64890Smrg 2321ab64890Smrg while (!any_ready && timeout_ms){ 2331ab64890Smrg 2341ab64890Smrg rc = DosWaitEventSem(hPipeSem, 10L); 2351ab64890Smrg if(rc == 0){ 2361ab64890Smrg np = os2_check_pipes(&sd,readfds,writefds); 2371ab64890Smrg if(np > 0){ 2381ab64890Smrg ready_handles+=np; 2391ab64890Smrg any_ready = TRUE; 2401ab64890Smrg } 24161b2299dSmrg else if (np == -1) { 2421ab64890Smrg return(-1); } 2431ab64890Smrg } 2441ab64890Smrg 2451ab64890Smrg ns = os2_check_sockets(&sd,readfds,writefds,exceptfds, 0); 2461ab64890Smrg if(ns>0){ 2471ab64890Smrg ready_handles+=ns; 2481ab64890Smrg any_ready = TRUE; 2491ab64890Smrg } 2501ab64890Smrg else if (ns == -1) {return(-1);} 2511ab64890Smrg 25261b2299dSmrg if (i%8 == 0) { 2531ab64890Smrg now_millis = os2_get_sys_millis(); 2541ab64890Smrg if((now_millis-start_millis) > timeout_ms) timeout_ms = 0; 2551ab64890Smrg } 2561ab64890Smrg i++; 2571ab64890Smrg } 2581ab64890Smrg } 2591ab64890Smrg 2601ab64890Smrgreturn(ready_handles); 2611ab64890Smrg} 2621ab64890Smrg 2631ab64890Smrg 2641ab64890SmrgULONG os2_get_sys_millis() 2651ab64890Smrg{ 2661ab64890Smrg APIRET rc; 2671ab64890Smrg ULONG milli; 2681ab64890Smrg 2691ab64890Smrg rc = DosQuerySysInfo(14, 14, &milli, sizeof(milli)); 2701ab64890Smrg if(rc) { 2711ab64890Smrg fprintf(stderr,"Bad return code querying the millisecond counter! rc=%d\n",rc); 2721ab64890Smrg return(0); 2731ab64890Smrg } 2741ab64890Smrg return(milli); 2751ab64890Smrg} 2761ab64890Smrg 2771ab64890Smrgint os2_parse_select(sd,nfds) 2781ab64890Smrgstruct select_data *sd; 2791ab64890Smrgint nfds; 2801ab64890Smrg{ 2811ab64890Smrg int i; 2821ab64890Smrg APIRET rc; 2831ab64890Smrg/* First we determine up to which descriptor we need to check. */ 2849c019ec5Smaya/* No need to check up to 256 if we don't have to (and usually we don't...)*/ 2851ab64890Smrg/* Note: stuff here is hardcoded for fd_sets which are int[8] as in EMX!!! */ 2861ab64890Smrg 2871ab64890Smrg if(nfds > sd->max_fds){ 2881ab64890Smrg for(i=0;i<((FD_SETSIZE+31)/32);i++){ 2891ab64890Smrg if(sd->read_copy.fds_bits[i] || 2901ab64890Smrg sd->write_copy.fds_bits[i]) 2911ab64890Smrg sd->max_fds=(i*32) +32; 2921ab64890Smrg } 2931ab64890Smrg } 2941ab64890Smrg else { sd->max_fds = nfds; } 2951ab64890Smrg/* Check if result is greater than specified in select() call */ 2961ab64890Smrg if(sd->max_fds > nfds) sd->max_fds = nfds; 2971ab64890Smrg 2981ab64890Smrg if (sd->have_read) 2991ab64890Smrg { 3001ab64890Smrg for (i = 0; i < sd->max_fds; ++i) { 3011ab64890Smrg if (FD_ISSET (i, &sd->read_copy)){ 3021ab64890Smrg if(_files[i] & F_SOCKET) 3031ab64890Smrg { 3041ab64890Smrg sd->tcp_select_mask[sd->socket_ntotal]=_getsockhandle(i); 3051ab64890Smrg sd->tcp_emx_handles[sd->socket_ntotal]=i; 3061ab64890Smrg sd->socket_ntotal++; sd->socket_nread++; 3071ab64890Smrg } 3081ab64890Smrg else if (_files[i] & F_PIPE) 3091ab64890Smrg { 3101ab64890Smrg sd -> pipe_ntotal++; 3111ab64890Smrg rc = DosSetNPipeSem((HPIPE)i, (HSEM) hPipeSem, i); 3121ab64890Smrg if(rc) { fprintf(stderr,"Error SETNPIPE rc = %d\n",rc); return -1;} 3131ab64890Smrg } 3141ab64890Smrg } 3151ab64890Smrg } 3161ab64890Smrg } 3171ab64890Smrg 3181ab64890Smrg if (sd->have_write) 3191ab64890Smrg { 3201ab64890Smrg for (i = 0; i < sd->max_fds; ++i) { 3211ab64890Smrg if (FD_ISSET (i, &sd->write_copy)){ 3221ab64890Smrg if(_files[i] & F_SOCKET) 3231ab64890Smrg { 3241ab64890Smrg sd->tcp_select_mask[sd->socket_ntotal]=_getsockhandle(i); 3251ab64890Smrg sd->tcp_emx_handles[sd->socket_ntotal]=i; 3261ab64890Smrg sd->socket_ntotal++; sd->socket_nwrite++; 3271ab64890Smrg } 3281ab64890Smrg else if (_files[i] & F_PIPE) 3291ab64890Smrg { 3301ab64890Smrg sd -> pipe_ntotal++; 3311ab64890Smrg rc = DosSetNPipeSem((HPIPE)i, (HSEM) hPipeSem, i); 3321ab64890Smrg if(rc) { fprintf(stderr,"Error SETNPIPE rc = %d\n",rc); return -1;} 3331ab64890Smrg sd -> pipe_have_write=TRUE; 3341ab64890Smrg } 3351ab64890Smrg } 3361ab64890Smrg } 3371ab64890Smrg } 3381ab64890Smrg 3391ab64890Smrg 3401ab64890Smrgreturn(sd->socket_ntotal); 3411ab64890Smrg} 3421ab64890Smrg 3431ab64890Smrg 3441ab64890Smrgint os2_check_sockets(sd,readfds,writefds) 3451ab64890Smrgstruct select_data *sd; 3461ab64890Smrgfd_set *readfds,*writefds; 3471ab64890Smrg{ 3481ab64890Smrg int e,i; 3491ab64890Smrg int j,n; 3501ab64890Smrg memcpy(sd->tcp_select_copy,sd->tcp_select_mask, 3511ab64890Smrg sd->socket_ntotal*sizeof(int)); 35261b2299dSmrg 3531ab64890Smrg e = os2_tcp_select(sd->tcp_select_copy,sd->socket_nread, 3541ab64890Smrg sd->socket_nwrite, 0, 0); 3551ab64890Smrg 3561ab64890Smrg if(e == 0) return(e); 3571ab64890Smrg/* We have something ready? */ 3581ab64890Smrg if(e>0){ 3591ab64890Smrg j = 0; n = 0; 3601ab64890Smrg for (i = 0; i < sd->socket_nread; ++i, ++j) 3611ab64890Smrg if (sd->tcp_select_copy[j] != -1) 3621ab64890Smrg { 3631ab64890Smrg FD_SET (sd->tcp_emx_handles[j], readfds); 3641ab64890Smrg n ++; 3651ab64890Smrg } 3661ab64890Smrg for (i = 0; i < sd->socket_nwrite; ++i, ++j) 3671ab64890Smrg if (sd->tcp_select_copy[j] != -1) 3681ab64890Smrg { 3691ab64890Smrg FD_SET (sd->tcp_emx_handles[j], writefds); 3701ab64890Smrg n ++; 3711ab64890Smrg } 3721ab64890Smrg errno = 0; 37361b2299dSmrg 3741ab64890Smrg return n; 3751ab64890Smrg } 3761ab64890Smrg if(e<0){ 3771ab64890Smrg /*Error -- TODO */ 3781ab64890Smrg fprintf(stderr,"Error in server select! e=%d\n",e); 3791ab64890Smrg errno = EBADF; 3801ab64890Smrg return (-1); 3811ab64890Smrg } 3821ab64890Smrg } 3831ab64890Smrg 3841ab64890Smrg/* Check to see if anything is ready on pipes */ 3851ab64890Smrg 3861ab64890Smrgint os2_check_pipes(sd,readfds,writefds) 3871ab64890Smrgstruct select_data *sd; 3881ab64890Smrgfd_set *readfds,*writefds; 3891ab64890Smrg{ 3901ab64890Smrgint i,e; 3911ab64890SmrgULONG ulPostCount; 3921ab64890SmrgPIPESEMSTATE pipeSemState[128]; 3931ab64890SmrgAPIRET rc; 3941ab64890Smrg e = 0; 3951ab64890Smrg rc = DosResetEventSem(hPipeSem,&ulPostCount); 39661b2299dSmrg rc = DosQueryNPipeSemState((HSEM) hPipeSem, (PPIPESEMSTATE)&pipeSemState, 3971ab64890Smrg sizeof(pipeSemState)); 3981ab64890Smrg if(rc) fprintf(stderr,"SELECT: rc from QueryNPipeSem: %d\n",rc); 3991ab64890Smrg i=0; 4001ab64890Smrg while (pipeSemState[i].fStatus != 0) { 4011ab64890Smrg /*fprintf(stderr,"SELECT: sem entry, stat=%d, flag=%d, key=%d,avail=%d\n", 4021ab64890Smrg pipeSemState[i].fStatus,pipeSemState[i].fFlag,pipeSemState[i].usKey, 4031ab64890Smrg pipeSemState[i].usAvail); */ 4041ab64890Smrg if((pipeSemState[i].fStatus == 1) && 4051ab64890Smrg (FD_ISSET(pipeSemState[i].usKey,&sd->read_copy))){ 4061ab64890Smrg FD_SET(pipeSemState[i].usKey,readfds); 4071ab64890Smrg e++; 4081ab64890Smrg } 4091ab64890Smrg else if((pipeSemState[i].fStatus == 2) && 4101ab64890Smrg (FD_ISSET(pipeSemState[i].usKey,&sd->write_copy))){ 4111ab64890Smrg FD_SET(pipeSemState[i].usKey,writefds); 4121ab64890Smrg e++; 4131ab64890Smrg } 4141ab64890Smrg else if( (pipeSemState[i].fStatus == 3) && 4151ab64890Smrg ( (FD_ISSET(pipeSemState[i].usKey,&sd->read_copy)) || 4161ab64890Smrg (FD_ISSET(pipeSemState[i].usKey,&sd->write_copy)) )){ 4171ab64890Smrg errno = EBADF; 4181ab64890Smrg return (-1); 4191ab64890Smrg } 4201ab64890Smrg i++; 4211ab64890Smrg } /* endwhile */ 4221ab64890Smrg /*fprintf(stderr,"Done listing pipe sem entries, total %d entries, total ready entries %d\n",i,e);*/ 4231ab64890Smrgerrno = 0; 4241ab64890Smrgreturn(e); 4251ab64890Smrg} 4261ab64890Smrg 4271ab64890Smrg 4281ab64890Smrg 429