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