kpartx: fail if dup() of dasd file descriptor fails
[multipath-tools/.git] / kpartx / dasd.c
1 /*
2  * dasd.c
3  *
4  * IBM DASD partition table handling.
5  *
6  * Mostly taken from drivers/s390/block/dasd.c
7  *
8  * Copyright (c) 2005, Hannes Reinecke, SUSE Linux Products GmbH
9  * Copyright IBM Corporation, 2009
10  *
11  * This program is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU General Public License
13  * as published by the Free Software Foundation; either version 2
14  * of the License, or (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
23  */
24
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <unistd.h>
28 #include <inttypes.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <sys/sysmacros.h>
32 #include <sys/ioctl.h>
33 #include <linux/hdreg.h>
34 #include <errno.h>
35 #include <string.h>
36 #include <fcntl.h>
37 #include <libdevmapper.h>
38 #include "devmapper.h"
39 #include "kpartx.h"
40 #include "byteorder.h"
41 #include "dasd.h"
42
43 unsigned long long sectors512(unsigned long long sectors, int blocksize)
44 {
45         return sectors * (blocksize >> 9);
46 }
47
48 /*
49  * Magic records per track calculation, copied from fdasd.c
50  */
51 static unsigned int ceil_quot(unsigned int d1, unsigned int d2)
52 {
53         return (d1 + (d2 - 1)) / d2;
54 }
55
56 unsigned int recs_per_track(unsigned int dl)
57 {
58         int dn = ceil_quot(dl + 6, 232) + 1;
59         return 1729 / (10 + 9 + ceil_quot(dl + 6 * dn, 34));
60 }
61
62
63 typedef unsigned int __attribute__((__may_alias__)) label_ints_t;
64
65 /*
66  */
67 int
68 read_dasd_pt(int fd, struct slice all, struct slice *sp, int ns)
69 {
70         int retval = -1;
71         int blocksize;
72         uint64_t disksize;
73         uint64_t offset, size, fmt_size;
74         dasd_information_t info;
75         struct hd_geometry geo;
76         char type[5] = {0,};
77         volume_label_t vlabel;
78         unsigned char *data = NULL;
79         uint64_t blk;
80         int fd_dasd = -1;
81         struct stat sbuf;
82         dev_t dev;
83         char *devname;
84         char pathname[256];
85
86         if (fd < 0) {
87                 return -1;
88         }
89
90         if (fstat(fd, &sbuf) == -1) {
91                 return -1;
92         }
93
94         devname = dm_mapname(major(sbuf.st_rdev), minor(sbuf.st_rdev));
95
96         if (devname != NULL) {
97                 /* We were passed a handle to a dm device.
98                  * Get the first target and operate on that instead.
99                  */
100                 if (!(dev = dm_get_first_dep(devname))) {
101                         free(devname);
102                         return -1;
103                 }
104                 free(devname);
105
106                 if ((unsigned int)major(dev) != 94) {
107                         /* Not a DASD */
108                         return -1;
109                 }
110
111                 /*
112                  * Hard to believe, but there's no simple way to translate
113                  * major/minor into an openable device file, so we have
114                  * to create one for ourselves.
115                  */
116
117                 sprintf(pathname, "/dev/.kpartx-node-%u-%u",
118                         (unsigned int)major(dev), (unsigned int)minor(dev));
119                 if ((fd_dasd = open(pathname, O_RDONLY)) == -1) {
120                         /* Devicenode does not exist. Try to create one */
121                         if (mknod(pathname, 0600 | S_IFBLK, dev) == -1) {
122                                 /* Couldn't create a device node */
123                                 return -1;
124                         }
125                         fd_dasd = open(pathname, O_RDONLY);
126                         /*
127                          * The file will vanish when the last process (we)
128                          * has ceased to access it.
129                          */
130                         unlink(pathname);
131                 }
132                 if (fd_dasd < 0) {
133                         /* Couldn't open the device */
134                         return -1;
135                 }
136         } else if ((unsigned int)major(sbuf.st_rdev) != 94) {
137                         /* Not a DASD */
138                         return -1;
139         } else {
140                 fd_dasd = dup(fd);
141                 if (fd_dasd < 0)
142                         return -1;
143         }
144
145         if (ioctl(fd_dasd, BIODASDINFO, (unsigned long)&info) != 0) {
146                 info.label_block = 2;
147                 info.FBA_layout = 0;
148                 memcpy(info.type, "ECKD", sizeof(info.type));
149         }
150
151         if (ioctl(fd_dasd, BLKSSZGET, &blocksize) != 0)
152                 goto out;
153
154         if (ioctl(fd_dasd, BLKGETSIZE64, &disksize) != 0)
155                 goto out;
156
157         if (ioctl(fd_dasd, HDIO_GETGEO, (unsigned long)&geo) != 0) {
158                 unsigned int cyl;
159
160                 geo.heads = 15;
161                 geo.sectors = recs_per_track(blocksize);
162                 cyl = disksize / ((uint64_t)blocksize * geo.heads *
163                                   geo.sectors);
164                 if (cyl < LV_COMPAT_CYL)
165                         geo.cylinders = cyl;
166                 else
167                         geo.cylinders = LV_COMPAT_CYL;
168                 geo.start = 0;
169         }
170
171         disksize >>= 9;
172
173         if (blocksize < 512 || blocksize > 4096)
174                 goto out;
175
176         /*
177          * Get volume label, extract name and type.
178          */
179
180         if (!(data = (unsigned char *)malloc(blocksize)))
181                 goto out;
182
183
184         if (lseek(fd_dasd, info.label_block * blocksize, SEEK_SET) == -1)
185                 goto out;
186         if (read(fd_dasd, data, blocksize) == -1) {
187                 perror("read");
188                 goto out;
189         }
190
191         if ((!info.FBA_layout) && (!strcmp(info.type, "ECKD")))
192                 memcpy (&vlabel, data, sizeof(vlabel));
193         else {
194                 bzero(&vlabel,4);
195                 memcpy ((char *)&vlabel + 4, data, sizeof(vlabel) - 4);
196         }
197         vtoc_ebcdic_dec(vlabel.vollbl, type, 4);
198
199         /*
200          * Three different types: CMS1, VOL1 and LNX1/unlabeled
201          */
202         if (strncmp(type, "CMS1", 4) == 0) {
203                 /*
204                  * VM style CMS1 labeled disk
205                  */
206                 label_ints_t *label = (label_ints_t *) &vlabel;
207
208                 blocksize = label[4];
209                 if (label[14] != 0) {
210                         /* disk is reserved minidisk */
211                         offset = label[14];
212                         size   = sectors512(label[8] - 1, blocksize);
213                 } else {
214                         offset = info.label_block + 1;
215                         size   = sectors512(label[8], blocksize);
216                 }
217                 sp[0].start = sectors512(offset, blocksize);
218                 sp[0].size  = size - sp[0].start;
219                 retval = 1;
220         } else if ((strncmp(type, "VOL1", 4) == 0) &&
221                 (!info.FBA_layout) && (!strcmp(info.type, "ECKD"))) {
222                 /*
223                  * New style VOL1 labeled disk
224                  */
225                 int counter;
226
227                 /* get block number and read then go through format1 labels */
228                 blk = cchhb2blk(&vlabel.vtoc, &geo) + 1;
229                 counter = 0;
230                 if (lseek(fd_dasd, blk * blocksize, SEEK_SET) == -1)
231                         goto out;
232
233                 while (read(fd_dasd, data, blocksize) != -1) {
234                         format1_label_t f1;
235
236                         memcpy(&f1, data, sizeof(format1_label_t));
237
238                         /* skip FMT4 / FMT5 / FMT7 labels */
239                         if (EBCtoASC[f1.DS1FMTID] == '4'
240                             || EBCtoASC[f1.DS1FMTID] == '5'
241                             || EBCtoASC[f1.DS1FMTID] == '7'
242                             || EBCtoASC[f1.DS1FMTID] == '9') {
243                                 blk++;
244                                 continue;
245                         }
246
247                         /* only FMT1 and FMT8 valid at this point */
248                         if (EBCtoASC[f1.DS1FMTID] != '1' &&
249                             EBCtoASC[f1.DS1FMTID] != '8')
250                                 break;
251
252                         /* OK, we got valid partition data */
253                         offset = cchh2blk(&f1.DS1EXT1.llimit, &geo);
254                         size  = cchh2blk(&f1.DS1EXT1.ulimit, &geo) -
255                                 offset + geo.sectors;
256                         sp[counter].start = sectors512(offset, blocksize);
257                         sp[counter].size  = sectors512(size, blocksize);
258                         counter++;
259                         blk++;
260                 }
261                 retval = counter;
262         } else {
263                 /*
264                  * Old style LNX1 or unlabeled disk
265                  */
266                 if (strncmp(type, "LNX1", 4) == 0) {
267                         if (vlabel.ldl_version == 0xf2) {
268                                 fmt_size = sectors512(vlabel.formatted_blocks,
269                                                       blocksize);
270                         } else if (!strcmp(info.type, "ECKD")) {
271                                 /* formatted w/o large volume support */
272                                 fmt_size = geo.cylinders * geo.heads
273                                         * geo.sectors * (blocksize >> 9);
274                         } else {
275                                 /* old label and no usable disk geometry
276                                  * (e.g. DIAG) */
277                                 fmt_size = disksize;
278                         }
279                         size = disksize;
280                         if (fmt_size < size)
281                                 size = fmt_size;
282                 } else
283                         size = disksize;
284
285                 sp[0].start = sectors512(info.label_block + 1, blocksize);
286                 sp[0].size  = size - sp[0].start;
287                 retval = 1;
288         }
289
290 out:
291         if (data != NULL)
292                 free(data);
293         close(fd_dasd);
294         return retval;
295 }