From owner-freebsd-current@FreeBSD.ORG Thu Apr 23 09:02:19 2015 Return-Path: Delivered-To: freebsd-current@freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:1900:2254:206a::19:1]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by hub.freebsd.org (Postfix) with ESMTPS id 4B8C158C for ; Thu, 23 Apr 2015 09:02:19 +0000 (UTC) Received: from vps1.elischer.org (vps1.elischer.org [204.109.63.16]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client CN "vps1.elischer.org", Issuer "CA Cert Signing Authority" (not verified)) by mx1.freebsd.org (Postfix) with ESMTPS id 2C1F41FEB for ; Thu, 23 Apr 2015 09:02:19 +0000 (UTC) Received: from Julian-MBP3.local (ppp121-45-229-105.lns20.per1.internode.on.net [121.45.229.105]) (authenticated bits=0) by vps1.elischer.org (8.14.9/8.14.9) with ESMTP id t3N92EYa020293 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES128-SHA bits=128 verify=NO) for ; Thu, 23 Apr 2015 02:02:17 -0700 (PDT) (envelope-from julian@freebsd.org) Message-ID: <5538B510.9040603@freebsd.org> Date: Thu, 23 Apr 2015 17:02:08 +0800 From: Julian Elischer User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:31.0) Gecko/20100101 Thunderbird/31.6.0 MIME-Version: 1.0 To: freebsd-current@freebsd.org Subject: Re: readdir/telldir/seekdir problem (i think) References: <55386505.70708@freebsd.org> In-Reply-To: <55386505.70708@freebsd.org> Content-Type: text/plain; charset=windows-1252; format=flowed Content-Transfer-Encoding: 7bit X-BeenThere: freebsd-current@freebsd.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: Discussions about the use of FreeBSD-current List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 23 Apr 2015 09:02:19 -0000 On 4/23/15 11:20 AM, Julian Elischer wrote: > I'm debugging a problem being seen with samba 3.6. > > basically telldir/seekdir/readdir don't seem to work as advertised.. ok so it looks like readdir() (and friends) is totally broken in the face of deletes unless you read the entire directory at once or reset to the the first file before the deletes, or earlier. So if you use readdir() to enumerate files in order to delete them, and you do it in sections in order to not have to load a potentially huge list of files all at once, you can not do it unless you rewind to 0 after each set of unlinks. (or you do all the unlinks in one hit). this appears to be because getdirentries (or maybe the filesystem behind) will 'compact' the returned data. At least this appears to be the case looking from the outside. Next step is to look at the getdirentries code.. > > here's a little test program > > #include > #include > #include > #include > #include > #include > #include > #include > #include > #include > > #define CHUNKSIZE 5 > #define TOTALFILES 40 > > static void > SeekDir(DIR *dirp, long loc) > { > printf("Seeking back to location %ld\n", loc); > seekdir(dirp, loc); > } > > static long > TellDir(DIR *dirp) > { > long loc; > > loc = telldir(dirp); > printf("telldir assigned location %ld\n", loc); > return (loc); > } > > int > main(int argc, char *argv[]) > { > DIR *dirp; > int i; > int j; > long offset = 0, prev_offset = 0; > char *files[100]; > char filename[100]; > int fd; > struct dirent *dp = NULL; > > if (chdir("./test2") != 0) { > err(EX_OSERR, "chdir"); > } > > /*****************************************************/ > /* Put a set of sample files in the target directory */ > /*****************************************************/ > > for (i=1; i < TOTALFILES ; i++) > { > sprintf(filename, "file-%d", i); > fd = open(filename, O_CREAT, 0666); > if (fd == -1) { > err(EX_OSERR, "open"); > } > close(fd); > } > dirp = opendir("."); > offset = TellDir(dirp); > for (i = 0; i < 20; i++) > files[i] = malloc(20); > > /*******************************************************/ > /* enumerate and delete small sets of files, one group */ > /* at a time. */ > /*******************************************************/ > do { > > /*****************************************/ > /* Read in up to CHUNKSIZE file names */ > /* i will be the number of files we hold */ > /*****************************************/ > for (i = 0; i < CHUNKSIZE; i++) { > if ((dp = readdir(dirp)) != NULL) { > strcpy(files[i], dp->d_name); > > printf("readdir (%ld) returned file %s\n", > offset, files[i]); > > prev_offset = offset; > offset = TellDir(dirp); > > } else { > printf("readdir returned null\n"); > break; > } > } > > > /****************************************************************/ > /* Simuate the last entry not fitting into our (samba's) > buffer */ > /* If we read someting in on the last slot, push it > back */ > /* Pretend it didn't fit. This is approximately what SAMBA > does.*/ > /****************************************************************/ > if (dp != NULL) { > /* Step back */ > SeekDir(dirp, prev_offset); > offset = TellDir(dirp); > i--; > printf("file %s returned\n", files[i]); > } > > /*****************************************/ > /* i is the number of names we have left.*/ > /* Delete them. */ > /*****************************************/ > for (j = 0; j < i; j++) { > if (*files[j] == '.') { > printf ("skipping %s\n", files[j]); > } else { > printf("Unlinking file %s\n", files[j]); > if (unlink(files[j]) != 0) { > err(EX_OSERR, "unlink"); > } > } > } > } while (dp != NULL); > > closedir(dirp); > //chdir(".."); > > } > > The program is simulating what Samba does when fails. (doing a > recursive delete of a directory) > What it does is reads a chunk of names using readdir() until it's > (small) buffer s full, > then it uses seekdir() to seek back before the last entry it read, > (which fails to fit), > theortically leaving it for the next read. > It then deletes the entries it found and repeats the cycle. > > Eventually it should have found all the files in the directory and > deleted them. > Except that it doesn't. > > What actually happens is that some files are not enumerated, even > though > the seekdir() should have made the readdir() find them. > for added fun. the FIRST seekdir appears to work. but all subsequent > ones don't. > > It behaves this way in -current , all the way back to at least 8.0. > > if there's a bug in my program please let me know, but samba has the > same problem.. e.g. on freeNAS. > > to use the program make a directory called "./test2" and then run it > in the current directory.. > It fills it with files and then tried to (fails) delete them in > small batches. > > here's some (annotated) output: > > ./testit > telldir assigned location 0 > readdir (0) returned file . > telldir assigned location 1 > readdir (1) returned file .. > telldir assigned location 2 > readdir (2) returned file file-1 > telldir assigned location 3 > readdir (3) returned file file-2 > telldir assigned location 4 > readdir (4) returned file file-3 > telldir assigned location 5 > >>>>> here we pretend the buffer was full and put the file > >>>>> marker back so that it will get read next time > Seeking back to location 4 > telldir assigned location 4 > file file-3 returned > skipping . > skipping .. > Unlinking file file-1 > Unlinking file file-2 > readdir (4) returned file file-3 > >>>>> hey it worked (this time) > telldir assigned location 5 > readdir (5) returned file file-4 > telldir assigned location 6 > readdir (6) returned file file-5 > telldir assigned location 7 > readdir (7) returned file file-6 > telldir assigned location 8 > readdir (8) returned file file-7 > telldir assigned location 9 > >>>>> OK do it again.. pretend file-7 didn't fit.. > >>>>> set the pointer back so we re-read it next time. > > Seeking back to location 8 > telldir assigned location 8 > file file-7 returned > Unlinking file file-3 > Unlinking file file-4 > Unlinking file file-5 > Unlinking file file-6 > >>>>> OK lets go get file-7 again > > readdir (8) returned file file-9 > >>>>> WTF? what happened to file-7 ? > > telldir assigned location 9 > readdir (9) returned file file-10 > telldir assigned location 10 > > > > > > _______________________________________________ > freebsd-current@freebsd.org mailing list > http://lists.freebsd.org/mailman/listinfo/freebsd-current > To unsubscribe, send any mail to > "freebsd-current-unsubscribe@freebsd.org" > >