diff --git a/constants.h b/constants.h index 5bba627..bc12430 100644 --- a/constants.h +++ b/constants.h @@ -340,6 +340,7 @@ enum RDP_INPUT_DEVICE /* NT status codes for RDPDR */ #define STATUS_SUCCESS 0x00000000 +#define STATUS_NOT_IMPLEMENTED 0x00000001 #define STATUS_PENDING 0x00000103 #define STATUS_NO_MORE_FILES 0x80000006 @@ -358,6 +359,7 @@ enum RDP_INPUT_DEVICE #define STATUS_FILE_IS_A_DIRECTORY 0xc00000ba #define STATUS_NOT_SUPPORTED 0xc00000bb #define STATUS_TIMEOUT 0xc0000102 +#define STATUS_NOTIFY_ENUM_DIR 0xc000010c #define STATUS_CANCELLED 0xc0000120 @@ -371,6 +373,8 @@ enum RDP_INPUT_DEVICE #define FILE_DIRECTORY_FILE 0x00000001 #define FILE_NON_DIRECTORY_FILE 0x00000040 +#define FILE_COMPLETE_IF_OPLOCKED 0x00000100 +#define FILE_DELETE_ON_CLOSE 0x00001000 #define FILE_OPEN_FOR_FREE_SPACE_QUERY 0x00800000 /* RDP5 disconnect PDU */ diff --git a/disk.c b/disk.c index 1761166..fb86178 100644 --- a/disk.c +++ b/disk.c @@ -81,6 +81,7 @@ extern RDPDR_DEVICE g_rdpdr_device[]; FILEINFO g_fileinfo[MAX_OPEN_FILES]; +BOOL g_notify_stamp = False; typedef struct { @@ -90,6 +91,7 @@ typedef struct char type[256]; } FsInfoType; +static NTSTATUS NotifyInfo(NTHANDLE handle, uint32 info_class, NOTIFY * p); static time_t get_create_time(struct stat *st) @@ -328,7 +330,7 @@ disk_create(uint32 device_id, uint32 accessmask, uint32 sharemode, uint32 create break; } - //printf("Open: \"%s\" flags: %u, accessmask: %u sharemode: %u create disp: %u\n", path, flags_and_attributes, accessmask, sharemode, create_disposition); + //printf("Open: \"%s\" flags: %X, accessmask: %X sharemode: %X create disp: %X\n", path, flags_and_attributes, accessmask, sharemode, create_disposition); // Get information about file and set that flag ourselfs if ((stat(path, &filestat) == 0) && (S_ISDIR(filestat.st_mode))) @@ -424,9 +426,15 @@ disk_create(uint32 device_id, uint32 accessmask, uint32 sharemode, uint32 create if (dirp) g_fileinfo[handle].pdir = dirp; + else + g_fileinfo[handle].pdir = NULL; + g_fileinfo[handle].device_id = device_id; g_fileinfo[handle].flags_and_attributes = flags_and_attributes; + g_fileinfo[handle].accessmask = accessmask; strncpy(g_fileinfo[handle].path, path, 255); + g_fileinfo[handle].delete_on_close = False; + g_notify_stamp = True; *phandle = handle; return STATUS_SUCCESS; @@ -439,14 +447,41 @@ disk_close(NTHANDLE handle) pfinfo = &(g_fileinfo[handle]); - if (pfinfo->flags_and_attributes & FILE_DIRECTORY_FILE) + g_notify_stamp = True; + + rdpdr_abort_io(handle, 0, STATUS_CANCELLED); + + if (pfinfo->pdir) { - closedir(pfinfo->pdir); - //FIXME: Should check exit code + if (closedir(pfinfo->pdir) < 0) + { + perror("closedir"); + return STATUS_INVALID_HANDLE; + } + + if (pfinfo->delete_on_close) + if (rmdir(pfinfo->path) < 0) + { + perror(pfinfo->path); + return STATUS_ACCESS_DENIED; + } + pfinfo->delete_on_close = False; } else { - close(handle); + if (close(handle) < 0) + { + perror("close"); + return STATUS_INVALID_HANDLE; + } + if (pfinfo->delete_on_close) + if (unlink(pfinfo->path) < 0) + { + perror(pfinfo->path); + return STATUS_ACCESS_DENIED; + } + + pfinfo->delete_on_close = False; } return STATUS_SUCCESS; @@ -477,7 +512,10 @@ disk_read(NTHANDLE handle, uint8 * data, uint32 length, uint32 offset, uint32 * switch (errno) { case EISDIR: - return STATUS_FILE_IS_A_DIRECTORY; + /* Implement 24 Byte directory read ?? + with STATUS_NOT_IMPLEMENTED server doesn't read again */ + /* return STATUS_FILE_IS_A_DIRECTORY; */ + return STATUS_NOT_IMPLEMENTED; default: perror("read"); return STATUS_INVALID_PARAMETER; @@ -600,10 +638,9 @@ disk_query_information(NTHANDLE handle, uint32 info_class, STREAM out) NTSTATUS disk_set_information(NTHANDLE handle, uint32 info_class, STREAM in, STREAM out) { - uint32 length, file_attributes, ft_high, ft_low; + uint32 length, file_attributes, ft_high, ft_low, delete_on_close; char newname[256], fullpath[256]; struct fileinfo *pfinfo; - int mode; struct stat filestat; time_t write_time, change_time, access_time, mod_time; @@ -611,6 +648,7 @@ disk_set_information(NTHANDLE handle, uint32 info_class, STREAM in, STREAM out) struct STATFS_T stat_fs; pfinfo = &(g_fileinfo[handle]); + g_notify_stamp = True; switch (info_class) { @@ -670,7 +708,7 @@ disk_set_information(NTHANDLE handle, uint32 info_class, STREAM in, STREAM out) printf("FileBasicInformation modification time %s", ctime(&tvs.modtime)); #endif - if (utime(pfinfo->path, &tvs)) + if (utime(pfinfo->path, &tvs) && errno != EPERM) return STATUS_ACCESS_DENIED; } @@ -728,25 +766,16 @@ disk_set_information(NTHANDLE handle, uint32 info_class, STREAM in, STREAM out) FileDispositionInformation requests with DeleteFile set to FALSE should unschedule the delete. See - http://www.osronline.com/article.cfm?article=245. Currently, - we are deleting the file immediately. I - guess this is a FIXME. */ + http://www.osronline.com/article.cfm?article=245. */ - //in_uint32_le(in, delete_on_close); + in_uint32_le(in, delete_on_close); - /* Make sure we close the file before - unlinking it. Not doing so would trigger - silly-delete if using NFS, which might fail - on FAT floppies, for example. */ - disk_close(handle); - - if ((pfinfo->flags_and_attributes & FILE_DIRECTORY_FILE)) // remove a directory + if (delete_on_close || + (pfinfo-> + accessmask & (FILE_DELETE_ON_CLOSE | FILE_COMPLETE_IF_OPLOCKED))) { - if (rmdir(pfinfo->path) < 0) - return STATUS_ACCESS_DENIED; + pfinfo->delete_on_close = True; } - else if (unlink(pfinfo->path) < 0) // unlink a file - return STATUS_ACCESS_DENIED; break; @@ -763,7 +792,7 @@ disk_set_information(NTHANDLE handle, uint32 info_class, STREAM in, STREAM out) /* prevents start of writing if not enough space left on device */ if (STATFS_FN(g_rdpdr_device[pfinfo->device_id].local_path, &stat_fs) == 0) - if (stat_fs.f_bsize * stat_fs.f_bfree < length) + if (stat_fs.f_bfree * stat_fs.f_bsize < length) return STATUS_DISK_FULL; if (ftruncate_growable(handle, length) != 0) @@ -780,20 +809,129 @@ disk_set_information(NTHANDLE handle, uint32 info_class, STREAM in, STREAM out) return STATUS_SUCCESS; } +NTSTATUS +disk_check_notify(NTHANDLE handle) +{ + struct fileinfo *pfinfo; + NTSTATUS status = STATUS_PENDING; + + NOTIFY notify; + + pfinfo = &(g_fileinfo[handle]); + if (!pfinfo->pdir) + return STATUS_INVALID_DEVICE_REQUEST; + + + + status = NotifyInfo(handle, pfinfo->info_class, ¬ify); + + if (status != STATUS_PENDING) + return status; + + if (memcmp(&pfinfo->notify, ¬ify, sizeof(NOTIFY))) + { + //printf("disk_check_notify found changed event\n"); + memcpy(&pfinfo->notify, ¬ify, sizeof(NOTIFY)); + status = STATUS_NOTIFY_ENUM_DIR; + } + + return status; + + +} + +NTSTATUS +disk_create_notify(NTHANDLE handle, uint32 info_class) +{ + + struct fileinfo *pfinfo; + NTSTATUS ret = STATUS_PENDING; + + /* printf("start disk_create_notify info_class %X\n", info_class); */ + + pfinfo = &(g_fileinfo[handle]); + pfinfo->info_class = info_class; + + ret = NotifyInfo(handle, info_class, &pfinfo->notify); + + if (info_class & 0x1000) + { /* ???? */ + if (ret == STATUS_PENDING) + return STATUS_SUCCESS; + } + + /* printf("disk_create_notify: num_entries %d\n", pfinfo->notify.num_entries); */ + + + return ret; + +} + +static NTSTATUS +NotifyInfo(NTHANDLE handle, uint32 info_class, NOTIFY * p) +{ + struct fileinfo *pfinfo; + struct stat buf; + struct dirent *dp; + char *fullname; + DIR *dpr; + + pfinfo = &(g_fileinfo[handle]); + if (fstat(handle, &buf) < 0) + { + perror("NotifyInfo"); + return STATUS_ACCESS_DENIED; + } + p->modify_time = buf.st_mtime; + p->status_time = buf.st_ctime; + p->num_entries = 0; + p->total_time = 0; + + + dpr = opendir(pfinfo->path); + if (!dpr) + { + perror("NotifyInfo"); + return STATUS_ACCESS_DENIED; + } + + + while ((dp = readdir(dpr))) + { + if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) + continue; + p->num_entries++; + fullname = xmalloc(strlen(pfinfo->path) + strlen(dp->d_name) + 2); + sprintf(fullname, "%s/%s", pfinfo->path, dp->d_name); + + if (!stat(fullname, &buf)) + { + p->total_time += (buf.st_mtime + buf.st_ctime); + } + + xfree(fullname); + } + closedir(dpr); + + return STATUS_PENDING; +} + static FsInfoType * FsVolumeInfo(char *fpath) { -#ifdef HAVE_MNTENT_H FILE *fdfs; - struct mntent *e; static FsInfoType info; +#ifdef HAVE_MNTENT_H + struct mntent *e; +#endif /* initialize */ memset(&info, 0, sizeof(info)); strcpy(info.label, "RDESKTOP"); strcpy(info.type, "RDPFS"); +#ifdef HAVE_MNTENT_H fdfs = setmntent(MNTENT_PATH, "r"); if (!fdfs) return &info; @@ -836,8 +974,6 @@ FsVolumeInfo(char *fpath) } endmntent(fdfs); #else - static FsInfoType info; - /* initialize */ memset(&info, 0, sizeof(info)); strcpy(info.label, "RDESKTOP"); diff --git a/proto.h b/proto.h index 8c13fda..233af76 100644 --- a/proto.h +++ b/proto.h @@ -32,6 +32,8 @@ NTSTATUS disk_query_information(NTHANDLE handle, uint32 info_class, STREAM out); NTSTATUS disk_set_information(NTHANDLE handle, uint32 info_class, STREAM in, STREAM out); NTSTATUS disk_query_volume_information(NTHANDLE handle, uint32 info_class, STREAM out); NTSTATUS disk_query_directory(NTHANDLE handle, uint32 info_class, char *pattern, STREAM out); +NTSTATUS disk_create_notify(NTHANDLE handle, uint32 info_class); +NTSTATUS disk_check_notify(NTHANDLE handle); /* mppc.c */ int mppc_expand(uint8 * data, uint32 clen, uint8 ctype, uint32 * roff, uint32 * rlen); /* ewmhints.c */ @@ -144,6 +146,7 @@ void sec_disconnect(void); /* serial.c */ int serial_enum_devices(uint32 * id, char *optarg); BOOL serial_get_timeout(NTHANDLE handle, uint32 length, uint32 * timeout, uint32 * itv_timeout); +BOOL serial_get_event(NTHANDLE handle, uint32 * result); /* tcp.c */ STREAM tcp_init(uint32 maxlen); void tcp_send(STREAM s); diff --git a/rdpdr.c b/rdpdr.c index 2e6573e..36e714c 100644 --- a/rdpdr.c +++ b/rdpdr.c @@ -61,6 +61,7 @@ extern DEVICE_FNS printer_fns; extern DEVICE_FNS parallel_fns; extern DEVICE_FNS disk_fns; extern FILEINFO g_fileinfo[]; +extern BOOL g_notify_stamp; static VCHANNEL *rdpdr_channel; @@ -323,8 +324,11 @@ rdpdr_send_completion(uint32 device, uint32 id, uint32 status, uint32 result, ui out_uint32_le(s, result); out_uint8p(s, buffer, length); s_mark_end(s); - /* JIF - hexdump(s->channel_hdr + 8, s->end - s->channel_hdr - 8); */ + /* JIF */ +#ifdef WITH_DEBUG_RDP5 + printf("--> rdpdr_send_completion\n"); + //hexdump(s->channel_hdr + 8, s->end - s->channel_hdr - 8); +#endif channel_send(s, rdpdr_channel); } @@ -623,15 +627,25 @@ rdpdr_process_irp(STREAM s) case IRP_MN_NOTIFY_CHANGE_DIRECTORY: /* JIF - unimpl("IRP major=0x%x minor=0x%x: IRP_MN_NOTIFY_CHANGE_DIRECTORY\n", major, minor); */ - status = STATUS_PENDING; // Don't send completion packet + unimpl("IRP major=0x%x minor=0x%x: IRP_MN_NOTIFY_CHANGE_DIRECTORY\n", major, minor); */ + + in_uint32_le(s, info_level); // notify mask + + g_notify_stamp = True; + + status = disk_create_notify(file, info_level); + result = 0; + + if (status == STATUS_PENDING) + add_async_iorequest(device, file, id, major, length, + fns, 0, 0, NULL, 0); break; default: status = STATUS_INVALID_PARAMETER; - /* JIF - unimpl("IRP major=0x%x minor=0x%x\n", major, minor); */ + /* JIF */ + unimpl("IRP major=0x%x minor=0x%x\n", major, minor); } break; @@ -659,6 +673,17 @@ rdpdr_process_irp(STREAM s) out.size = sizeof(buffer); status = fns->device_control(file, request, s, &out); result = buffer_len = out.p - out.data; + + /* Serial SERIAL_WAIT_ON_MASK */ + if (status == STATUS_PENDING) + { + if (add_async_iorequest + (device, file, id, major, length, fns, 0, 0, NULL, 0)) + { + status = STATUS_PENDING; + break; + } + } break; @@ -827,25 +852,34 @@ rdpdr_add_fds(int *n, fd_set * rfds, fd_set * wfds, struct timeval *tv, BOOL * t reconnecting. FIXME: Real support for reconnects. */ - if ((read(iorq->fd, &c, 0) != 0) && (errno == EBADF)) - break; - FD_SET(iorq->fd, rfds); *n = MAX(*n, iorq->fd); - // Check if io request timeout is smaller than current (but not 0). + /* Check if io request timeout is smaller than current (but not 0). */ if (iorq->timeout && (select_timeout == 0 || iorq->timeout < select_timeout)) { - // Set new timeout + /* Set new timeout */ select_timeout = iorq->timeout; g_min_timeout_fd = iorq->fd; /* Remember fd */ tv->tv_sec = select_timeout / 1000; tv->tv_usec = (select_timeout % 1000) * 1000; *timeout = True; + break; + } + if (iorq->itv_timeout && iorq->partial_len > 0 + && (select_timeout == 0 + || iorq->itv_timeout < select_timeout)) + { + /* Set new timeout */ + select_timeout = iorq->itv_timeout; + g_min_timeout_fd = iorq->fd; /* Remember fd */ + tv->tv_sec = select_timeout / 1000; + tv->tv_usec = (select_timeout % 1000) * 1000; + *timeout = True; + break; } - break; case IRP_MJ_WRITE: @@ -857,6 +891,11 @@ rdpdr_add_fds(int *n, fd_set * rfds, fd_set * wfds, struct timeval *tv, BOOL * t *n = MAX(*n, iorq->fd); break; + case IRP_MJ_DEVICE_CONTROL: + if (select_timeout > 5) + select_timeout = 5; /* serial event queue */ + break; + } } @@ -891,7 +930,7 @@ rdpdr_remove_iorequest(struct async_iorequest *prev, struct async_iorequest *ior /* Check if select() returned with one of the rdpdr file descriptors, and complete io if it did */ void -rdpdr_check_fds(fd_set * rfds, fd_set * wfds, BOOL timed_out) +_rdpdr_check_fds(fd_set * rfds, fd_set * wfds, BOOL timed_out) { NTSTATUS status; uint32 result = 0; @@ -899,9 +938,53 @@ rdpdr_check_fds(fd_set * rfds, fd_set * wfds, BOOL timed_out) struct async_iorequest *iorq; struct async_iorequest *prev; uint32 req_size = 0; + uint32 buffer_len; + struct stream out; + uint8 *buffer = NULL; + if (timed_out) { + /* check serial iv_timeout */ + + iorq = g_iorequest; + prev = NULL; + while (iorq != NULL) + { + if (iorq->fd == g_min_timeout_fd) + { + if ((iorq->partial_len > 0) && + (g_rdpdr_device[iorq->device].device_type == + DEVICE_TYPE_SERIAL)) + { + + /* iv_timeout between 2 chars, send partial_len */ + //printf("RDPDR: IVT total %u bytes read of %u\n", iorq->partial_len, iorq->length); + rdpdr_send_completion(iorq->device, + iorq->id, STATUS_SUCCESS, + iorq->partial_len, + iorq->buffer, iorq->partial_len); + iorq = rdpdr_remove_iorequest(prev, iorq); + return; + } + else + { + break; + } + + } + else + { + break; + } + + + prev = iorq; + if (iorq) + iorq = iorq->next; + + } + rdpdr_abort_io(g_min_timeout_fd, 0, STATUS_TIMEOUT); return; } @@ -929,7 +1012,7 @@ rdpdr_check_fds(fd_set * rfds, fd_set * wfds, BOOL timed_out) iorq->buffer + iorq->partial_len, req_size, iorq->offset, &result); - if (result > 0) + if ((long) result > 0) { iorq->partial_len += result; iorq->offset += result; @@ -940,8 +1023,7 @@ rdpdr_check_fds(fd_set * rfds, fd_set * wfds, BOOL timed_out) /* only delete link if all data has been transfered */ /* or if result was 0 and status success - EOF */ if ((iorq->partial_len == iorq->length) || - (g_rdpdr_device[iorq->device].device_type == - DEVICE_TYPE_SERIAL) || (result == 0)) + (result == 0)) { #if WITH_DEBUG_RDP5 DEBUG(("RDPDR: AIO total %u bytes read of %u\n", iorq->partial_len, iorq->length)); @@ -972,7 +1054,7 @@ rdpdr_check_fds(fd_set * rfds, fd_set * wfds, BOOL timed_out) iorq->partial_len, req_size, iorq->offset, &result); - if (result > 0) + if ((long) result > 0) { iorq->partial_len += result; iorq->offset += result; @@ -998,6 +1080,23 @@ rdpdr_check_fds(fd_set * rfds, fd_set * wfds, BOOL timed_out) iorq = rdpdr_remove_iorequest(prev, iorq); } } + break; + case IRP_MJ_DEVICE_CONTROL: + if (serial_get_event(iorq->fd, &result)) + { + buffer = (uint8 *) xrealloc((void *) buffer, 0x14); + out.data = out.p = buffer; + out.size = sizeof(buffer); + out_uint32_le(&out, result); + result = buffer_len = out.p - out.data; + status = STATUS_SUCCESS; + rdpdr_send_completion(iorq->device, iorq->id, + status, result, buffer, + buffer_len); + xfree(buffer); + iorq = rdpdr_remove_iorequest(prev, iorq); + } + break; } @@ -1007,8 +1106,68 @@ rdpdr_check_fds(fd_set * rfds, fd_set * wfds, BOOL timed_out) iorq = iorq->next; } + /* Check notify */ + iorq = g_iorequest; + prev = NULL; + while (iorq != NULL) + { + if (iorq->fd != 0) + { + switch (iorq->major) + { + + case IRP_MJ_DIRECTORY_CONTROL: + if (g_rdpdr_device[iorq->device].device_type == + DEVICE_TYPE_DISK) + { + + if (g_notify_stamp) + { + g_notify_stamp = False; + status = disk_check_notify(iorq->fd); + if (status != STATUS_PENDING) + { + rdpdr_send_completion(iorq->device, + iorq->id, + status, 0, + NULL, 0); + iorq = rdpdr_remove_iorequest(prev, + iorq); + } + } + } + break; + + + + } + } + + prev = iorq; + if (iorq) + iorq = iorq->next; + } + } +void +rdpdr_check_fds(fd_set * rfds, fd_set * wfds, BOOL timed_out) +{ + fd_set dummy; + + + FD_ZERO(&dummy); + + + /* fist check event queue only, + any serial wait event must be done before read block will be sent + */ + + _rdpdr_check_fds(&dummy, &dummy, False); + _rdpdr_check_fds(rfds, wfds, timed_out); +} + + /* Abort a pending io request for a given handle and major */ BOOL rdpdr_abort_io(uint32 fd, uint32 major, NTSTATUS status) diff --git a/serial.c b/serial.c index df62a49..f14fbd3 100644 --- a/serial.c +++ b/serial.c @@ -5,6 +5,12 @@ #include #include "rdesktop.h" +#ifdef WITH_DEBUG_SERIAL +#define DEBUG_SERIAL(args) printf args; +#else +#define DEBUG_SERIAL(args) +#endif + #define FILE_DEVICE_SERIAL_PORT 0x1b #define SERIAL_SET_BAUD_RATE 1 @@ -75,15 +81,39 @@ #define SERIAL_EV_EVENT2 0x1000 // Provider specific event 2 /* Modem Status */ +#define SERIAL_MS_DTR 0x01 +#define SERIAL_MS_RTS 0x02 #define SERIAL_MS_CTS 0x10 #define SERIAL_MS_DSR 0x20 #define SERIAL_MS_RNG 0x40 #define SERIAL_MS_CAR 0x80 +/* Handflow */ +#define SERIAL_DTR_CONTROL 0x01 +#define SERIAL_CTS_HANDSHAKE 0x08 +#define SERIAL_ERROR_ABORT 0x80000000 + +#define SERIAL_XON_HANDSHAKE 0x01 +#define SERIAL_XOFF_HANDSHAKE 0x02 +#define SERIAL_DSR_SENSITIVITY 0x40 + +#define SERIAL_CHAR_EOF 0 +#define SERIAL_CHAR_ERROR 1 +#define SERIAL_CHAR_BREAK 2 +#define SERIAL_CHAR_EVENT 3 +#define SERIAL_CHAR_XON 4 +#define SERIAL_CHAR_XOFF 5 + #ifndef CRTSCTS #define CRTSCTS 0 #endif +/* FIONREAD should really do the same thing as TIOCINQ, where it is + * not available */ +#ifndef TIOCINQ +#include +#define TIOCINQ FIONREAD +#endif extern RDPDR_DEVICE g_rdpdr_device[]; @@ -188,9 +218,19 @@ get_termios(SERIAL_DEVICE * pser_inf, NTHANDLE serial_fd) case B115200: pser_inf->baud_rate = 115200; break; +#endif +#ifdef B230400 + case B230400: + pser_inf->baud_rate = 230400; + break; +#endif +#ifdef B460800 + case B460800: + pser_inf->baud_rate = 460800; + break; #endif default: - pser_inf->baud_rate = 0; + pser_inf->baud_rate = 9600; break; } @@ -218,7 +258,27 @@ get_termios(SERIAL_DEVICE * pser_inf, NTHANDLE serial_fd) break; } - pser_inf->rts = (ptermios->c_cflag & CRTSCTS) ? 1 : 0; + if (ptermios->c_cflag & CRTSCTS) + { + pser_inf->control = SERIAL_DTR_CONTROL | SERIAL_CTS_HANDSHAKE | SERIAL_ERROR_ABORT; + } + else + { + pser_inf->control = SERIAL_DTR_CONTROL | SERIAL_ERROR_ABORT; + } + + pser_inf->xonoff = SERIAL_DSR_SENSITIVITY; + if (ptermios->c_iflag & IXON) + pser_inf->xonoff |= SERIAL_XON_HANDSHAKE; + + if (ptermios->c_iflag & IXOFF) + pser_inf->xonoff |= SERIAL_XOFF_HANDSHAKE; + + pser_inf->chars[SERIAL_CHAR_XON] = ptermios->c_cc[VSTART]; + pser_inf->chars[SERIAL_CHAR_XOFF] = ptermios->c_cc[VSTOP]; + pser_inf->chars[SERIAL_CHAR_EOF] = ptermios->c_cc[VEOF]; + pser_inf->chars[SERIAL_CHAR_BREAK] = ptermios->c_cc[VINTR]; + pser_inf->chars[SERIAL_CHAR_ERROR] = ptermios->c_cc[VKILL]; return True; } @@ -309,16 +369,31 @@ set_termios(SERIAL_DEVICE * pser_inf, NTHANDLE serial_fd) case 115200: speed = B115200; break; +#endif +#ifdef B230400 + case 230400: + speed = B115200; + break; +#endif +#ifdef B460800 + case 460800: + speed = B115200; + break; #endif default: - speed = B0; + speed = B9600; break; } +#if 0 /* on systems with separate ispeed and ospeed, we can remember the speed in ispeed while changing DTR with ospeed */ cfsetispeed(pser_inf->ptermios, speed); cfsetospeed(pser_inf->ptermios, pser_inf->dtr ? speed : 0); +#endif + + ptermios->c_cflag &= ~CBAUD; + ptermios->c_cflag |= speed; ptermios->c_cflag &= ~(CSTOPB | PARENB | PARODD | CSIZE | CRTSCTS); switch (pser_inf->stop_bits) @@ -326,6 +401,9 @@ set_termios(SERIAL_DEVICE * pser_inf, NTHANDLE serial_fd) case STOP_BITS_2: ptermios->c_cflag |= CSTOPB; break; + default: + ptermios->c_cflag &= ~CSTOPB; + break; } switch (pser_inf->parity) @@ -336,6 +414,9 @@ set_termios(SERIAL_DEVICE * pser_inf, NTHANDLE serial_fd) case ODD_PARITY: ptermios->c_cflag |= PARENB | PARODD; break; + case NO_PARITY: + ptermios->c_cflag &= ~(PARENB | PARODD); + break; } switch (pser_inf->word_length) @@ -354,8 +435,43 @@ set_termios(SERIAL_DEVICE * pser_inf, NTHANDLE serial_fd) break; } +#if 0 if (pser_inf->rts) ptermios->c_cflag |= CRTSCTS; + else + ptermios->c_cflag &= ~CRTSCTS; +#endif + + if (pser_inf->control & SERIAL_CTS_HANDSHAKE) + { + ptermios->c_cflag |= CRTSCTS; + } + else + { + ptermios->c_cflag &= ~CRTSCTS; + } + + + if (pser_inf->xonoff & SERIAL_XON_HANDSHAKE) + { + ptermios->c_iflag |= IXON | IMAXBEL; + } + if (pser_inf->xonoff & SERIAL_XOFF_HANDSHAKE) + { + ptermios->c_iflag |= IXOFF | IMAXBEL; + } + + if ((pser_inf->xonoff & (SERIAL_XOFF_HANDSHAKE | SERIAL_XON_HANDSHAKE)) == 0) + { + ptermios->c_iflag &= ~IXON; + ptermios->c_iflag &= ~IXOFF; + } + + ptermios->c_cc[VSTART] = pser_inf->chars[SERIAL_CHAR_XON]; + ptermios->c_cc[VSTOP] = pser_inf->chars[SERIAL_CHAR_XOFF]; + ptermios->c_cc[VEOF] = pser_inf->chars[SERIAL_CHAR_EOF]; + ptermios->c_cc[VINTR] = pser_inf->chars[SERIAL_CHAR_BREAK]; + ptermios->c_cc[VKILL] = pser_inf->chars[SERIAL_CHAR_ERROR]; tcsetattr(serial_fd, TCSANOW, ptermios); } @@ -434,31 +550,31 @@ serial_create(uint32 device_id, uint32 access, uint32 share_mode, uint32 disposi g_rdpdr_device[device_id].handle = serial_fd; /* some sane information */ - printf("INFO: SERIAL %s to %s\nINFO: speed %u baud, stop bits %u, parity %u, word length %u bits, dtr %u, rts %u\n", g_rdpdr_device[device_id].name, g_rdpdr_device[device_id].local_path, pser_inf->baud_rate, pser_inf->stop_bits, pser_inf->parity, pser_inf->word_length, pser_inf->dtr, pser_inf->rts); + DEBUG_SERIAL(("INFO: SERIAL %s to %s\nINFO: speed %u baud, stop bits %u, parity %u, word length %u bits, dtr %u, rts %u\n", g_rdpdr_device[device_id].name, g_rdpdr_device[device_id].local_path, pser_inf->baud_rate, pser_inf->stop_bits, pser_inf->parity, pser_inf->word_length, pser_inf->dtr, pser_inf->rts)); - printf("INFO: use stty to change settings\n"); - -/* ptermios->c_cflag = B115200 | CRTSCTS | CS8 | CLOCAL | CREAD; - ptermios->c_cflag |= CREAD; - ptermios->c_lflag |= ICANON; - ptermios->c_iflag = IGNPAR | ICRNL; - - tcsetattr(serial_fd, TCSANOW, ptermios); -*/ pser_inf->ptermios->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON); pser_inf->ptermios->c_oflag &= ~OPOST; - pser_inf->ptermios->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); + pser_inf->ptermios->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN | XCASE); pser_inf->ptermios->c_cflag &= ~(CSIZE | PARENB); pser_inf->ptermios->c_cflag |= CS8; + tcsetattr(serial_fd, TCSANOW, pser_inf->ptermios); + pser_inf->event_txempty = 0; + pser_inf->event_cts = 0; + pser_inf->event_dsr = 0; + pser_inf->event_rlsd = 0; + pser_inf->event_pending = 0; + *handle = serial_fd; /* all read and writes should be non blocking */ if (fcntl(*handle, F_SETFL, O_NONBLOCK) == -1) perror("fcntl"); + pser_inf->read_total_timeout_constant = 5; + return STATUS_SUCCESS; } @@ -468,6 +584,8 @@ serial_close(NTHANDLE handle) int i = get_device_index(handle); if (i >= 0) g_rdpdr_device[i].handle = 0; + + rdpdr_abort_io(handle, 0, STATUS_TIMEOUT); close(handle); return STATUS_SUCCESS; } @@ -478,6 +596,10 @@ serial_read(NTHANDLE handle, uint8 * data, uint32 length, uint32 offset, uint32 long timeout; SERIAL_DEVICE *pser_inf; struct termios *ptermios; +#ifdef WITH_DEBUG_SERIAL + int bytes_inqueue; +#endif + timeout = 90; pser_inf = get_serial_info(handle); @@ -511,10 +633,18 @@ serial_read(NTHANDLE handle, uint8 * data, uint32 length, uint32 offset, uint32 } tcsetattr(handle, TCSANOW, ptermios); +#ifdef WITH_DEBUG_SERIAL + ioctl(handle, TIOCINQ, &bytes_inqueue); + DEBUG_SERIAL(("serial_read inqueue: %d expected %d\n", bytes_inqueue, length)); +#endif *result = read(handle, data, length); - //hexdump(data, *read); +#ifdef WITH_DEBUG_SERIAL + DEBUG_SERIAL(("serial_read Bytes %d\n", *result)); + if (*result > 0) + hexdump(data, *result); +#endif return STATUS_SUCCESS; } @@ -522,16 +652,24 @@ serial_read(NTHANDLE handle, uint8 * data, uint32 length, uint32 offset, uint32 static NTSTATUS serial_write(NTHANDLE handle, uint8 * data, uint32 length, uint32 offset, uint32 * result) { + SERIAL_DEVICE *pser_inf; + + pser_inf = get_serial_info(handle); + *result = write(handle, data, length); + + if (*result > 0) + pser_inf->event_txempty = *result; + + DEBUG_SERIAL(("serial_write length %d, offset %d result %d\n", length, offset, *result)); + return STATUS_SUCCESS; } static NTSTATUS serial_device_control(NTHANDLE handle, uint32 request, STREAM in, STREAM out) { -#if 0 int flush_mask, purge_mask; -#endif uint32 result, modemstate; uint8 immediate; SERIAL_DEVICE *pser_inf; @@ -547,81 +685,134 @@ serial_device_control(NTHANDLE handle, uint32 request, STREAM in, STREAM out) request >>= 2; request &= 0xfff; - printf("SERIAL IOCTL %d\n", request); - switch (request) { case SERIAL_SET_BAUD_RATE: in_uint32_le(in, pser_inf->baud_rate); set_termios(pser_inf, handle); + DEBUG_SERIAL(("serial_ioctl -> SERIAL_SET_BAUD_RATE %d\n", pser_inf->baud_rate)); break; case SERIAL_GET_BAUD_RATE: out_uint32_le(out, pser_inf->baud_rate); + DEBUG_SERIAL(("serial_ioctl -> SERIAL_GET_BAUD_RATE %d\n", pser_inf->baud_rate)); break; case SERIAL_SET_QUEUE_SIZE: in_uint32_le(in, pser_inf->queue_in_size); in_uint32_le(in, pser_inf->queue_out_size); + DEBUG_SERIAL(("serial_ioctl -> SERIAL_SET_QUEUE_SIZE in %d out %d\n", + pser_inf->queue_in_size, pser_inf->queue_out_size)); break; case SERIAL_SET_LINE_CONTROL: in_uint8(in, pser_inf->stop_bits); in_uint8(in, pser_inf->parity); in_uint8(in, pser_inf->word_length); set_termios(pser_inf, handle); + DEBUG_SERIAL(("serial_ioctl -> SERIAL_SET_LINE_CONTROL stop %d parity %d word %d\n", + pser_inf->stop_bits, pser_inf->parity, pser_inf->word_length)); break; case SERIAL_GET_LINE_CONTROL: + DEBUG_SERIAL(("serial_ioctl -> SERIAL_GET_LINE_CONTROL\n")); out_uint8(out, pser_inf->stop_bits); out_uint8(out, pser_inf->parity); out_uint8(out, pser_inf->word_length); break; case SERIAL_IMMEDIATE_CHAR: + DEBUG_SERIAL(("serial_ioctl -> SERIAL_IMMEDIATE_CHAR\n")); in_uint8(in, immediate); serial_write(handle, &immediate, 1, 0, &result); break; case SERIAL_CONFIG_SIZE: + DEBUG_SERIAL(("serial_ioctl -> SERIAL_CONFIG_SIZE\n")); out_uint32_le(out, 0); break; case SERIAL_GET_CHARS: - out_uint8s(out, 6); + DEBUG_SERIAL(("serial_ioctl -> SERIAL_GET_CHARS\n")); + out_uint8a(out, pser_inf->chars, 6); break; case SERIAL_SET_CHARS: - in_uint8s(in, 6); + DEBUG_SERIAL(("serial_ioctl -> SERIAL_SET_CHARS\n")); + in_uint8a(in, pser_inf->chars, 6); +#ifdef WITH_DEBUG_SERIAL + hexdump(pser_inf->chars, 6); +#endif + set_termios(pser_inf, handle); break; case SERIAL_GET_HANDFLOW: - out_uint32_le(out, 0); - out_uint32_le(out, 3); /* Xon/Xoff */ - out_uint32_le(out, 0); - out_uint32_le(out, 0); + DEBUG_SERIAL(("serial_ioctl -> SERIAL_GET_HANDFLOW\n")); + get_termios(pser_inf, handle); + out_uint32_le(out, pser_inf->control); + out_uint32_le(out, pser_inf->xonoff); /* Xon/Xoff */ + out_uint32_le(out, pser_inf->onlimit); + out_uint32_le(out, pser_inf->offlimit); break; case SERIAL_SET_HANDFLOW: - in_uint8s(in, 16); + in_uint32_le(in, pser_inf->control); + in_uint32_le(in, pser_inf->xonoff); + in_uint32_le(in, pser_inf->onlimit); + in_uint32_le(in, pser_inf->offlimit); + DEBUG_SERIAL(("serial_ioctl -> SERIAL_SET_HANDFLOW %x %x %x %x\n", + pser_inf->control, pser_inf->xonoff, pser_inf->onlimit, + pser_inf->onlimit)); + set_termios(pser_inf, handle); break; case SERIAL_SET_TIMEOUTS: - in_uint8s(in, 20); + in_uint32(in, pser_inf->read_interval_timeout); + in_uint32(in, pser_inf->read_total_timeout_multiplier); + in_uint32(in, pser_inf->read_total_timeout_constant); + in_uint32(in, pser_inf->write_total_timeout_multiplier); + in_uint32(in, pser_inf->write_total_timeout_constant); + DEBUG_SERIAL(("serial_ioctl -> SERIAL_SET_TIMEOUTS read timeout %d %d %d\n", + pser_inf->read_interval_timeout, + pser_inf->read_total_timeout_multiplier, + pser_inf->read_total_timeout_constant)); break; case SERIAL_GET_TIMEOUTS: - out_uint8s(out, 20); + DEBUG_SERIAL(("serial_ioctl -> SERIAL_GET_TIMEOUTS read timeout %d %d %d\n", + pser_inf->read_interval_timeout, + pser_inf->read_total_timeout_multiplier, + pser_inf->read_total_timeout_constant)); + + out_uint32(out, pser_inf->read_interval_timeout); + out_uint32(out, pser_inf->read_total_timeout_multiplier); + out_uint32(out, pser_inf->read_total_timeout_constant); + out_uint32(out, pser_inf->write_total_timeout_multiplier); + out_uint32(out, pser_inf->write_total_timeout_constant); break; case SERIAL_GET_WAIT_MASK: - out_uint32(out, pser_inf->wait_mask); + DEBUG_SERIAL(("serial_ioctl -> SERIAL_GET_WAIT_MASK %X\n", pser_inf->wait_mask); + out_uint32(out, pser_inf->wait_mask)); break; case SERIAL_SET_WAIT_MASK: in_uint32(in, pser_inf->wait_mask); + DEBUG_SERIAL(("serial_ioctl -> SERIAL_SET_WAIT_MASK %X\n", pser_inf->wait_mask)); break; case SERIAL_SET_DTR: + DEBUG_SERIAL(("serial_ioctl -> SERIAL_SET_DTR\n")); + ioctl(handle, TIOCMGET, &result); + result |= TIOCM_DTR; + ioctl(handle, TIOCMSET, &result); pser_inf->dtr = 1; - set_termios(pser_inf, handle); break; case SERIAL_CLR_DTR: + DEBUG_SERIAL(("serial_ioctl -> SERIAL_CLR_DTR\n")); + ioctl(handle, TIOCMGET, &result); + result &= ~TIOCM_DTR; + ioctl(handle, TIOCMSET, &result); pser_inf->dtr = 0; - set_termios(pser_inf, handle); break; case SERIAL_SET_RTS: + DEBUG_SERIAL(("serial_ioctl -> SERIAL_SET_RTS\n")); + ioctl(handle, TIOCMGET, &result); + result |= TIOCM_RTS; + ioctl(handle, TIOCMSET, &result); pser_inf->rts = 1; - set_termios(pser_inf, handle); break; case SERIAL_CLR_RTS: + DEBUG_SERIAL(("serial_ioctl -> SERIAL_CLR_RTS\n")); + ioctl(handle, TIOCMGET, &result); + result &= ~TIOCM_RTS; + ioctl(handle, TIOCMSET, &result); pser_inf->rts = 0; - set_termios(pser_inf, handle); break; case SERIAL_GET_MODEMSTATUS: modemstate = 0; @@ -635,46 +826,76 @@ serial_device_control(NTHANDLE handle, uint32 request, STREAM in, STREAM out) modemstate |= SERIAL_MS_RNG; if (result & TIOCM_CAR) modemstate |= SERIAL_MS_CAR; + if (result & TIOCM_DTR) + modemstate |= SERIAL_MS_DTR; + if (result & TIOCM_RTS) + modemstate |= SERIAL_MS_RTS; #endif + DEBUG_SERIAL(("serial_ioctl -> SERIAL_GET_MODEMSTATUS %X\n", modemstate)); out_uint32_le(out, modemstate); break; case SERIAL_GET_COMMSTATUS: out_uint32_le(out, 0); /* Errors */ out_uint32_le(out, 0); /* Hold reasons */ - out_uint32_le(out, 0); /* Amount in in queue */ - out_uint32_le(out, 0); /* Amount in out queue */ + + result = 0; + ioctl(handle, TIOCINQ, &result); + out_uint32_le(out, result); /* Amount in in queue */ + if (result) + DEBUG_SERIAL(("serial_ioctl -> SERIAL_GET_COMMSTATUS in queue %d\n", result)); + + result = 0; + ioctl(handle, TIOCOUTQ, &result); + out_uint32_le(out, result); /* Amount in out queue */ + if (result) + DEBUG_SERIAL(("serial_ioctl -> SERIAL_GET_COMMSTATUS out queue %d\n", result)); + out_uint8(out, 0); /* EofReceived */ out_uint8(out, 0); /* WaitForImmediate */ break; -#if 0 case SERIAL_PURGE: - printf("SERIAL_PURGE\n"); in_uint32(in, purge_mask); - if (purge_mask & 0x04) + DEBUG_SERIAL(("serial_ioctl -> SERIAL_PURGE purge_mask %X\n", purge_mask)); + flush_mask = 0; + if (purge_mask & SERIAL_PURGE_TXCLEAR) flush_mask |= TCOFLUSH; - if (purge_mask & 0x08) + if (purge_mask & SERIAL_PURGE_RXCLEAR) flush_mask |= TCIFLUSH; if (flush_mask != 0) tcflush(handle, flush_mask); - if (purge_mask & 0x01) + if (purge_mask & SERIAL_PURGE_TXABORT) rdpdr_abort_io(handle, 4, STATUS_CANCELLED); - if (purge_mask & 0x02) + if (purge_mask & SERIAL_PURGE_RXABORT) rdpdr_abort_io(handle, 3, STATUS_CANCELLED); break; case SERIAL_WAIT_ON_MASK: - /* XXX implement me */ - out_uint32_le(out, pser_inf->wait_mask); + DEBUG_SERIAL(("serial_ioctl -> SERIAL_WAIT_ON_MASK %X\n", pser_inf->wait_mask)); + pser_inf->event_pending = 1; + if (serial_get_event(handle, &result)) + { + DEBUG_SERIAL(("WAIT end event = %x\n", result)); + out_uint32_le(out, result); + break; + } + return STATUS_PENDING; break; case SERIAL_SET_BREAK_ON: - tcsendbreak(serial_fd, 0); + DEBUG_SERIAL(("serial_ioctl -> SERIAL_SET_BREAK_ON\n")); + tcsendbreak(handle, 0); break; case SERIAL_RESET_DEVICE: - case SERIAL_SET_BREAK_OFF: - case SERIAL_SET_XOFF: - case SERIAL_SET_XON: - /* ignore */ + DEBUG_SERIAL(("serial_ioctl -> SERIAL_RESET_DEVICE\n")); + break; + case SERIAL_SET_BREAK_OFF: + DEBUG_SERIAL(("serial_ioctl -> SERIAL_SET_BREAK_OFF\n")); + break; + case SERIAL_SET_XOFF: + DEBUG_SERIAL(("serial_ioctl -> SERIAL_SET_XOFF\n")); + break; + case SERIAL_SET_XON: + DEBUG_SERIAL(("serial_ioctl -> SERIAL_SET_XON\n")); + tcflow(handle, TCION); break; -#endif default: unimpl("SERIAL IOCTL %d\n", request); return STATUS_INVALID_PARAMETER; @@ -683,6 +904,100 @@ serial_device_control(NTHANDLE handle, uint32 request, STREAM in, STREAM out) return STATUS_SUCCESS; } +BOOL +serial_get_event(NTHANDLE handle, uint32 * result) +{ + int index; + SERIAL_DEVICE *pser_inf; + int bytes; + BOOL ret = False; + + *result = 0; + index = get_device_index(handle); + if (index < 0) + return False; + + pser_inf = (SERIAL_DEVICE *) g_rdpdr_device[index].pdevice_data; + + + ioctl(handle, TIOCINQ, &bytes); + + if (bytes > 0) + { + DEBUG_SERIAL(("serial_get_event Bytes %d\n", bytes)); + if (bytes > pser_inf->event_rlsd) + { + pser_inf->event_rlsd = bytes; + if (pser_inf->wait_mask & SERIAL_EV_RLSD) + { + DEBUG_SERIAL(("Event -> SERIAL_EV_RLSD \n")); + *result |= SERIAL_EV_RLSD; + ret = True; + } + + } + + if ((bytes > 1) && (pser_inf->wait_mask & SERIAL_EV_RXFLAG)) + { + DEBUG_SERIAL(("Event -> SERIAL_EV_RXFLAG Bytes %d\n", bytes)); + *result |= SERIAL_EV_RXFLAG; + ret = True; + } + if ((pser_inf->wait_mask & SERIAL_EV_RXCHAR)) + { + DEBUG_SERIAL(("Event -> SERIAL_EV_RXCHAR Bytes %d\n", bytes)); + *result |= SERIAL_EV_RXCHAR; + ret = True; + } + + } + else + { + pser_inf->event_rlsd = 0; + } + + + ioctl(handle, TIOCOUTQ, &bytes); + if ((bytes == 0) + && (pser_inf->event_txempty > 0) && (pser_inf->wait_mask & SERIAL_EV_TXEMPTY)) + { + + DEBUG_SERIAL(("Event -> SERIAL_EV_TXEMPTY\n")); + *result |= SERIAL_EV_TXEMPTY; + ret = True; + } + pser_inf->event_txempty = bytes; + + + ioctl(handle, TIOCMGET, &bytes); + if ((bytes & TIOCM_DSR) != pser_inf->event_dsr) + { + pser_inf->event_dsr = bytes & TIOCM_DSR; + if (pser_inf->wait_mask & SERIAL_EV_DSR) + { + DEBUG_SERIAL(("event -> SERIAL_EV_DSR %s\n", (bytes & TIOCM_DSR) ? "ON" : "OFF")); + *result |= SERIAL_EV_DSR; + ret = True; + } + } + + if ((bytes & TIOCM_CTS) != pser_inf->event_cts) + { + pser_inf->event_cts = bytes & TIOCM_CTS; + if (pser_inf->wait_mask & SERIAL_EV_CTS) + { + DEBUG_SERIAL((" EVENT-> SERIAL_EV_CTS %s\n", (bytes & TIOCM_CTS) ? "ON" : "OFF")); + *result |= SERIAL_EV_CTS; + ret = True; + } + } + + if (ret) + pser_inf->event_pending = 0; + + return ret; +} + /* Read timeout for a given file descripter (device) when adding fd's to select() */ BOOL serial_get_timeout(NTHANDLE handle, uint32 length, uint32 * timeout, uint32 * itv_timeout) diff --git a/types.h b/types.h index 197120d..bcad57e 100644 --- a/types.h +++ b/types.h @@ -197,6 +197,7 @@ typedef struct rdpdr_serial_device_info { int dtr; int rts; + uint32 control, xonoff, onlimit, offlimit; uint32 baud_rate, queue_in_size, queue_out_size, @@ -206,7 +207,9 @@ typedef struct rdpdr_serial_device_info read_total_timeout_constant, write_total_timeout_multiplier, write_total_timeout_constant, posix_wait_mask; uint8 stop_bits, parity, word_length; + uint8 chars[6]; struct termios *ptermios, *pold_termios; + int event_txempty, event_cts, event_dsr, event_rlsd, event_pending; } SERIAL_DEVICE; @@ -235,13 +238,24 @@ typedef struct rdpdr_printer_info } PRINTER; +typedef struct notify_data +{ + time_t modify_time; + time_t status_time; + time_t total_time; + unsigned int num_entries; +} +NOTIFY; + typedef struct fileinfo { - uint32 device_id, flags_and_attributes; + uint32 device_id, flags_and_attributes, accessmask; char path[256]; DIR *pdir; struct dirent *pdirent; char pattern[64]; BOOL delete_on_close; + NOTIFY notify; + uint32 info_class; } FILEINFO; diff --git a/xwin.c b/xwin.c index 467114b..7c74d47 100644 --- a/xwin.c +++ b/xwin.c @@ -1435,12 +1435,9 @@ ui_select(int rdp_socket) error("select: %s\n", strerror(errno)); case 0: - /* TODO: if tv.tv_sec just times out - * we will segfault. - * FIXME: - */ - //s_timeout = True; - //rdpdr_check_fds(&rfds, &wfds, (BOOL) True); + /* Abort serial read calls */ + if (s_timeout) + rdpdr_check_fds(&rfds, &wfds, (BOOL) True); continue; }