int max_ctx_on_plun(int cmd) { int i; int rc = 0; struct ctx myctx; struct ctx *p_ctx=&myctx; pid = getpid(); pthread_t thread; int max_p = MAX_OPENS; for (i=0; i<max_p;i++) { if (0==fork()) { //child process pid = getpid(); debug("%d: ......process %d created...\n",pid,i); memset(p_ctx, 0, sizeof(myctx)); strcpy(p_ctx->dev, cflash_path); if ((p_ctx->fd = open_dev(p_ctx->dev, O_RDWR)) < 0) { fprintf(stderr,"open failed %s, errno %d\n",cflash_path, errno); exit(rc); } #ifdef _AIX rc |= ioctl_dk_capi_query_path(p_ctx); rc|=ctx_init_internal(p_ctx, 0, p_ctx->devno); #else rc|=ctx_init_internal(p_ctx, 0x2, p_ctx->devno); #endif if (2 == cmd) rc |=create_resource(p_ctx,0,0,LUN_VIRTUAL); if (3 == cmd) rc |=create_resource(p_ctx,0,0,LUN_DIRECT); if (4 == cmd) { //do io all vluns created on path_id_mask pthread_create(&thread, NULL,ctx_rrq_rx,p_ctx); rc |= create_resource(p_ctx,p_ctx->chunk_size,0,LUN_VIRTUAL); rc |= do_io(p_ctx,0x10); pthread_cancel(thread); } sleep(10); //lets all context get created if ( 1 != cmd ) rc|=close_res(p_ctx); rc|=ctx_close(p_ctx); debug("%d:.exiting with rc=%d\n",pid,rc); exit(rc); } } rc=wait4all(); return rc; }
int test_fc_port_reset_vlun() { int rc; struct ctx myctx; struct ctx *p_ctx = &myctx; pthread_t thread; int ioCounter=0; __u64 nlba; __u64 stride=0x1; pid = getpid(); #ifdef _AIX memset(p_ctx, 0, sizeof(myctx)); strcpy(p_ctx->dev, cflash_path); if ((p_ctx->fd =open_dev(p_ctx->dev, O_RDWR)) < 0) { fprintf(stderr,"open %s failed, errno=%d\n",p_ctx->dev,errno); return -1; } rc = ioctl_dk_capi_query_path(p_ctx); CHECK_RC(rc,"dk_capi_query_path failed..\n"); rc = ctx_init_internal(p_ctx, 0,p_ctx->devno); #else rc = ctx_init(p_ctx); #endif CHECK_RC(rc, "Context init failed"); //thread to handle AFU interrupt & events pthread_create(&thread, NULL, ctx_rrq_rx, p_ctx); nlba = 1 * (p_ctx->chunk_size); rc = create_resource(p_ctx, nlba, 0, LUN_VIRTUAL); CHECK_RC(rc, "create LUN_VIRTUAL failed"); rc = compare_size(p_ctx->last_lba, nlba-1); CHECK_RC(rc, "failed compare_size"); debug("-- Going to start IO.Please do chportfc -reset <pnum> at texan --\n"); debug("rc=%d,g_error=%d\n",rc,g_error); do { rc = do_io(p_ctx, stride); if (rc !=0 ) { debug("rc=%d,ioCounter=%d,IO failed..... \n",rc,ioCounter); if ( ioCounter==1 ) { debug("rc=%d, Going to verify.... \n",rc); p_ctx->flags=DK_VF_LUN_RESET; #ifdef _AIX p_ctx->hint = DK_HINT_SENSE; #else p_ctx->hint = DK_CXLFLASH_VERIFY_HINT_SENSE; #endif rc = ioctl_dk_capi_verify(p_ctx); CHECK_RC(rc, "ioctl_dk_capi_verify failed\n"); } else { if (ioCounter > 1) { rc=-1; // IO failed third time break; } } } else { debug("rc=%d,IO succeeded \n",rc); g_error=0; } ioCounter++; rc|=g_error; sleep(3); } while ( rc !=0); debug("rc=%d,g_error=%d\n",rc,g_error); if ( ioCounter <= 1) { debug("WARNING: Test case not excuted properly... Please rerun\n"); rc =255; } pthread_cancel(thread); close_res(p_ctx); ctx_close(p_ctx); rc |= g_error; return rc; }
//int create_res_hndl_afu_reset(char *dev, dev64_t devno, __u64 chunk) int create_res_hndl_afu_reset(bool do_recover, bool last) { int rc; struct ctx my_ctx; struct ctx *p_ctx = &my_ctx; //int i; pthread_t thread; __u64 chunk = 0x1; __u64 stride= 0x1; int msgid; struct mymsgbuf msg_buf; pthread_t ioThreadId; do_io_thread_arg_t ioThreadData; do_io_thread_arg_t * p_ioThreadData=&ioThreadData; // we have to export "NO_IO; if we want to avoid IO char * noIOP = getenv("NO_IO"); pid = getpid(); #ifdef _AIX memset(p_ctx,0,sizeof(my_ctx)); strcpy(p_ctx->dev,cflash_path); if ((p_ctx->fd = open_dev(p_ctx->dev, O_RDWR)) < 0) { fprintf(stderr,"open failed %s, errno %d\n",p_ctx->dev, errno); return -1; } rc = ioctl_dk_capi_query_path(p_ctx); CHECK_RC(rc, "ioctl_dk_capi_query_path failed...\n"); rc = ctx_init_internal(p_ctx, 0, p_ctx->devno); #else rc = ctx_init(p_ctx); #endif CHECK_RC(rc, "Context init failed"); //thread to handle AFU interrupt & events if ( noIOP == NULL ) pthread_create(&thread, NULL, ctx_rrq_rx, p_ctx); //create 0 vlun size & later call resize ioctl rc = create_resource(p_ctx, chunk * (p_ctx->chunk_size), 0, LUN_VIRTUAL); CHECK_RC(rc, "create LUN_VIRTUAL failed"); //last new process send message to waiting process //that new ctx created now you can try to reattach msgid = msgget(key, IPC_CREAT | 0666); if (msgid < 0 ) { fprintf(stderr, "%d: msgget() failed before msgsnd()\n", pid); return -1; } memset(&msg_buf, 0, sizeof(struct mymsgbuf)); if (last) { goto end; } if ( noIOP == NULL ) { p_ioThreadData->p_ctx=p_ctx; p_ioThreadData->stride=stride; p_ioThreadData->loopCount=0x100000; // Need this to go on 10 secs debug("%d: things look good, doing IO...\n",pid); rc =pthread_create(&ioThreadId,NULL, do_io_thread, (void *)p_ioThreadData); CHECK_RC(rc, "do_io_thread() pthread_create failed"); } #ifdef _AIX rc = do_eeh(p_ctx); #else rc = do_poll_eeh(p_ctx); #endif g_error=0; //reset any prev error might caught while EEH if ( noIOP == NULL ) { pthread_join(ioThreadId, NULL); } #ifndef _AIX //for linux if ( noIOP == NULL ) pthread_cancel(thread); #endif //We here after EEH done if (do_recover) { //do if recover true debug("%d: woow EEH is done recovering...\n",pid); rc = ioctl_dk_capi_recover_ctx(p_ctx); CHECK_RC(rc, "ctx reattached failed"); msg_buf.mtype =2; strcpy(msg_buf.mtext, "K"); if (msgsnd(msgid, &msg_buf, 2, IPC_NOWAIT) < 0) { fprintf(stderr, "%d: msgsnd failed\n", pid); return -1; } #ifdef _AIX if (p_ctx->return_flags != DK_RF_REATTACHED) CHECK_RC(1, "recover ctx, expected DK_RF_REATTACHED"); p_ctx->flags = DK_VF_HC_TUR; p_ctx->hint = DK_HINT_SENSE; #endif fflush(stdout); ctx_reinit(p_ctx); #ifdef _AIX p_ctx->hint=DK_HINT_SENSE; #else p_ctx->hint=DK_CXLFLASH_VERIFY_HINT_SENSE; // if dummy_sense_flag is set; // a dummy sense data will be copied into ioctl input p_ctx->dummy_sense_flag=1; // if dummy_sense_flag is set; #endif rc = ioctl_dk_capi_verify(p_ctx); CHECK_RC(rc, "ioctl_dk_capi_verify failed"); #ifndef _AIX //for linux pthread_create(&thread, NULL, ctx_rrq_rx, p_ctx); #endif } else { //last one is /*msgid = msgget(key, IPC_CREAT | 0666); if(msgid < 0 ){ fprintf(stderr, "%d: msgget() failed before msgrcv()\n", pid); return -1; } debug("%d: Going to wait at msgrcv()..\n", pid); fflush(stdout); if(msgrcv(msgid, &msg_buf, 2, 1, 0) < 0) { fprintf(stderr, "%d: msgrcv failed with errno %d\n", pid, errno); return -1; } debug("%d: Got out of msgrcv()..EEH is done, Try to recover....\n",pid); */ //as per today(9/28/2015) discussion with Sanket that //new attach will fail until holding context not exited //hope same apply for Linux as well return 100; /*rc = ioctl_dk_capi_recover_ctx(p_ctx); if(rc) return 100; //this to make sure recover failed else { fprintf(stderr,"%d:com'on recover should fail here...\n",pid); return 1; // we don't want to try IO anyway }*/ } end: if ( noIOP == NULL ) { stride=0x1; rc = do_io(p_ctx, stride); CHECK_RC(rc, "IO failed after EEH/recover"); } if ( noIOP == NULL ) pthread_cancel(thread); sleep(1); fflush(stdout); sleep(5); // additional time to be safe ! rc=close_res(p_ctx); sleep(5); // Don't let child exit to keep max ctx alive rc |= ctx_close(p_ctx); CHECK_RC(rc,"ctx close or close_res failed\n"); return rc; }
int max_ctx_max_res(int cmd) { int rc; int i; struct ctx myctx; struct ctx *p_ctx=&myctx; int max_p = MAX_OPENS; // we can change MAX_OPEN Value in cflash_tests.h memset(p_ctx, 0, sizeof(struct ctx)); __u64 chunk = 0; __u64 lba; MAX_RES_HANDLE=get_max_res_hndl_by_capacity(cflash_path); if (MAX_RES_HANDLE <= 0) { fprintf(stderr,"Unable to run max_ctx_max_res.. refere prior error..\n"); return -1; } strcpy(p_ctx->dev, cflash_path); #ifdef _AIX p_ctx->fd =open_dev(p_ctx->dev, O_RDWR); if (p_ctx->fd < 0) { fprintf(stderr,"%s open failed...\n",p_ctx->dev); return -1; } rc = ioctl_dk_capi_query_path(p_ctx); CHECK_RC(rc, "query ioctl failed"); close(p_ctx->fd); #endif uint64_t chunk_size; if (2 == cmd) { lba = get_disk_last_lba(p_ctx->dev, p_ctx->devno, &chunk_size); if (lba == 0) { fprintf(stderr, "Failed to get last_lba of %s\n", p_ctx->dev); return -1; } chunk = (lba+1)/chunk_size; //chunks per context( each context will 16 VLUNS same size) chunk = chunk/(max_p * MAX_RES_HANDLE); if ( chunk == 0 ) { fprintf(stderr,"chunk-> 0X%"PRIX64" should >= max_p * MAX_RES_HANDLE=>%d\n", chunk,(max_p * MAX_RES_HANDLE)); return -1; } chunkRemain = chunk%(max_p * MAX_RES_HANDLE); } for (i = 0; i< max_p; i++) { if (0 == fork()) { //child process usleep(10000); if (2 == cmd) { if (i == max_p-1 ) { // this is the last context imLastContext=1; } rc = create_ctx_process(p_ctx->dev, p_ctx->devno,chunk); } else { rc = create_ctx_process(p_ctx->dev, p_ctx->devno,1); } debug("%d: exiting with rc=%d\n",getpid(), rc); exit(rc); } } rc = wait4all(); return rc; }
// 7.1.202 : Try to send a ctx_id in some ioctl before attach // (i.e. when no ctx is established). & some more scenarios int test_ioctl_spio_errcase() { int rc; int itr, type; struct ctx myctx; struct ctx *p_ctx = &myctx; char *fvt_dev; pthread_t threadId; __u64 nlba; // stride; //stride not used pid = getpid(); memset((void *)p_ctx, 0, sizeof(struct ctx)); // Case1 // ---------- IOCTL error cases w/o context : Start ---------- fvt_dev = getenv("FVT_DEV"); if (NULL == fvt_dev) { fprintf(stderr, "FVT_DEV ENV var NOT set, Please set...\n"); return -1; } strcpy(p_ctx->dev, fvt_dev); //open CAPI Flash disk device debug("Going to open CAPI Flash disk \n"); p_ctx->fd = open_dev(fvt_dev, O_RDWR); if (p_ctx->fd < 0) CHECK_RC(1, "capi device open() failed"); #ifdef _AIX // Get the devno. : only needed for AIX rc = ioctl_dk_capi_query_path(p_ctx); CHECK_RC(rc, "query path ioctl failed"); #endif #ifdef _AIX p_ctx->work.num_interrupts = 5; // use num_interrupts from AFU desc #else p_ctx->work.num_interrupts = 4; // use num_interrupts from AFU desc #endif /*_AIX*/ p_ctx->context_id = 0x1; rc = ioctl_dk_capi_detach(p_ctx); if ( 22 != rc ) CHECK_RC(1, "context detach ioctl did not fail"); p_ctx->flags = DK_UDF_ASSIGN_PATH; rc = ioctl_dk_capi_udirect(p_ctx); if ( 22 != rc ) CHECK_RC(1, "pLun creation did not fail"); g_error=0; // reset-ing the g_error close(p_ctx->fd); debug("Done. Close the fd \n"); // ---------- IOCTL error cases w/o context : End ---------- // Test for both pLun & vLun ! for (itr=0; itr<2; itr++) { debug("\n\n\n%d:-------- Start Itr# %d -------\n", pid, itr); // Test Case2 & Case3 for (type=0; type<2; type++) { debug("\n%d:-------- Start test type# %d -------\n", pid, type); // ctx_init with default flash disk & devno rc = ctx_init(p_ctx); CHECK_RC(rc, "Context init failed"); // thread to handle AFU interrupt & events rc = pthread_create(&threadId, NULL, ctx_rrq_rx, p_ctx); CHECK_RC(rc, "pthread_create failed"); if ( 0 == itr ) { // create plun rc = create_resource(p_ctx, 0, DK_UDF_ASSIGN_PATH, LUN_DIRECT); CHECK_RC(rc, "create LUN_DIRECT failed"); } else { nlba = p_ctx->chunk_size; // create vlun rc = create_resource(p_ctx,nlba, DK_UVF_ALL_PATHS, LUN_VIRTUAL); CHECK_RC(rc, "create LUN_VIRTUAL failed"); } if ( 0 == type ) { // -------- Case2: IO error cases after context detach ------- pthread_cancel(threadId); rc = ioctl_dk_capi_detach(p_ctx); CHECK_RC(rc, "detach ioctl failed"); } else { // -------- Case3: IO error cases after fd close ------- pthread_cancel(threadId); rc = close(p_ctx->fd); CHECK_RC(rc, "close(fd) failed"); debug("%d: close(p_ctx->fd) i.e. close(%d): done !\n", pid, p_ctx->fd); } // Reset rc if we reach this point. rc = 0; g_error=0; debug("rc %d, g_error =%d, errno =%d\n" , rc , g_error, errno ); //open CAPI Flash disk device again for clean using ioctls p_ctx->fd = open_dev(fvt_dev, O_RDWR); debug("After disk reopned -- rc %d, g_error =%d, errno =%d\n" , rc , g_error, errno ); if (p_ctx->fd < 0) CHECK_RC(1, "capi device open() failed"); debug("%d: disk re-opened, new fd: %d\n", pid, p_ctx->fd); cleanup(p_ctx, -1); } } return 0; }
// 7.1.194 : Test DK_CAPI_QUERY_PATH & DK_CAPI_ATTACH ioctl for FCP disk int test_ioctl_fcp() { int rc=0; struct ctx myctx; struct ctx *p_ctx = &myctx; char fc_dev[MC_PATHLEN]; dev64_t fc_devno; pid = getpid(); // Get the FC disk from env. rc = get_nonflash_disk(&fc_dev[0], &fc_devno); CHECK_RC(rc, "get_nonflash_disk() failed to get FC disk"); // Open fc disk p_ctx->fd = open(fc_dev, O_RDWR); if (p_ctx->fd < 0) { fprintf(stderr, "open() failed: device %s, errno %d\n", fc_dev, errno); return -1; } #ifdef _AIX // query path ioctl isn't supported on linux // Call query path ioctl rc = ioctl_dk_capi_query_path(p_ctx); if ( 0 == rc ) CHECK_RC(1, "ioctl_dk_capi_query_path didn't fail"); // Verify return values after ioctl // TBD: return_path_count need to be added to ctx. if ( p_ctx->return_path_count != 0 || p_ctx->return_flags != DK_RF_IOCTL_FAILED ) CHECK_RC(1, "returned_path_count/return_flags is incorrect.."); #endif /*_AIX */ // Prepare for attach ioctl p_ctx->flags = DK_AF_ASSIGN_AFU; p_ctx->work.num_interrupts = 4; // use num_interrupts from AFU desc #ifdef _AIX p_ctx->devno = fc_devno; #endif /*_AIX */ // Clear the previous RF before subsequent ioctl p_ctx->return_flags=0; strcpy(p_ctx->dev, fc_dev); //do context attach rc = ioctl_dk_capi_attach(p_ctx); if ( 0 == rc ) CHECK_RC(1, "ioctl_dk_capi_attach didn't fail"); // Verify return values after ioctl if ( p_ctx->return_flags != DK_RF_IOCTL_FAILED ) CHECK_RC(1, "return_flags is incorrect.."); // If we reach here, we return success. return 0; }
int ioctl_7_1_196() { int rc,i,j; struct ctx myctx[21],myctx_1, myctx_2; struct ctx *p_ctx[21],*p_ctx_1,*p_ctx_2; __u64 stride=0x1000,st_lba=0; pthread_t thread[20]; struct flash_disk disks[MAX_FDISK]; char disk1[30]; char disk2[30]; int cfdisk = MAX_FDISK; pid = getpid(); cfdisk = get_flash_disks(disks, FDISKS_SAME_ADPTR); //need to check the number of disks if (cfdisk < 2) { fprintf(stderr,"Must have 2 flash disks..\n"); TESTCASE_SKIP("Need disk from same adapter and each disk multipathed"); return 0; } strcpy(disk1,disks[0].dev); strcpy(disk2,disks[1].dev); // creating first context for (i=0;i<21;i++) { p_ctx[i]=&myctx[i]; } p_ctx_1=&myctx_1; p_ctx_2=&myctx_2; debug("1ST PROCEDURE\n"); // using p_ctx[[0] for LUN direct for firect disk /* rc = ctx_init2(p_ctx[0], disks[0].dev, DK_AF_ASSIGN_AFU, disks[0].devno[0]); pthread_create(&thread[0], NULL, ctx_rrq_rx, p_ctx[0]); */ /* rc = create_resource(p_ctx[0], 0, DK_UDF_ASSIGN_PATH, LUN_DIRECT); CHECK_RC(rc, "create LUN_DIRECT failed"); */ // creating another 19 context LUN VIRTUAL for ( i=2;i<21;i++) { sleep(2); rc = ctx_init2(p_ctx[i], disks[1].dev, DK_AF_ASSIGN_AFU, disks[1].devno[0]); rc=create_resource(p_ctx[i], p_ctx[i]->chunk_size, DK_UVF_ASSIGN_PATH, LUN_VIRTUAL); } // do context reuse for direct LUN strcpy(p_ctx[0]->dev,disks[0].dev); strcpy(p_ctx[1]->dev,disks[1].dev); p_ctx[0]->fd = open_dev(disks[0].dev, O_RDWR); if (p_ctx[0]->fd < 0) { fprintf(stderr, "open() failed: device %s, errno %d\n", disks[0].dev, errno); g_error = -1; return -1; } p_ctx[1]->fd = open_dev(disks[1].dev, O_RDWR); //Hoping to open second disk if (p_ctx[1]->fd < 0) { fprintf(stderr, "open() failed: device %s, errno %d\n", disks[1].dev, errno); g_error = -1; } #ifdef _AIX rc = ioctl_dk_capi_query_path(p_ctx[0]); CHECK_RC(rc, "DK_CAPI_QUERY_PATH failed"); #else //TBD for linux #endif p_ctx[0]->work.num_interrupts = p_ctx[1]->work.num_interrupts = 4; rc=ioctl_dk_capi_attach_reuse(p_ctx[0],p_ctx[1],LUN_DIRECT); // CHECK_RC(rc, "DK_CAPI_ATTACH with reuse flag failed"); if ( rc != 0 ) { fprintf(stderr,"LUN DIRECT got attached to new disk with VLUN, should have succeeded"); return rc; } // initiate I/O on all the LUNs for (i=2;i<21;i++) { pthread_create(&thread[i], NULL, ctx_rrq_rx, p_ctx[i]); rc = do_io(p_ctx[i], stride); } if ( rc != 0 ) { fprintf(stderr,"io on some LUN failed"); return rc; } /* using a goto-label removes the compile warning (-O3 issue) */ i=2; for_loop: pthread_cancel(thread[i]); close_res(p_ctx[i]); if (++i < 21) {goto for_loop;} ctx_close(p_ctx[2]); debug("2nd PROCEDURE\n"); // procedure 2 of the same case debug("%d: ........Phase 1 done.. Starting 2nd Phase........\n",getpid()); memset(p_ctx_1, 0, sizeof(struct ctx)); memset(p_ctx_2, 0, sizeof(struct ctx)); // open the first flash disk in write mode and create a DIRECT LUN // restoring from backup strcpy(disks[0].dev,disk1); p_ctx_1->fd = open_dev(disks[0].dev, O_WRONLY); if (p_ctx_1->fd < 0) { fprintf(stderr, "open() failed: device %s, errno %d\n", disks[0].dev, errno); return -1; } rc = ctx_init2(p_ctx_1, disks[0].dev, DK_AF_ASSIGN_AFU, disks[0].devno[0]); pthread_create(&thread[0], NULL, ctx_rrq_rx, p_ctx_1); CHECK_RC(rc, "create context failed"); rc = create_resource(p_ctx_1, 0, DK_UDF_ASSIGN_PATH, LUN_DIRECT); CHECK_RC(rc, "create LUN_DIRECT failed"); // open the same flash disk in read mode again. p_ctx_2->fd = open_dev(disks[0].dev, O_RDONLY); if (p_ctx_2->fd < 0) { fprintf(stderr, "open() failed: device %s, errno %d\n", disks[0].dev, errno); return -1; } rc = ctx_init2(p_ctx_2, disks[0].dev, DK_AF_ASSIGN_AFU, disks[0].devno[0]); pthread_create(&thread[1], NULL, ctx_rrq_rx, p_ctx_2); CHECK_RC(rc, "create context failed"); rc = create_resource(p_ctx_2, 0, DK_UDF_ASSIGN_PATH, LUN_DIRECT); CHECK_RC(rc, "create LUN_DIRECT failed"); // now write to the disk and then read for (st_lba = 0; st_lba <= p_ctx_1->last_lba; st_lba += (NUM_CMDS*stride)) { rc = send_write(p_ctx_1, st_lba, stride, pid); CHECK_RC(rc, "send_write failed"); rc = send_read(p_ctx_2, st_lba, stride); CHECK_RC(rc, "send_read failed"); /*if (rc !=0 ) { rc = rw_cmp_buf(p_ctx_1, st_lba); if (rc != 0) { fprintf(stderr,"buf cmp failed for lba 0x%lX,rc =%d\n",st_lba,rc); break; } }*/ } if ( rc != 0 ) return rc; for (i=0;i<2;i++) { pthread_cancel(thread[i]); } //close_res(p_ctx_1); ctx_close(p_ctx_1); //close_res(p_ctx_2); ctx_close(p_ctx_2); debug("3rd PROCEDURE\n"); debug("%d: ........Phase 2 done.. Starting 3rd Phase........\n",getpid()); // case 3 of the same case // creating multiple process for LUN_DIRECT creation. for (j=0;j<long_run;j++) { for (i=0; i<20;i++) { if ( 0 == fork()) { rc = ctx_init(p_ctx[i]); CHECK_RC_EXIT(rc, "Context init failed"); // CHECK_RC(rc, "Context init failed"); //thread to handle AFU interrupt & events rc = create_resource(p_ctx[i], 0, DK_UDF_ASSIGN_PATH , LUN_DIRECT); CHECK_RC_EXIT(rc, "create LUN_DIRECT failed"); // do io on context pthread_create(&thread[i], NULL, ctx_rrq_rx, p_ctx[i]); stride=0x1000; sleep(2); //do_io(p_ctx[i], stride); pthread_cancel(thread[i]); close_res(p_ctx[i]); exit(rc); } } wait4all(); } return 0; }